]> git.lizzy.rs Git - micro.git/blob - internal/buffer/backup.go
Add autoretab
[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 * 'abort' will abort the open operation, and instead open an empty buffer.
30
31 Options: [r]ecover, [i]gnore, [a]bort: `
32
33 var backupRequestChan chan *Buffer
34
35 func backupThread() {
36         for {
37                 time.Sleep(time.Second * 8)
38
39                 for len(backupRequestChan) > 0 {
40                         b := <-backupRequestChan
41                         bfini := atomic.LoadInt32(&(b.fini)) != 0
42                         if !bfini {
43                                 b.Backup()
44                         }
45                 }
46         }
47 }
48
49 func init() {
50         backupRequestChan = make(chan *Buffer, 10)
51
52         go backupThread()
53 }
54
55 func (b *Buffer) RequestBackup() {
56         if !b.requestedBackup {
57                 select {
58                 case backupRequestChan <- b:
59                 default:
60                         // channel is full
61                 }
62                 b.requestedBackup = true
63         }
64 }
65
66 // Backup saves the current buffer to ConfigDir/backups
67 func (b *Buffer) Backup() error {
68         if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
69                 return nil
70         }
71
72         backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
73         if backupdir == "" || err != nil {
74                 backupdir = filepath.Join(config.ConfigDir, "backups")
75         }
76         if _, err := os.Stat(backupdir); os.IsNotExist(err) {
77                 os.Mkdir(backupdir, os.ModePerm)
78         }
79
80         name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
81
82         err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
83                 if len(b.lines) == 0 {
84                         return
85                 }
86
87                 // end of line
88                 eol := []byte{'\n'}
89
90                 // write lines
91                 if _, e = file.Write(b.lines[0].data); e != nil {
92                         return
93                 }
94
95                 for _, l := range b.lines[1:] {
96                         if _, e = file.Write(eol); e != nil {
97                                 return
98                         }
99                         if _, e = file.Write(l.data); e != nil {
100                                 return
101                         }
102                 }
103                 return
104         }, false)
105
106         b.requestedBackup = false
107
108         return err
109 }
110
111 // RemoveBackup removes any backup file associated with this buffer
112 func (b *Buffer) RemoveBackup() {
113         if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
114                 return
115         }
116         f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
117         os.Remove(f)
118 }
119
120 // ApplyBackup applies the corresponding backup file to this buffer (if one exists)
121 // Returns true if a backup was applied
122 func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
123         if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
124                 backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
125                 if info, err := os.Stat(backupfile); err == nil {
126                         backup, err := os.Open(backupfile)
127                         if err == nil {
128                                 defer backup.Close()
129                                 t := info.ModTime()
130                                 msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
131                                 choice := screen.TermPrompt(msg, []string{"r", "i", "a", "recover", "ignore", "abort"}, true)
132
133                                 if choice%3 == 0 {
134                                         // recover
135                                         b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
136                                         b.isModified = true
137                                         return true, true
138                                 } else if choice%3 == 1 {
139                                         // delete
140                                         os.Remove(backupfile)
141                                 } else if choice%3 == 2 {
142                                         return false, false
143                                 }
144                         }
145                 }
146         }
147
148         return false, true
149 }