Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

save: Save files atomically #3273

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a56c5f3
actions: SaveAs: Print the error of `os.Stat()` to the `InfoBar`
JoeKar Apr 29, 2024
3b7c9de
save: Convert `os.IsNotExist()` into `errors.Is()`
JoeKar May 1, 2024
d97d8a5
open & write: Process regular files only
JoeKar May 12, 2024
e6e24b6
buffer: Convert `os.Is()` into `errors.Is()`
JoeKar May 29, 2024
17d4056
backup: Convert `os.IsNotExist()` into `errors.Is()`
JoeKar May 12, 2024
bd3c16c
backup: Store the file with the endings of the buffer
JoeKar May 23, 2024
cfeed95
backup: Lock the buffer lines in `Backup()`
JoeKar May 24, 2024
fdc0709
bindings: Convert `os.IsNotExist()` into `errors.Is()`
JoeKar May 12, 2024
4cb0586
clean: Inform about all failed write steps
JoeKar May 12, 2024
85b3268
clean: Remove some unneeded `filepath.Join()` calls
JoeKar May 12, 2024
2a8a891
util: Improve `EscapePath()` to apply different encodings
JoeKar May 24, 2024
f7f8783
util: Generalize the file mode of 0666 with `util.FileMode`
JoeKar May 30, 2024
dc701a1
ioutil: Remove deprecated functions where possible
JoeKar May 30, 2024
efbab5a
config: Make `backupdir` & `permbackup` global-only settings
JoeKar May 26, 2024
b60611d
util: Provide `SafeWrite()` to generalize the file write process
JoeKar May 29, 2024
0a1e17d
save: Use new `SafeWrite()`
JoeKar May 30, 2024
89507ce
backup: Use new `SafeWrite()`
JoeKar May 31, 2024
72d3656
backup: Prevent async backup when save was successfully performed
JoeKar Jun 4, 2024
5a0690b
serialize: Use new `SafeWrite()`
JoeKar Jun 1, 2024
e31da2f
bindings: Use new `SafeWrite()`
JoeKar Jun 1, 2024
8c6835c
settings: Use new `SafeWrite()`
JoeKar Jun 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 14 additions & 9 deletions cmd/micro/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bufio"
"encoding/gob"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -39,7 +38,12 @@ func CleanConfig() {
}

fmt.Println("Cleaning default settings")
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))

settingsFile := filepath.Join(config.ConfigDir, "settings.json")
err := config.WriteSettings(settingsFile)
if err != nil {
fmt.Println("Error writing settings.json file: " + err.Error())
}

// detect unused options
var unusedOptions []string
Expand Down Expand Up @@ -67,16 +71,16 @@ func CleanConfig() {
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
}

fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
fmt.Printf("These options will be removed from %s\n", settingsFile)

