From d8377140f146ca4b93cb52ed8792295911c07c55 Mon Sep 17 00:00:00 2001 From: Seb Patane Date: Mon, 14 Nov 2016 03:17:09 +1000 Subject: [PATCH] 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 --- src/cmds/restic/cmd_restore.go | 11 +++++++++- src/restic/node.go | 8 ++++--- src/restic/node_test.go | 2 +- src/restic/restorer.go | 39 +++++++++++++++++++++++++++++----- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/cmds/restic/cmd_restore.go b/src/cmds/restic/cmd_restore.go index 2e5253569..eba5026c3 100644 --- a/src/cmds/restic/cmd_restore.go +++ b/src/cmds/restic/cmd_restore.go @@ -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)) } diff --git a/src/restic/node.go b/src/restic/node.go index 81f3ef8d5..0f4d8a25b 100644 --- a/src/restic/node.go +++ b/src/restic/node.go @@ -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 diff --git a/src/restic/node_test.go b/src/restic/node_test.go index a2e175b14..ba42edc9e 100644 --- a/src/restic/node_test.go +++ b/src/restic/node_test.go @@ -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 diff --git a/src/restic/restorer.go b/src/restic/restorer.go index 3bbfee3be..5af62e6ef 100644 --- a/src/restic/restorer.go +++ b/src/restic/restorer.go @@ -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) }