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) }