restore/ui: refactor for extensibility

This commit is contained in:
Michael Eischer 2024-05-31 13:43:57 +02:00
parent 6a4ae9d6b1
commit 64b7b6b975
6 changed files with 57 additions and 53 deletions

View File

@ -20,31 +20,31 @@ func (t *jsonPrinter) print(status interface{}) {
t.terminal.Print(ui.ToJSONString(status)) t.terminal.Print(ui.ToJSONString(status))
} }
func (t *jsonPrinter) Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { func (t *jsonPrinter) Update(p State, duration time.Duration) {
status := statusUpdate{ status := statusUpdate{
MessageType: "status", MessageType: "status",
SecondsElapsed: uint64(duration / time.Second), SecondsElapsed: uint64(duration / time.Second),
TotalFiles: filesTotal, TotalFiles: p.FilesTotal,
FilesRestored: filesFinished, FilesRestored: p.FilesFinished,
TotalBytes: allBytesTotal, TotalBytes: p.AllBytesTotal,
BytesRestored: allBytesWritten, BytesRestored: p.AllBytesWritten,
} }
if allBytesTotal > 0 { if p.AllBytesTotal > 0 {
status.PercentDone = float64(allBytesWritten) / float64(allBytesTotal) status.PercentDone = float64(p.AllBytesWritten) / float64(p.AllBytesTotal)
} }
t.print(status) t.print(status)
} }
func (t *jsonPrinter) Finish(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { func (t *jsonPrinter) Finish(p State, duration time.Duration) {
status := summaryOutput{ status := summaryOutput{
MessageType: "summary", MessageType: "summary",
SecondsElapsed: uint64(duration / time.Second), SecondsElapsed: uint64(duration / time.Second),
TotalFiles: filesTotal, TotalFiles: p.FilesTotal,
FilesRestored: filesFinished, FilesRestored: p.FilesFinished,
TotalBytes: allBytesTotal, TotalBytes: p.AllBytesTotal,
BytesRestored: allBytesWritten, BytesRestored: p.AllBytesWritten,
} }
t.print(status) t.print(status)
} }

View File

@ -10,20 +10,20 @@ import (
func TestJSONPrintUpdate(t *testing.T) { func TestJSONPrintUpdate(t *testing.T) {
term := &mockTerm{} term := &mockTerm{}
printer := NewJSONProgress(term) printer := NewJSONProgress(term)
printer.Update(3, 11, 29, 47, 5*time.Second) printer.Update(State{3, 11, 29, 47}, 5*time.Second)
test.Equals(t, []string{"{\"message_type\":\"status\",\"seconds_elapsed\":5,\"percent_done\":0.6170212765957447,\"total_files\":11,\"files_restored\":3,\"total_bytes\":47,\"bytes_restored\":29}\n"}, term.output) test.Equals(t, []string{"{\"message_type\":\"status\",\"seconds_elapsed\":5,\"percent_done\":0.6170212765957447,\"total_files\":11,\"files_restored\":3,\"total_bytes\":47,\"bytes_restored\":29}\n"}, term.output)
} }
func TestJSONPrintSummaryOnSuccess(t *testing.T) { func TestJSONPrintSummaryOnSuccess(t *testing.T) {
term := &mockTerm{} term := &mockTerm{}
printer := NewJSONProgress(term) printer := NewJSONProgress(term)
printer.Finish(11, 11, 47, 47, 5*time.Second) printer.Finish(State{11, 11, 47, 47}, 5*time.Second)
test.Equals(t, []string{"{\"message_type\":\"summary\",\"seconds_elapsed\":5,\"total_files\":11,\"files_restored\":11,\"total_bytes\":47,\"bytes_restored\":47}\n"}, term.output) test.Equals(t, []string{"{\"message_type\":\"summary\",\"seconds_elapsed\":5,\"total_files\":11,\"files_restored\":11,\"total_bytes\":47,\"bytes_restored\":47}\n"}, term.output)
} }
func TestJSONPrintSummaryOnErrors(t *testing.T) { func TestJSONPrintSummaryOnErrors(t *testing.T) {
term := &mockTerm{} term := &mockTerm{}
printer := NewJSONProgress(term) printer := NewJSONProgress(term)
printer.Finish(3, 11, 29, 47, 5*time.Second) printer.Finish(State{3, 11, 29, 47}, 5*time.Second)
test.Equals(t, []string{"{\"message_type\":\"summary\",\"seconds_elapsed\":5,\"total_files\":11,\"files_restored\":3,\"total_bytes\":47,\"bytes_restored\":29}\n"}, term.output) test.Equals(t, []string{"{\"message_type\":\"summary\",\"seconds_elapsed\":5,\"total_files\":11,\"files_restored\":3,\"total_bytes\":47,\"bytes_restored\":29}\n"}, term.output)
} }

View File

@ -7,15 +7,19 @@ import (
"github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/progress"
) )
type State struct {
FilesFinished uint64
FilesTotal uint64
AllBytesWritten uint64
AllBytesTotal uint64
}
type Progress struct { type Progress struct {
updater progress.Updater updater progress.Updater
m sync.Mutex m sync.Mutex
progressInfoMap map[string]progressInfoEntry progressInfoMap map[string]progressInfoEntry
filesFinished uint64 s State
filesTotal uint64
allBytesWritten uint64
allBytesTotal uint64
started time.Time started time.Time
printer ProgressPrinter printer ProgressPrinter
@ -32,8 +36,8 @@ type term interface {
} }
type ProgressPrinter interface { type ProgressPrinter interface {
Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) Update(progress State, duration time.Duration)
Finish(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) Finish(progress State, duration time.Duration)
} }
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress { func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
@ -51,9 +55,9 @@ func (p *Progress) update(runtime time.Duration, final bool) {
defer p.m.Unlock() defer p.m.Unlock()
if !final { if !final {
p.printer.Update(p.filesFinished, p.filesTotal, p.allBytesWritten, p.allBytesTotal, runtime) p.printer.Update(p.s, runtime)
} else { } else {
p.printer.Finish(p.filesFinished, p.filesTotal, p.allBytesWritten, p.allBytesTotal, runtime) p.printer.Finish(p.s, runtime)
} }
} }
@ -66,8 +70,8 @@ func (p *Progress) AddFile(size uint64) {
p.m.Lock() p.m.Lock()
defer p.m.Unlock() defer p.m.Unlock()
p.filesTotal++ p.s.FilesTotal++
p.allBytesTotal += size p.s.AllBytesTotal += size
} }
// AddProgress accumulates the number of bytes written for a file // AddProgress accumulates the number of bytes written for a file
@ -86,10 +90,10 @@ func (p *Progress) AddProgress(name string, bytesWrittenPortion uint64, bytesTot
entry.bytesWritten += bytesWrittenPortion entry.bytesWritten += bytesWrittenPortion
p.progressInfoMap[name] = entry p.progressInfoMap[name] = entry
p.allBytesWritten += bytesWrittenPortion p.s.AllBytesWritten += bytesWrittenPortion
if entry.bytesWritten == entry.bytesTotal { if entry.bytesWritten == entry.bytesTotal {
delete(p.progressInfoMap, name) delete(p.progressInfoMap, name)
p.filesFinished++ p.s.FilesFinished++
} }
} }

