1
0
Fork 0
mirror of https://github.com/restic/restic.git synced 2025-03-16 00:00:05 +01:00

Implement a progress bar for restores

* Adds a Scan function for Restores (similar to the one in the Archiver) - this returns the size & number of items to be restored for the specified repository snapshot (instead of the filesystem)
* We pass the scan results into the RestoreTo function which sorts out the Progress bar initialization
  * We can't do this in the restorer constructor like in the archiver because the filters aren't set up yet
  * At this stage this is just the same progress bar that is used for archiving
* The bytes completed stats are reported by the Node itself (which involves passing the progress reference through)
  * he Restorer reports the other stats - namely items completed & error count
This commit is contained in:
Seb Patane 2016-11-14 03:17:09 +10:00
parent 28202edf33
commit d8377140f1
4 changed files with 50 additions and 10 deletions

View file

@ -47,6 +47,10 @@ func init() {
flags.StringSliceVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
}
func newRestoreProgress(gopts GlobalOptions, todo restic.Stat) *restic.Progress {
return newArchiveProgress(gopts, todo)
}
func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
if len(args) != 1 {
return errors.Fatalf("no snapshot ID specified")
@ -132,5 +136,10 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target)
return res.RestoreTo(opts.Target)
stat, err := res.Scan()
if err != nil {
return err
}
return res.RestoreTo(opts.Target, newRestoreProgress(gopts, stat))
}

View file

@ -97,7 +97,7 @@ func nodeTypeFromFileInfo(fi os.FileInfo) string {
}
// CreateAt creates the node at the given path and restores all the meta data.
func (node *Node) CreateAt(path string, repo Repository) error {
func (node *Node) CreateAt(path string, repo Repository, p *Progress) error {
debug.Log("create node %v at %v", node.Name, path)
switch node.Type {
@ -106,7 +106,7 @@ func (node *Node) CreateAt(path string, repo Repository) error {
return err
}
case "file":
if err := node.createFileAt(path, repo); err != nil {
if err := node.createFileAt(path, repo, p); err != nil {
return err
}
case "symlink":
@ -191,7 +191,7 @@ func (node Node) createDirAt(path string) error {
return nil
}
func (node Node) createFileAt(path string, repo Repository) error {
func (node Node) createFileAt(path string, repo Repository, p *Progress) error {
f, err := fs.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
defer f.Close()
@ -221,6 +221,8 @@ func (node Node) createFileAt(path string, repo Repository) error {
if err != nil {
return errors.Wrap(err, "Write")
}
p.Report(Stat{Bytes: uint64(size)})
}
return nil

View file

@ -153,7 +153,7 @@ func TestNodeRestoreAt(t *testing.T) {
for _, test := range nodeTests {
nodePath := filepath.Join(tempdir, test.Name)
OK(t, test.CreateAt(nodePath, nil))
OK(t, test.CreateAt(nodePath, nil, nil))
if test.Type == "symlink" && runtime.GOOS == "windows" {
continue

View file

@ -12,8 +12,9 @@ import (
// Restorer is used to restore a snapshot to a directory.
type Restorer struct {
repo Repository
sn *Snapshot
repo Repository
sn *Snapshot
progressBar *Progress
Error func(dir string, node *Node, err error) error
SelectFilter func(item string, node *Node) bool
@ -39,6 +40,23 @@ func NewRestorer(repo Repository, id ID) (*Restorer, error) {
return r, nil
}
// Scan traverses the directories/files to be restored to collect restic.Stat information
func (res *Restorer) Scan() (Stat, error) {
var stat Stat
err := res.walk("", *res.sn.Tree, func(node *Node, dir string) error {
if node.Type == "dir" {
stat.Add(Stat{Dirs: 1})
} else {
stat.Add(Stat{Files: 1, Bytes: node.Size})
}
return nil
})
return stat, err
}
func (res *Restorer) walk(dir string, treeID ID, callback func(*Node, string) error) error {
tree, err := res.repo.LoadTree(treeID)
if err != nil {
@ -105,7 +123,7 @@ func (res *Restorer) restoreNodeTo(node *Node, dir string, dst string) error {
debug.Log("node %v, dir %v, dst %v", node.Name, dir, dst)
dstPath := filepath.Join(dst, dir, node.Name)
err := node.CreateAt(dstPath, res.repo)
err := node.CreateAt(dstPath, res.repo, res.progressBar)
if err != nil {
debug.Log("node.CreateAt(%s) error %v", dstPath, err)
}
@ -117,18 +135,25 @@ func (res *Restorer) restoreNodeTo(node *Node, dir string, dst string) error {
// Create parent directories and retry
err = fs.MkdirAll(filepath.Dir(dstPath), 0700)
if err == nil || os.IsExist(errors.Cause(err)) {
err = node.CreateAt(dstPath, res.repo)
err = node.CreateAt(dstPath, res.repo, res.progressBar)
}
}
if err != nil {
debug.Log("error %v", err)
res.progressBar.Report(Stat{Errors: 1})
err = res.Error(dstPath, node, err)
if err != nil {
return err
}
}
if node.Type == "dir" {
res.progressBar.Report(Stat{Dirs: 1})
} else {
res.progressBar.Report(Stat{Files: 1})
}
debug.Log("successfully restored %v", node.Name)
return nil
@ -136,7 +161,11 @@ func (res *Restorer) restoreNodeTo(node *Node, dir string, dst string) error {
// RestoreTo creates the directories and files in the snapshot below dir.
// Before an item is created, res.Filter is called.
func (res *Restorer) RestoreTo(dir string) error {
func (res *Restorer) RestoreTo(dir string, p *Progress) error {
res.progressBar = p
res.progressBar.Start()
defer res.progressBar.Done()
return res.restoreTo(dir, "", *res.sn.Tree)
}