Improve backup system

This commit introduces several improvements to the backup system.

* Backups are made every 8 seconds for buffers that have been modified
  since the last backup.

* The `permbackup` option allows users to specify that backups should
  be kept permanently.

* `The backupdir` option allows users to store backups in a custom
   directory.

Fixes #1641
Fixes #1536
Ref #1539 (removes possibility of race condition for backups)
This commit is contained in:
Zachary Yedidia
2020-06-22 17:54:56 -04:00
parent c5136820c4
commit a8332fd316
5 changed files with 68 additions and 27 deletions

View File

@@ -28,29 +28,53 @@ The backup was created on %s, and the file is
Options: [r]ecover, [i]gnore: `
var backupRequestChan chan *Buffer
func backupThread() {
for {
time.Sleep(time.Second * 8)
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
b.Backup()
}
}
}
func init() {
backupRequestChan = make(chan *Buffer, 10)
go backupThread()
}
func (b *Buffer) RequestBackup() {
if !b.requestedBackup {
select {
case backupRequestChan <- b:
default:
// channel is full
}
b.requestedBackup = true
}
}
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) Backup(checkTime bool) error {
func (b *Buffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
if checkTime {
sub := time.Now().Sub(b.lastbackup)
if sub < time.Duration(backupTime)*time.Millisecond {
return nil
}
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if len(backupdir) == 0 || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
b.lastbackup = time.Now()
backupdir := filepath.Join(config.ConfigDir, "backups")
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm)
}
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
@@ -74,12 +98,14 @@ func (b *Buffer) Backup(checkTime bool) error {
return
}, false)
b.requestedBackup = false
return err
}
// RemoveBackup removes any backup file associated with this buffer
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
return
}
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
@@ -89,7 +115,7 @@ func (b *Buffer) RemoveBackup() {
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
// Returns true if a backup was applied
func (b *Buffer) ApplyBackup(fsize int64) bool {
if b.Settings["backup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)

View File

@@ -102,9 +102,7 @@ type SharedBuffer struct {
diffLock sync.RWMutex
diff map[int]DiffStatus
// counts the number of edits
// resets every backupTime edits
lastbackup time.Time
requestedBackup bool
// ReloadDisabled allows the user to disable reloads if they
// are viewing a file that is constantly changing
@@ -271,6 +269,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
}
hasBackup := false
if !found {
b.SharedBuffer = new(SharedBuffer)
b.Type = btype
@@ -293,7 +292,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b.Settings["encoding"] = "utf-8"
}
hasBackup := b.ApplyBackup(size)
hasBackup = b.ApplyBackup(size)
if !hasBackup {
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
@@ -356,7 +355,9 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
if size > LargeFileThreshold {
// If the file is larger than LargeFileThreshold fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
} else if !hasBackup {
// since applying a backup does not save the applied backup to disk, we should
// not calculate the original hash based on the backup data
calcHash(b, &b.origHash)
}
}
@@ -425,7 +426,7 @@ func (b *Buffer) Insert(start Loc, text string) {
b.EventHandler.active = b.curCursor
b.EventHandler.Insert(start, text)
go b.Backup(true)
b.RequestBackup()
}
}
@@ -436,7 +437,7 @@ func (b *Buffer) Remove(start, end Loc) {
b.EventHandler.active = b.curCursor
b.EventHandler.Remove(start, end)
go b.Backup(true)
b.RequestBackup()
}
}

View File

@@ -247,6 +247,7 @@ var defaultCommonSettings = map[string]interface{}{
"autoindent": true,
"autosu": false,
"backup": true,
"backupdir": "",
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
@@ -261,6 +262,7 @@ var defaultCommonSettings = map[string]interface{}{
"keepautoindent": false,
"matchbrace": true,
"mkparents": false,
"permbackup": false,
"readonly": false,
"rmtrailingws": false,
"ruler": true,