View File

@ -8,7 +8,7 @@ import (
) )
type printerTraceEntry struct { type printerTraceEntry struct {
filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64 progress State
duration time.Duration duration time.Duration
isFinished bool isFinished bool
@ -22,11 +22,11 @@ type mockPrinter struct {
const mockFinishDuration = 42 * time.Second const mockFinishDuration = 42 * time.Second
func (p *mockPrinter) Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { func (p *mockPrinter) Update(progress State, duration time.Duration) {
p.trace = append(p.trace, printerTraceEntry{filesFinished, filesTotal, allBytesWritten, allBytesTotal, duration, false}) p.trace = append(p.trace, printerTraceEntry{progress, duration, false})
} }
func (p *mockPrinter) Finish(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, _ time.Duration) { func (p *mockPrinter) Finish(progress State, _ time.Duration) {
p.trace = append(p.trace, printerTraceEntry{filesFinished, filesTotal, allBytesWritten, allBytesTotal, mockFinishDuration, true}) p.trace = append(p.trace, printerTraceEntry{progress, mockFinishDuration, true})
} }
func testProgress(fn func(progress *Progress) bool) printerTrace { func testProgress(fn func(progress *Progress) bool) printerTrace {
@ -45,7 +45,7 @@ func TestNew(t *testing.T) {
return false return false
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{0, 0, 0, 0, 0, false}, printerTraceEntry{State{0, 0, 0, 0}, 0, false},
}, result) }, result)
} }
@ -57,7 +57,7 @@ func TestAddFile(t *testing.T) {
return false return false
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{0, 1, 0, fileSize, 0, false}, printerTraceEntry{State{0, 1, 0, fileSize}, 0, false},
}, result) }, result)
} }
@ -71,7 +71,7 @@ func TestFirstProgressOnAFile(t *testing.T) {
return false return false
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{0, 1, expectedBytesWritten, expectedBytesTotal, 0, false}, printerTraceEntry{State{0, 1, expectedBytesWritten, expectedBytesTotal}, 0, false},
}, result) }, result)
} }
@ -86,7 +86,7 @@ func TestLastProgressOnAFile(t *testing.T) {
return false return false
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{1, 1, fileSize, fileSize, 0, false}, printerTraceEntry{State{1, 1, fileSize, fileSize}, 0, false},
}, result) }, result)
} }
@ -102,7 +102,7 @@ func TestLastProgressOnLastFile(t *testing.T) {
return false return false
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{2, 2, 50 + fileSize, 50 + fileSize, 0, false}, printerTraceEntry{State{2, 2, 50 + fileSize, 50 + fileSize}, 0, false},
}, result) }, result)
} }
@ -117,7 +117,7 @@ func TestSummaryOnSuccess(t *testing.T) {
return true return true
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{2, 2, 50 + fileSize, 50 + fileSize, mockFinishDuration, true}, printerTraceEntry{State{2, 2, 50 + fileSize, 50 + fileSize}, mockFinishDuration, true},
}, result) }, result)
} }
@ -132,6 +132,6 @@ func TestSummaryOnErrors(t *testing.T) {
return true return true
}) })
test.Equals(t, printerTrace{ test.Equals(t, printerTrace{
printerTraceEntry{1, 2, 50 + fileSize/2, 50 + fileSize, mockFinishDuration, true}, printerTraceEntry{State{1, 2, 50 + fileSize/2, 50 + fileSize}, mockFinishDuration, true},
}, result) }, result)
} }