if shouldContinue() {
for _, s := range unusedOptions {
delete(config.GlobalSettings, s)
}

err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
err := config.OverwriteSettings(settingsFile)
if err != nil {
fmt.Println("Error writing settings.json file: " + err.Error())
fmt.Println("Error overwriting settings.json file: " + err.Error())
}

fmt.Println("Removed unused options")
Expand All @@ -85,12 +89,13 @@ func CleanConfig() {
}

// detect incorrectly formatted buffer/ files
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
buffersPath := filepath.Join(config.ConfigDir, "buffers")
files, err := os.ReadDir(buffersPath)
if err == nil {
var badFiles []string
var buffer buffer.SerializedBuffer
for _, f := range files {
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
fname := filepath.Join(buffersPath, f.Name())
file, e := os.Open(fname)

if e == nil {
Expand All @@ -105,9 +110,9 @@ func CleanConfig() {
}

if len(badFiles) > 0 {
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), buffersPath)
fmt.Println("These files store cursor and undo history.")
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
fmt.Printf("Removing badly formatted files in %s\n", buffersPath)

if shouldContinue() {
removed := 0
Expand Down
2 changes: 1 addition & 1 deletion cmd/micro/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func (NullWriter) Write(data []byte) (n int, err error) {
// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables
func InitLog() {
if util.Debug == "ON" {
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
f, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/micro/micro.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
Expand Down Expand Up @@ -210,7 +209,7 @@ func LoadInput(args []string) []*buffer.Buffer {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err = ioutil.ReadAll(os.Stdin)
input, err = io.ReadAll(os.Stdin)
if err != nil {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
Expand Down
13 changes: 6 additions & 7 deletions cmd/micro/micro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
Expand All @@ -26,7 +25,7 @@ func init() {
func startup(args []string) (tcell.SimulationScreen, error) {
var err error

tempDir, err = ioutil.TempDir("", "micro_test")
tempDir, err = os.MkdirTemp("", "micro_test")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -165,7 +164,7 @@ func findBuffer(file string) *buffer.Buffer {
}

func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name)
testf, err := os.CreateTemp("", name)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -223,7 +222,7 @@ func TestSimpleEdit(t *testing.T) {

injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)

data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
t.Error(err)
return
Expand Down Expand Up @@ -275,7 +274,7 @@ func TestMouse(t *testing.T) {
// base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)

data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
t.Error(err)
return
Expand Down Expand Up @@ -321,7 +320,7 @@ func TestSearchAndReplace(t *testing.T) {

injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)

data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
t.Error(err)
return
Expand All @@ -337,7 +336,7 @@ func TestSearchAndReplace(t *testing.T) {

injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)

data, err = ioutil.ReadFile(file)
data, err = os.ReadFile(file)
if err != nil {
t.Error(err)
return
Expand Down
3 changes: 3 additions & 0 deletions internal/action/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,9 @@ func (h *BufPane) SaveAsCB(action string, callback func()) bool {
h.completeAction(action)
return
}
} else {
InfoBar.Error(err)
return
}
} else {
InfoBar.YNPrompt(
Expand Down
45 changes: 35 additions & 10 deletions internal/action/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io/fs"
"os"
"path/filepath"
"regexp"
Expand All @@ -14,6 +14,7 @@ import (
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell/v2"
)

Expand All @@ -23,9 +24,29 @@ var Binder = map[string]func(e Event, action string){
"terminal": TermMapEvent,
}

type BindingsWriter struct {
txt []byte
}

func (w BindingsWriter) Overwrite(name string, isBackup bool) error {
return os.WriteFile(name, w.txt, util.FileMode)
}

func (w BindingsWriter) BackupDir() string {
backupdir, err := util.ReplaceHome(config.GlobalSettings["backupdir"].(string))
if backupdir == "" || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
return backupdir
}

func (w BindingsWriter) KeepBackup() bool {
return config.GlobalSettings["permbackup"].(bool)
}

func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
if _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {
util.SafeWrite(fname, BindingsWriter{[]byte("{}")})
}
}

Expand All @@ -37,7 +58,7 @@ func InitBindings() {
createBindingsIfNotExist(filename)

if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
screen.TermMessage("Error reading bindings.json file: " + err.Error())
return
Expand Down Expand Up @@ -278,7 +299,7 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
return false, errors.New("Error reading bindings.json file: " + err.Error())
}
Expand Down Expand Up @@ -316,8 +337,10 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {

BindKey(k, v, Binder["buffer"])

txt, _ := json.MarshalIndent(parsed, "", " ")
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
var w BindingsWriter
w.txt, _ = json.MarshalIndent(parsed, "", " ")
w.txt = append(w.txt, '\n')
return true, util.SafeWrite(filename, w)
}
return false, e
}
Expand All @@ -330,7 +353,7 @@ func UnbindKey(k string) error {
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
input, err := os.ReadFile(filename)
if err != nil {
return errors.New("Error reading bindings.json file: " + err.Error())
}
Expand Down Expand Up @@ -366,8 +389,10 @@ func UnbindKey(k string) error {
delete(config.Bindings["buffer"], k)
}

txt, _ := json.MarshalIndent(parsed, "", " ")
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
var w BindingsWriter
w.txt, _ = json.MarshalIndent(parsed, "", " ")
w.txt = append(w.txt, '\n')
return util.SafeWrite(filename, w)
}
return e
}
Expand Down
8 changes: 4 additions & 4 deletions internal/buffer/autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package buffer

import (
"bytes"
"io/ioutil"
"io/fs"
"os"
"sort"
"strings"
Expand Down Expand Up @@ -109,15 +109,15 @@ func FileComplete(b *Buffer) ([]string, []string) {
sep := string(os.PathSeparator)
dirs := strings.Split(input, sep)

var files []os.FileInfo
var files []fs.DirEntry
var err error
if len(dirs) > 1 {
directories := strings.Join(dirs[:len(dirs)-1], sep) + sep

directories, _ = util.ReplaceHome(directories)
files, err = ioutil.ReadDir(directories)
files, err = os.ReadDir(directories)
} else {
files, err = ioutil.ReadDir(".")
files, err = os.ReadDir(".")
}

if err != nil {
Expand Down