Skip to content

Commit 9592bb1

Browse files
committed
save: Further rework of overwriteFile()
- extract the open logic into `openFile()` and return a `wrappedFile` - extract the closing logic into `Close()` and make a method of `wrappedFile` - rename `writeFile()` into `Write()` and make a method of `wrappedFile` This allows to use the split parts alone while keeping overwriteFile() as simple interface to use all in a row.
1 parent f8d9855 commit 9592bb1

2 files changed

Lines changed: 111 additions & 67 deletions

File tree

internal/buffer/backup.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ func (b *Buffer) Backup() error {
8888

8989
name := util.DetermineEscapePath(backupdir, b.AbsPath)
9090
if _, err := os.Stat(name); errors.Is(err, fs.ErrNotExist) {
91-
_, err = b.overwriteFile(name, false)
91+
_, err = b.overwriteFile(name)
9292
if err == nil {
9393
b.requestedBackup = false
9494
}
9595
return err
9696
}
9797

9898
tmp := util.AppendBackupSuffix(name)
99-
_, err := b.overwriteFile(tmp, false)
99+
_, err := b.overwriteFile(tmp)
100100
if err != nil {
101101
os.Remove(tmp)
102102
return err

internal/buffer/save.go

Lines changed: 109 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,63 @@ import (
2424
// because hashing is too slow
2525
const LargeFileThreshold = 50000
2626

27-
func (b *Buffer) writeFile(file io.Writer) (int, error) {
27+
type wrappedFile struct {
28+
writeCloser io.WriteCloser
29+
withSudo bool
30+
screenb bool
31+
cmd *exec.Cmd
32+
sigChan chan os.Signal
33+
}
34+
35+
func openFile(name string, withSudo bool) (wrappedFile, error) {
36+
var err error
37+
var writeCloser io.WriteCloser
38+
var screenb bool
39+
var cmd *exec.Cmd
40+
var sigChan chan os.Signal
41+
42+
if withSudo {
43+
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
44+
writeCloser, err = cmd.StdinPipe()
45+
if err != nil {
46+
return wrappedFile{}, err
47+
}
48+
49+
sigChan = make(chan os.Signal, 1)
50+
signal.Reset(os.Interrupt)
51+
signal.Notify(sigChan, os.Interrupt)
52+
53+
screenb = screen.TempFini()
54+
// need to start the process now, otherwise when we flush the file
55+
// contents to its stdin it might hang because the kernel's pipe size
56+
// is too small to handle the full file contents all at once
57+
err = cmd.Start()
58+
if err != nil {
59+
screen.TempStart(screenb)
60+
61+
signal.Notify(util.Sigterm, os.Interrupt)
62+
signal.Stop(sigChan)
63+
64+
return wrappedFile{}, err
65+
}
66+
} else {
67+
writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)
68+
if err != nil {
69+
return wrappedFile{}, err
70+
}
71+
}
72+
73+
return wrappedFile{writeCloser, withSudo, screenb, cmd, sigChan}, nil
74+
}
75+
76+
func (wf wrappedFile) Write(b *Buffer) (int, error) {
77+
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
78+
if err != nil {
79+
return 0, err
80+
}
81+
82+
file := bufio.NewWriter(transform.NewWriter(wf.writeCloser, enc.NewEncoder()))
83+
2884
b.Lock()
2985
defer b.Unlock()
3086

@@ -40,6 +96,14 @@ func (b *Buffer) writeFile(file io.Writer) (int, error) {
4096
eol = []byte{'\n'}
4197
}
4298

99+
if !wf.withSudo {
100+
f := wf.writeCloser.(*os.File)
101+
err = f.Truncate(0)
102+
if err != nil {
103+
return 0, err
104+
}
105+
}
106+
43107
// write lines
44108
size, err := file.Write(b.lines[0].data)
45109
if err != nil {
@@ -55,78 +119,45 @@ func (b *Buffer) writeFile(file io.Writer) (int, error) {
55119
}
56120
size += len(eol) + len(l.data)
57121
}
58-
return size, nil
59-
}
60122

61-
func (b *Buffer) overwriteFile(name string, withSudo bool) (int, error) {
62-
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
63-
if err != nil {
64-
return 0, err
123+
err = file.Flush()
124+
if err == nil && !wf.withSudo {
125+
// Call Sync() on the file to make sure the content is safely on disk.
126+
f := wf.writeCloser.(*os.File)
127+
err = f.Sync()
65128
}
129+
return size, err
130+
}
66131

67-
var writeCloser io.WriteCloser
68-
var screenb bool
69-
var cmd *exec.Cmd
70-
var c chan os.Signal
132+
func (wf wrappedFile) Close() error {
133+
err := wf.writeCloser.Close()
134+
if wf.withSudo {
135+
// wait for dd to finish and restart the screen if we used sudo
136+
err := wf.cmd.Wait()
137+
screen.TempStart(wf.screenb)
71138

72-
if withSudo {
73-
cmd = exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
139+
signal.Notify(util.Sigterm, os.Interrupt)
140+
signal.Stop(wf.sigChan)
74141

75-
if writeCloser, err = cmd.StdinPipe(); err != nil {
76-
return 0, err
142+
if err != nil {
143+
return err
77144
}
145+
}
146+
return err
147+
}
78148

79-
c = make(chan os.Signal, 1)
80-
signal.Reset(os.Interrupt)
81-
signal.Notify(c, os.Interrupt)
82-
83-
screenb = screen.TempFini()
84-
// need to start the process now, otherwise when we flush the file
85-
// contents to its stdin it might hang because the kernel's pipe size
86-
// is too small to handle the full file contents all at once
87-
if err = cmd.Start(); err != nil {
88-
screen.TempStart(screenb)
89-
90-
signal.Notify(util.Sigterm, os.Interrupt)
91-
signal.Stop(c)
92-
93-
return 0, err
94-
}
95-
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, util.FileMode); err != nil {
149+
func (b *Buffer) overwriteFile(name string) (int, error) {
150+
file, err := openFile(name, false)
151+
if err != nil {
96152
return 0, err
97153
}
98154

99-
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
100-
size, err := b.writeFile(w)
155+
size, err := file.Write(b)
101156

102-
if err2 := w.Flush(); err2 != nil && err == nil {
157+
err2 := file.Close()
158+
if err2 != nil && err == nil {
103159
err = err2
104160
}
105-
// Call Sync() on the file to make sure the content is safely on disk.
106-
// Does not work with sudo as we don't have direct access to the file.
107-
if !withSudo {
108-
f := writeCloser.(*os.File)
109-
if err2 := f.Sync(); err2 != nil && err == nil {
110-
err = err2
111-
}
112-
}
113-
if err2 := writeCloser.Close(); err2 != nil && err == nil {
114-
err = err2
115-
}
116-
117-
if withSudo {
118-
// wait for dd to finish and restart the screen if we used sudo
119-
err := cmd.Wait()
120-
screen.TempStart(screenb)
121-
122-
signal.Notify(util.Sigterm, os.Interrupt)
123-
signal.Stop(c)
124-
125-
if err != nil {
126-
return size, err
127-
}
128-
}
129-
130161
return size, err
131162
}
132163

@@ -262,6 +293,17 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
262293
// This means that the file is not overwritten directly but by writing to the
263294
// backup file first.
264295
func (b *Buffer) safeWrite(path string, withSudo bool, newFile bool) (int, error) {
296+
file, err := openFile(path, withSudo)
297+
if err != nil {
298+
return 0, err
299+
}
300+
301+
defer func() {
302+
if newFile && err != nil {
303+
os.Remove(path)
304+
}
305+
}()
306+
265307
backupDir := b.backupDir()
266308
if _, err := os.Stat(backupDir); err != nil {
267309
if !errors.Is(err, fs.ErrNotExist) {
@@ -273,18 +315,15 @@ func (b *Buffer) safeWrite(path string, withSudo bool, newFile bool) (int, error
273315
}
274316

275317
backupName := util.DetermineEscapePath(backupDir, path)
276-
_, err := b.overwriteFile(backupName, false)
318+
_, err = b.overwriteFile(backupName)
277319
if err != nil {
278320
os.Remove(backupName)
279321
return 0, err
280322
}
281323

282324
b.forceKeepBackup = true
283-
size, err := b.overwriteFile(path, withSudo)
325+
size, err := file.Write(b)
284326
if err != nil {
285-
if newFile {
286-
os.Remove(path)
287-
}
288327
return size, err
289328
}
290329
b.forceKeepBackup = false
@@ -293,5 +332,10 @@ func (b *Buffer) safeWrite(path string, withSudo bool, newFile bool) (int, error
293332
os.Remove(backupName)
294333
}
295334

335+
err2 := file.Close()
336+
if err2 != nil && err == nil {
337+
err = err2
338+
}
339+
296340
return size, err
297341
}

0 commit comments

Comments
 (0)