View File

@ -17,30 +17,30 @@ func NewTextProgress(terminal term) ProgressPrinter {
} }
} }
func (t *textPrinter) Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { func (t *textPrinter) Update(p State, duration time.Duration) {
timeLeft := ui.FormatDuration(duration) timeLeft := ui.FormatDuration(duration)
formattedAllBytesWritten := ui.FormatBytes(allBytesWritten) formattedAllBytesWritten := ui.FormatBytes(p.AllBytesWritten)
formattedAllBytesTotal := ui.FormatBytes(allBytesTotal) formattedAllBytesTotal := ui.FormatBytes(p.AllBytesTotal)
allPercent := ui.FormatPercent(allBytesWritten, allBytesTotal) allPercent := ui.FormatPercent(p.AllBytesWritten, p.AllBytesTotal)
progress := fmt.Sprintf("[%s] %s %v files/dirs %s, total %v files/dirs %v", progress := fmt.Sprintf("[%s] %s %v files/dirs %s, total %v files/dirs %v",
timeLeft, allPercent, filesFinished, formattedAllBytesWritten, filesTotal, formattedAllBytesTotal) timeLeft, allPercent, p.FilesFinished, formattedAllBytesWritten, p.FilesTotal, formattedAllBytesTotal)
t.terminal.SetStatus([]string{progress}) t.terminal.SetStatus([]string{progress})
} }
func (t *textPrinter) Finish(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { func (t *textPrinter) Finish(p State, duration time.Duration) {
t.terminal.SetStatus([]string{}) t.terminal.SetStatus([]string{})
timeLeft := ui.FormatDuration(duration) timeLeft := ui.FormatDuration(duration)
formattedAllBytesTotal := ui.FormatBytes(allBytesTotal) formattedAllBytesTotal := ui.FormatBytes(p.AllBytesTotal)
var summary string var summary string
if filesFinished == filesTotal && allBytesWritten == allBytesTotal { if p.FilesFinished == p.FilesTotal && p.AllBytesWritten == p.AllBytesTotal {
summary = fmt.Sprintf("Summary: Restored %d files/dirs (%s) in %s", filesTotal, formattedAllBytesTotal, timeLeft) summary = fmt.Sprintf("Summary: Restored %d files/dirs (%s) in %s", p.FilesTotal, formattedAllBytesTotal, timeLeft)
} else { } else {
formattedAllBytesWritten := ui.FormatBytes(allBytesWritten) formattedAllBytesWritten := ui.FormatBytes(p.AllBytesWritten)
summary = fmt.Sprintf("Summary: Restored %d / %d files/dirs (%s / %s) in %s", summary = fmt.Sprintf("Summary: Restored %d / %d files/dirs (%s / %s) in %s",
filesFinished, filesTotal, formattedAllBytesWritten, formattedAllBytesTotal, timeLeft) p.FilesFinished, p.FilesTotal, formattedAllBytesWritten, formattedAllBytesTotal, timeLeft)
} }
t.terminal.Print(summary) t.terminal.Print(summary)

View File

@ -22,20 +22,20 @@ func (m *mockTerm) SetStatus(lines []string) {
func TestPrintUpdate(t *testing.T) { func TestPrintUpdate(t *testing.T) {
term := &mockTerm{} term := &mockTerm{}
printer := NewTextProgress(term) printer := NewTextProgress(term)
printer.Update(3, 11, 29, 47, 5*time.Second) printer.Update(State{3, 11, 29, 47}, 5*time.Second)
test.Equals(t, []string{"[0:05] 61.70% 3 files/dirs 29 B, total 11 files/dirs 47 B"}, term.output) test.Equals(t, []string{"[0:05] 61.70% 3 files/dirs 29 B, total 11 files/dirs 47 B"}, term.output)
} }
func TestPrintSummaryOnSuccess(t *testing.T) { func TestPrintSummaryOnSuccess(t *testing.T) {
term := &mockTerm{} term := &mockTerm{}
printer := NewTextProgress(term) printer := NewTextProgress(term)
printer.Finish(11, 11, 47, 47, 5*time.Second) printer.Finish(State{11, 11, 47, 47}, 5*time.Second)
test.Equals(t, []string{"Summary: Restored 11 files/dirs (47 B) in 0:05"}, term.output) test.Equals(t, []string{"Summary: Restored 11 files/dirs (47 B) in 0:05"}, term.output)
} }
func TestPrintSummaryOnErrors(t *testing.T) { func TestPrintSummaryOnErrors(t *testing.T) {
term := &mockTerm{} term := &mockTerm{}
printer := NewTextProgress(term) printer := NewTextProgress(term)
printer.Finish(3, 11, 29, 47, 5*time.Second) printer.Finish(State{3, 11, 29, 47}, 5*time.Second)
test.Equals(t, []string{"Summary: Restored 3 / 11 files/dirs (29 B / 47 B) in 0:05"}, term.output) test.Equals(t, []string{"Summary: Restored 3 / 11 files/dirs (29 B / 47 B) in 0:05"}, term.output)
} }