]> git.lizzy.rs Git - micro.git/blob - internal/buffer/backup.go
Merge
[micro.git] / internal / buffer / backup.go
1 package buffer
2
3 import (
4         "fmt"
5         "io"
6         "os"
7         "path/filepath"
8         "sync/atomic"
9         "time"
10
11         "github.com/zyedidia/micro/v2/internal/config"
12         "github.com/zyedidia/micro/v2/internal/screen"
13         "github.com/zyedidia/micro/v2/internal/util"
14         "golang.org/x/text/encoding"
15 )
16
17 const backupMsg = `A backup was detected for this file. This likely means that micro
18 crashed while editing this file, or another instance of micro is currently
19 editing this file.
20
21 The backup was created on %s, and the file is
22
23 %s
24
25 * 'recover' will apply the backup as unsaved changes to the current buffer.
26   When the buffer is closed, the backup will be removed.
27 * 'ignore' will ignore the backup, discarding its changes. The backup file
28   will be removed.
29
30 Options: [r]ecover, [i]gnore: `
31
32 var backupRequestChan chan *Buffer
33
34 func backupThread() {
35         for {
36                 time.Sleep(time.Second * 8)
37
38                 for len(backupRequestChan) > 0 {
39                         b := <-backupRequestChan
40                         bfini := atomic.LoadInt32(&(b.fini)) != 0
41                         if !bfini {
42                                 b.Backup()
43                         }
44                 }
45         }
46 }
47
48 func init() {
49         backupRequestChan = make(chan *Buffer, 10)
50
51         go backupThread()
52 }
53
54 func (b *Buffer) RequestBackup() {
55         if !b.requestedBackup {
56                 select {
57                 case backupRequestChan <- b:
58                 default:
59                         // channel is full
60                 }
61                 b.requestedBackup = true
62         }
63 }
64
65 // Backup saves the current buffer to ConfigDir/backups
66 func (b *Buffer) Backup() error {
67         if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
68                 return nil
69         }
70
71         backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
72         if backupdir == "" || err != nil {
73                 backupdir = filepath.Join(config.ConfigDir, "backups")
74         }
75         if _, err := os.Stat(backupdir); os.IsNotExist(err) {
76                 os.Mkdir(backupdir, os.ModePerm)
77         }
78
79         name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
80
81         err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
82                 if len(b.lines) == 0 {
83                         return
84                 }
85
86                 // end of line
87                 eol := []byte{'\n'}
88
89                 // write lines
90                 if _, e = file.Write(b.lines[0].data); e != nil {
91                         return
92                 }
93
94                 for _, l := range b.lines[1:] {
95                         if _, e = file.Write(eol); e != nil {
96                                 return
97                         }
98                         if _, e = file.Write(l.data); e != nil {
99                                 return
100                         }
101                 }
102                 return
103         }, false)
104
105         b.requestedBackup = false
106
107         return err
108 }
109
110 // RemoveBackup removes any backup file associated with this buffer
111 func (b *Buffer) RemoveBackup() {
112         if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
113                 return
114         }
115         f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
116         os.Remove(f)
117 }
118
119 // ApplyBackup applies the corresponding backup file to this buffer (if one exists)
120 // Returns true if a backup was applied
121 func (b *Buffer) ApplyBackup(fsize int64) bool {
122         if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
123                 backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
124                 if info, err := os.Stat(backupfile); err == nil {
125                         backup, err := os.Open(backupfile)
126                         if err == nil {
127                                 defer backup.Close()
128                                 t := info.ModTime()
129                                 msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
130                                 choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
131
132                                 if choice%2 == 0 {
133                                         // recover
134                                         b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
135                                         b.isModified = true
136                                         return true
137                                 } else if choice%2 == 1 {
138                                         // delete
139                                         os.Remove(backupfile)
140                                 }
141                         }
142                 }
143         }
144
145         return false
146 }