archiver: pass ItemAction to progress reporter instead of data.Node (#21886)

This commit is contained in:
Michael Eischer
2026-06-20 19:06:45 +02:00
committed by GitHub
parent 57ac5d107c
commit 532f36a5a8
4 changed files with 84 additions and 56 deletions
+21 -26
View File
@@ -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++
}
}
+53
View File
@@ -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 ""
}
}
+8 -25
View File
@@ -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)
}
}
+2 -5
View File
@@ -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()