From 532f36a5a89e5a1746312e5e593f626e148762a7 Mon Sep 17 00:00:00 2001 From: Michael Eischer <9106997+MichaelEischer@users.noreply.github.com> Date: Sat, 20 Jun 2026 19:06:45 +0200 Subject: [PATCH] archiver: pass ItemAction to progress reporter instead of data.Node (#21886) --- internal/archiver/archiver.go | 47 ++++++++++++------------- internal/archiver/progress.go | 53 +++++++++++++++++++++++++++++ internal/ui/backup/progress.go | 33 +++++------------- internal/ui/backup/progress_test.go | 7 ++-- 4 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 internal/archiver/progress.go diff --git a/internal/archiver/archiver.go b/internal/archiver/archiver.go index 037abf189..11be3b884 100644 --- a/internal/archiver/archiver.go +++ b/internal/archiver/archiver.go @@ -110,11 +110,11 @@ type Archiver struct { // particular file/dir. // // Once reading a file has completed successfully (but not saving it yet), - // CompleteItem will be called with current == nil. + // CompleteItem will be called with a zero ItemAction. // // CompleteItem may be called asynchronously from several different // goroutines! - CompleteItem func(item string, previous, current *data.Node, s ItemStats, d time.Duration) + CompleteItem func(item string, action ItemAction, s ItemStats, d time.Duration) // StartFile is called when a file is being processed by a worker. StartFile func(filename string) @@ -180,7 +180,7 @@ func New(repo archiverRepo, filesystem fs.FS, opts Options) *Archiver { FS: filesystem, Options: opts.applyDefaults(), - CompleteItem: func(string, *data.Node, *data.Node, ItemStats, time.Duration) {}, + CompleteItem: func(string, ItemAction, ItemStats, time.Duration) {}, StartFile: func(string) {}, CompleteBlob: func(uint64) {}, } @@ -211,40 +211,35 @@ func (arch *Archiver) error(item string, err error) error { } func (arch *Archiver) trackItem(item string, previous, current *data.Node, s ItemStats, d time.Duration) { - arch.CompleteItem(item, previous, current, s, d) + action := itemProgressAction(previous, current) + arch.CompleteItem(item, action, s, d) arch.mu.Lock() defer arch.mu.Unlock() arch.summary.ItemStats.Add(s) - if current != nil { - arch.summary.ProcessedBytes += current.Size - } else { + if action.IsZero() { // last item or an error occurred return } - switch current.Type { - case data.NodeTypeDir: - switch { - case previous == nil: - arch.summary.Dirs.New++ - case previous.Equals(*current): - arch.summary.Dirs.Unchanged++ - default: - arch.summary.Dirs.Changed++ - } + arch.summary.ProcessedBytes += current.Size - case data.NodeTypeFile: - switch { - case previous == nil: - arch.summary.Files.New++ - case previous.Equals(*current): - arch.summary.Files.Unchanged++ - default: - arch.summary.Files.Changed++ - } + switch action { + case ActionDirNew: + arch.summary.Dirs.New++ + case ActionDirUnchanged: + arch.summary.Dirs.Unchanged++ + case ActionDirModified: + arch.summary.Dirs.Changed++ + + case ActionFileNew: + arch.summary.Files.New++ + case ActionFileUnchanged: + arch.summary.Files.Unchanged++ + case ActionFileModified: + arch.summary.Files.Changed++ } } diff --git a/internal/archiver/progress.go b/internal/archiver/progress.go new file mode 100644 index 000000000..bbe64f034 --- /dev/null +++ b/internal/archiver/progress.go @@ -0,0 +1,53 @@ +package archiver + +import "github.com/restic/restic/internal/data" + +// ItemAction describes backup progress for a single file or directory. +// The zero value indicates an error or incomplete item. +type ItemAction string + +// Constants for the different CompleteItem actions. +const ( + ActionDirNew ItemAction = "dir new" + ActionDirUnchanged ItemAction = "dir unchanged" + ActionDirModified ItemAction = "dir modified" + ActionFileNew ItemAction = "file new" + ActionFileUnchanged ItemAction = "file unchanged" + ActionFileModified ItemAction = "file modified" +) + +// IsZero reports whether the action describes a zero value. +func (a ItemAction) IsZero() bool { + return a == "" +} + +func itemProgressAction(previous, current *data.Node) ItemAction { + if current == nil { + return "" + } + + switch current.Type { + case data.NodeTypeDir: + switch { + case previous == nil: + return ActionDirNew + case previous.Equals(*current): + return ActionDirUnchanged + default: + return ActionDirModified + } + + case data.NodeTypeFile: + switch { + case previous == nil: + return ActionFileNew + case previous.Equals(*current): + return ActionFileUnchanged + default: + return ActionFileModified + } + + default: + return "" + } +} diff --git a/internal/ui/backup/progress.go b/internal/ui/backup/progress.go index c5af04413..dafac2bdc 100644 --- a/internal/ui/backup/progress.go +++ b/internal/ui/backup/progress.go @@ -5,7 +5,6 @@ import ( "time" "github.com/restic/restic/internal/archiver" - "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/ui/progress" ) @@ -118,44 +117,28 @@ func (p *Progress) CompleteBlob(bytes uint64) { // CompleteItem is the status callback function for the archiver when a // file/dir has been saved successfully. -func (p *Progress) CompleteItem(item string, previous, current *data.Node, s archiver.ItemStats, d time.Duration) { - if current == nil { - // error occurred, tell the status display to remove the line +func (p *Progress) CompleteItem(item string, action archiver.ItemAction, s archiver.ItemStats, d time.Duration) { + if action.IsZero() { + // file reading completed but not fully saved, tell the status display to remove the line p.mu.Lock() delete(p.currentFiles, item) p.mu.Unlock() return } - switch current.Type { - case data.NodeTypeDir: + switch action { + case archiver.ActionDirNew, archiver.ActionDirUnchanged, archiver.ActionDirModified: p.mu.Lock() p.addProcessed(Counter{Dirs: 1}) p.mu.Unlock() + p.printer.CompleteItem(string(action), item, s, d) - switch { - case previous == nil: - p.printer.CompleteItem("dir new", item, s, d) - case previous.Equals(*current): - p.printer.CompleteItem("dir unchanged", item, s, d) - default: - p.printer.CompleteItem("dir modified", item, s, d) - } - - case data.NodeTypeFile: + case archiver.ActionFileNew, archiver.ActionFileUnchanged, archiver.ActionFileModified: p.mu.Lock() p.addProcessed(Counter{Files: 1}) delete(p.currentFiles, item) p.mu.Unlock() - - switch { - case previous == nil: - p.printer.CompleteItem("file new", item, s, d) - case previous.Equals(*current): - p.printer.CompleteItem("file unchanged", item, s, d) - default: - p.printer.CompleteItem("file modified", item, s, d) - } + p.printer.CompleteItem(string(action), item, s, d) } } diff --git a/internal/ui/backup/progress_test.go b/internal/ui/backup/progress_test.go index ef396ef80..9940f6d44 100644 --- a/internal/ui/backup/progress_test.go +++ b/internal/ui/backup/progress_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/restic/restic/internal/archiver" - "github.com/restic/restic/internal/data" "github.com/restic/restic/internal/restic" ) @@ -54,11 +53,9 @@ func TestProgress(t *testing.T) { prog.CompleteBlob(1024) // "dir unchanged" - node := data.Node{Type: data.NodeTypeDir} - prog.CompleteItem("foo", &node, &node, archiver.ItemStats{}, 0) + prog.CompleteItem("foo", archiver.ActionDirUnchanged, archiver.ItemStats{}, 0) // "file new" - node.Type = data.NodeTypeFile - prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0) + prog.CompleteItem("foo", archiver.ActionFileNew, archiver.ItemStats{}, 0) time.Sleep(10 * time.Millisecond) id := restic.NewRandomID()