mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-16 05:47:06 +09:00
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:
@@ -274,7 +274,7 @@ func main() {
|
|||||||
fmt.Println("Micro encountered an error:", err)
|
fmt.Println("Micro encountered an error:", err)
|
||||||
// backup all open buffers
|
// backup all open buffers
|
||||||
for _, b := range buffer.OpenBuffers {
|
for _, b := range buffer.OpenBuffers {
|
||||||
b.Backup(false)
|
b.Backup()
|
||||||
}
|
}
|
||||||
// Print the stack trace too
|
// Print the stack trace too
|
||||||
fmt.Print(errors.Wrap(err, 2).ErrorStack())
|
fmt.Print(errors.Wrap(err, 2).ErrorStack())
|
||||||
|
|||||||
@@ -28,29 +28,53 @@ The backup was created on %s, and the file is
|
|||||||
|
|
||||||
Options: [r]ecover, [i]gnore: `
|
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
|
// 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 {
|
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkTime {
|
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
|
||||||
sub := time.Now().Sub(b.lastbackup)
|
if len(backupdir) == 0 || err != nil {
|
||||||
if sub < time.Duration(backupTime)*time.Millisecond {
|
backupdir = filepath.Join(config.ConfigDir, "backups")
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.lastbackup = time.Now()
|
|
||||||
|
|
||||||
backupdir := filepath.Join(config.ConfigDir, "backups")
|
|
||||||
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
|
||||||
os.Mkdir(backupdir, os.ModePerm)
|
os.Mkdir(backupdir, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
|
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 {
|
if len(b.lines) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,12 +98,14 @@ func (b *Buffer) Backup(checkTime bool) error {
|
|||||||
return
|
return
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
|
b.requestedBackup = false
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveBackup removes any backup file associated with this buffer
|
// RemoveBackup removes any backup file associated with this buffer
|
||||||
func (b *Buffer) RemoveBackup() {
|
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
|
return
|
||||||
}
|
}
|
||||||
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
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)
|
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
|
||||||
// Returns true if a backup was applied
|
// Returns true if a backup was applied
|
||||||
func (b *Buffer) ApplyBackup(fsize int64) bool {
|
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))
|
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
|
||||||
if info, err := os.Stat(backupfile); err == nil {
|
if info, err := os.Stat(backupfile); err == nil {
|
||||||
backup, err := os.Open(backupfile)
|
backup, err := os.Open(backupfile)
|
||||||
|
|||||||
@@ -102,9 +102,7 @@ type SharedBuffer struct {
|
|||||||
diffLock sync.RWMutex
|
diffLock sync.RWMutex
|
||||||
diff map[int]DiffStatus
|
diff map[int]DiffStatus
|
||||||
|
|
||||||
// counts the number of edits
|
requestedBackup bool
|
||||||
// resets every backupTime edits
|
|
||||||
lastbackup time.Time
|
|
||||||
|
|
||||||
// ReloadDisabled allows the user to disable reloads if they
|
// ReloadDisabled allows the user to disable reloads if they
|
||||||
// are viewing a file that is constantly changing
|
// 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 {
|
if !found {
|
||||||
b.SharedBuffer = new(SharedBuffer)
|
b.SharedBuffer = new(SharedBuffer)
|
||||||
b.Type = btype
|
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"
|
b.Settings["encoding"] = "utf-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
hasBackup := b.ApplyBackup(size)
|
hasBackup = b.ApplyBackup(size)
|
||||||
|
|
||||||
if !hasBackup {
|
if !hasBackup {
|
||||||
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
|
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 size > LargeFileThreshold {
|
||||||
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
// If the file is larger than LargeFileThreshold fastdirty needs to be on
|
||||||
b.Settings["fastdirty"] = true
|
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)
|
calcHash(b, &b.origHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,7 +426,7 @@ func (b *Buffer) Insert(start Loc, text string) {
|
|||||||
b.EventHandler.active = b.curCursor
|
b.EventHandler.active = b.curCursor
|
||||||
b.EventHandler.Insert(start, text)
|
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.active = b.curCursor
|
||||||
b.EventHandler.Remove(start, end)
|
b.EventHandler.Remove(start, end)
|
||||||
|
|
||||||
go b.Backup(true)
|
b.RequestBackup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ var defaultCommonSettings = map[string]interface{}{
|
|||||||
"autoindent": true,
|
"autoindent": true,
|
||||||
"autosu": false,
|
"autosu": false,
|
||||||
"backup": true,
|
"backup": true,
|
||||||
|
"backupdir": "",
|
||||||
"basename": false,
|
"basename": false,
|
||||||
"colorcolumn": float64(0),
|
"colorcolumn": float64(0),
|
||||||
"cursorline": true,
|
"cursorline": true,
|
||||||
@@ -261,6 +262,7 @@ var defaultCommonSettings = map[string]interface{}{
|
|||||||
"keepautoindent": false,
|
"keepautoindent": false,
|
||||||
"matchbrace": true,
|
"matchbrace": true,
|
||||||
"mkparents": false,
|
"mkparents": false,
|
||||||
|
"permbackup": false,
|
||||||
"readonly": false,
|
"readonly": false,
|
||||||
"rmtrailingws": false,
|
"rmtrailingws": false,
|
||||||
"ruler": true,
|
"ruler": true,
|
||||||
|
|||||||
@@ -37,21 +37,26 @@ Here are the available options:
|
|||||||
closed cleanly. In the case of a system crash or a micro crash, the contents
|
closed cleanly. In the case of a system crash or a micro crash, the contents
|
||||||
of the buffer can be recovered automatically by opening the file that was
|
of the buffer can be recovered automatically by opening the file that was
|
||||||
being edited before the crash, or manually by searching for the backup in
|
being edited before the crash, or manually by searching for the backup in
|
||||||
the backup directory. Backups are made in the background when a buffer is
|
the backup directory. Backups are made in the background for newly modified
|
||||||
modified and the latest backup is more than 8 seconds old, or when micro
|
buffers every 8 seconds, or when micro detects a crash.
|
||||||
detects a crash. It is highly recommended that you leave this feature
|
|
||||||
enabled.
|
|
||||||
|
|
||||||
default value: `true`
|
default value: `true`
|
||||||
|
|
||||||
|
* `backupdir`: the directory micro should place backups in. For the default
|
||||||
|
value of `""` (empty string), the backup directory will be
|
||||||
|
`ConfigDir/backups`, which is `~/.config/micro/backups` by default. The
|
||||||
|
directory specified for backups will be created if it does not exist.
|
||||||
|
|
||||||
|
default value: `""` (empty string)
|
||||||
|
|
||||||
* `basename`: in the infobar and tabbar, show only the basename of the file
|
* `basename`: in the infobar and tabbar, show only the basename of the file
|
||||||
being edited rather than the full path.
|
being edited rather than the full path.
|
||||||
|
|
||||||
default value: `false`
|
default value: `false`
|
||||||
|
|
||||||
* `colorcolumn`: if this is not set to 0, it will display a column at the
|
* `colorcolumn`: if this is not set to 0, it will display a column at the
|
||||||
specified column. This is useful if you want column 80 to be highlighted
|
specified column. This is useful if you want column 80 to be highlighted
|
||||||
special for example.
|
special for example.
|
||||||
|
|
||||||
default value: `0`
|
default value: `0`
|
||||||
|
|
||||||
@@ -200,6 +205,13 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `false`
|
default value: `false`
|
||||||
|
|
||||||
|
* `permbackup`: this option causes backups (see `backup` option) to be
|
||||||
|
permanently saved. With permanent backups, micro will not remove backups when
|
||||||
|
files are closed and will never apply them to existing files. Use this option
|
||||||
|
if you are interested in manually managing your backup files.
|
||||||
|
|
||||||
|
default value: `false`
|
||||||
|
|
||||||
* `pluginchannels`: list of URLs pointing to plugin channels for downloading and
|
* `pluginchannels`: list of URLs pointing to plugin channels for downloading and
|
||||||
installing plugins. A plugin channel consists of a json file with links to
|
installing plugins. A plugin channel consists of a json file with links to
|
||||||
plugin repos, which store information about plugin versions and download URLs.
|
plugin repos, which store information about plugin versions and download URLs.
|
||||||
|
|||||||
Reference in New Issue
Block a user