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))
}
func (t *jsonPrinter) Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) {
func (t *jsonPrinter) Update(p State, duration time.Duration) {
status := statusUpdate{
MessageType: "status",
SecondsElapsed: uint64(duration / time.Second),
TotalFiles: filesTotal,
FilesRestored: filesFinished,
TotalBytes: allBytesTotal,
BytesRestored: allBytesWritten,
TotalFiles: p.FilesTotal,
FilesRestored: p.FilesFinished,
TotalBytes: p.AllBytesTotal,
BytesRestored: p.AllBytesWritten,
}
if allBytesTotal > 0 {
status.PercentDone = float64(allBytesWritten) / float64(allBytesTotal)
if p.AllBytesTotal > 0 {
status.PercentDone = float64(p.AllBytesWritten) / float64(p.AllBytesTotal)
}
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{
MessageType: "summary",
SecondsElapsed: uint64(duration / time.Second),
TotalFiles: filesTotal,
FilesRestored: filesFinished,
TotalBytes: allBytesTotal,
BytesRestored: allBytesWritten,
TotalFiles: p.FilesTotal,
FilesRestored: p.FilesFinished,
TotalBytes: p.AllBytesTotal,
BytesRestored: p.AllBytesWritten,
}
t.print(status)
}

View file

@ -10,20 +10,20 @@ import (
func TestJSONPrintUpdate(t *testing.T) {
term := &mockTerm{}
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)
}
func TestJSONPrintSummaryOnSuccess(t *testing.T) {
term := &mockTerm{}
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)
}
func TestJSONPrintSummaryOnErrors(t *testing.T) {
term := &mockTerm{}
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)
}

View file

@ -7,15 +7,19 @@ import (
"github.com/restic/restic/internal/ui/progress"
)
type State struct {
FilesFinished uint64
FilesTotal uint64
AllBytesWritten uint64
AllBytesTotal uint64
}
type Progress struct {
updater progress.Updater
m sync.Mutex
progressInfoMap map[string]progressInfoEntry
filesFinished uint64
filesTotal uint64
allBytesWritten uint64
allBytesTotal uint64
s State
started time.Time
printer ProgressPrinter
@ -32,8 +36,8 @@ type term interface {
}
type ProgressPrinter interface {
Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration)
Finish(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration)
Update(progress State, duration time.Duration)
Finish(progress State, duration time.Duration)
}
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()
if !final {
p.printer.Update(p.filesFinished, p.filesTotal, p.allBytesWritten, p.allBytesTotal, runtime)
p.printer.Update(p.s, runtime)
} 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()
defer p.m.Unlock()
p.filesTotal++
p.allBytesTotal += size
p.s.FilesTotal++
p.s.AllBytesTotal += size
}
// 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
p.progressInfoMap[name] = entry
p.allBytesWritten += bytesWrittenPortion
p.s.AllBytesWritten += bytesWrittenPortion
if entry.bytesWritten == entry.bytesTotal {
delete(p.progressInfoMap, name)
p.filesFinished++
p.s.FilesFinished++
}
}

View file

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

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)
formattedAllBytesWritten := ui.FormatBytes(allBytesWritten)
formattedAllBytesTotal := ui.FormatBytes(allBytesTotal)
allPercent := ui.FormatPercent(allBytesWritten, allBytesTotal)
formattedAllBytesWritten := ui.FormatBytes(p.AllBytesWritten)
formattedAllBytesTotal := ui.FormatBytes(p.AllBytesTotal)
allPercent := ui.FormatPercent(p.AllBytesWritten, p.AllBytesTotal)
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})
}
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{})
timeLeft := ui.FormatDuration(duration)
formattedAllBytesTotal := ui.FormatBytes(allBytesTotal)
formattedAllBytesTotal := ui.FormatBytes(p.AllBytesTotal)
var summary string
if filesFinished == filesTotal && allBytesWritten == allBytesTotal {
summary = fmt.Sprintf("Summary: Restored %d files/dirs (%s) in %s", filesTotal, formattedAllBytesTotal, timeLeft)
if p.FilesFinished == p.FilesTotal && p.AllBytesWritten == p.AllBytesTotal {
summary = fmt.Sprintf("Summary: Restored %d files/dirs (%s) in %s", p.FilesTotal, formattedAllBytesTotal, timeLeft)
} else {
formattedAllBytesWritten := ui.FormatBytes(allBytesWritten)
formattedAllBytesWritten := ui.FormatBytes(p.AllBytesWritten)
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)

View file

@ -22,20 +22,20 @@ func (m *mockTerm) SetStatus(lines []string) {
func TestPrintUpdate(t *testing.T) {
term := &mockTerm{}
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)
}
func TestPrintSummaryOnSuccess(t *testing.T) {
term := &mockTerm{}
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)
}
func TestPrintSummaryOnErrors(t *testing.T) {
term := &mockTerm{}
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)
}