ui/backup: Use progress.Updater for progress updates

This commit is contained in:
Michael Eischer 2022-12-29 12:31:20 +01:00
parent e499bbe3ae
commit 4a7a6b06af
3 changed files with 30 additions and 65 deletions

View file

@ -483,16 +483,12 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
} }
progressReporter := backup.NewProgress(progressPrinter, progressReporter := backup.NewProgress(progressPrinter,
calculateProgressInterval(!gopts.Quiet, gopts.JSON)) calculateProgressInterval(!gopts.Quiet, gopts.JSON))
defer progressReporter.Done()
if opts.DryRun { if opts.DryRun {
repo.SetDryRun() repo.SetDryRun()
} }
wg, wgCtx := errgroup.WithContext(ctx)
cancelCtx, cancel := context.WithCancel(wgCtx)
defer cancel()
wg.Go(func() error { progressReporter.Run(cancelCtx); return nil })
if !gopts.JSON { if !gopts.JSON {
progressPrinter.V("lock repository") progressPrinter.V("lock repository")
} }
@ -590,6 +586,10 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
targets = []string{filename} targets = []string{filename}
} }
wg, wgCtx := errgroup.WithContext(ctx)
cancelCtx, cancel := context.WithCancel(wgCtx)
defer cancel()
if !opts.NoScan { if !opts.NoScan {
sc := archiver.NewScanner(targetFS) sc := archiver.NewScanner(targetFS)
sc.SelectByName = selectByNameFilter sc.SelectByName = selectByNameFilter

View file

@ -1,13 +1,12 @@
package backup package backup
import ( import (
"context"
"sync" "sync"
"time" "time"
"github.com/restic/restic/internal/archiver" "github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/signals" "github.com/restic/restic/internal/ui/progress"
) )
// A ProgressPrinter can print various progress messages. // A ProgressPrinter can print various progress messages.
@ -41,10 +40,10 @@ type Summary struct {
// Progress reports progress for the `backup` command. // Progress reports progress for the `backup` command.
type Progress struct { type Progress struct {
progress.Updater
mu sync.Mutex mu sync.Mutex
interval time.Duration start time.Time
start time.Time
scanStarted, scanFinished bool scanStarted, scanFinished bool
@ -52,66 +51,37 @@ type Progress struct {
processed, total Counter processed, total Counter
errors uint errors uint
closed chan struct{}
summary Summary summary Summary
printer ProgressPrinter printer ProgressPrinter
} }
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress { func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
return &Progress{ p := &Progress{
interval: interval, start: time.Now(),
start: time.Now(),
currentFiles: make(map[string]struct{}), currentFiles: make(map[string]struct{}),
closed: make(chan struct{}), printer: printer,
printer: printer,
} }
} p.Updater = *progress.NewUpdater(interval, func(runtime time.Duration, final bool) {
if final {
p.printer.Reset()
} else {
p.mu.Lock()
defer p.mu.Unlock()
if !p.scanStarted {
return
}
// Run regularly updates the status lines. It should be called in a separate var secondsRemaining uint64
// goroutine. if p.scanFinished {
func (p *Progress) Run(ctx context.Context) { secs := float64(runtime / time.Second)
defer close(p.closed) todo := float64(p.total.Bytes - p.processed.Bytes)
// Reset status when finished secondsRemaining = uint64(secs / float64(p.processed.Bytes) * todo)
defer p.printer.Reset() }
var tick <-chan time.Time p.printer.Update(p.total, p.processed, p.errors, p.currentFiles, p.start, secondsRemaining)
if p.interval != 0 {
t := time.NewTicker(p.interval)
defer t.Stop()
tick = t.C
}
signalsCh := signals.GetProgressChannel()
for {
var now time.Time
select {
case <-ctx.Done():
return
case now = <-tick:
case <-signalsCh:
now = time.Now()
} }
})
p.mu.Lock() return p
if !p.scanStarted {
p.mu.Unlock()
continue
}
var secondsRemaining uint64
if p.scanFinished {
secs := float64(now.Sub(p.start) / time.Second)
todo := float64(p.total.Bytes - p.processed.Bytes)
secondsRemaining = uint64(secs / float64(p.processed.Bytes) * todo)
}
p.printer.Update(p.total, p.processed, p.errors, p.currentFiles, p.start, secondsRemaining)
p.mu.Unlock()
}
} }
// Error is the error callback function for the archiver, it prints the error and returns nil. // Error is the error callback function for the archiver, it prints the error and returns nil.
@ -236,6 +206,6 @@ func (p *Progress) ReportTotal(item string, s archiver.ScanStats) {
// Finish prints the finishing messages. // Finish prints the finishing messages.
func (p *Progress) Finish(snapshotID restic.ID, dryrun bool) { func (p *Progress) Finish(snapshotID restic.ID, dryrun bool) {
// wait for the status update goroutine to shut down // wait for the status update goroutine to shut down
<-p.closed p.Updater.Done()
p.printer.Finish(snapshotID, p.start, &p.summary, dryrun) p.printer.Finish(snapshotID, p.start, &p.summary, dryrun)
} }

View file

@ -1,7 +1,6 @@
package backup package backup
import ( import (
"context"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -53,9 +52,6 @@ func TestProgress(t *testing.T) {
prnt := &mockPrinter{} prnt := &mockPrinter{}
prog := NewProgress(prnt, time.Millisecond) prog := NewProgress(prnt, time.Millisecond)
ctx, cancel := context.WithCancel(context.Background())
go prog.Run(ctx)
prog.StartFile("foo") prog.StartFile("foo")
prog.CompleteBlob(1024) prog.CompleteBlob(1024)
@ -67,7 +63,6 @@ func TestProgress(t *testing.T) {
prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0) prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0)
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
cancel()
id := restic.NewRandomID() id := restic.NewRandomID()
prog.Finish(id, false) prog.Finish(id, false)