restic/internal/ui/backup/text.go
Michael Eischer aedead2823 ui/termstatus: fix clearing status lines
To clear the status lines, they should be set to an empty array to
prevent future updates of those lines. Setting the status lines to an
array containing an empty string is wrong as this causes the output to
continuously add that empty status line after each message.
2024-07-06 11:27:35 +02:00

156 lines
4.7 KiB
Go

package backup
import (
"fmt"
"sort"
"time"
"github.com/restic/restic/internal/archiver"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/termstatus"
)
// TextProgress reports progress for the `backup` command.
type TextProgress struct {
*ui.Message
term *termstatus.Terminal
}
// assert that Backup implements the ProgressPrinter interface
var _ ProgressPrinter = &TextProgress{}
// NewTextProgress returns a new backup progress reporter.
func NewTextProgress(term *termstatus.Terminal, verbosity uint) *TextProgress {
return &TextProgress{
Message: ui.NewMessage(term, verbosity),
term: term,
}
}
// Update updates the status lines.
func (b *TextProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
var status string
if total.Files == 0 && total.Dirs == 0 {
// no total count available yet
status = fmt.Sprintf("[%s] %v files, %s, %d errors",
ui.FormatDuration(time.Since(start)),
processed.Files, ui.FormatBytes(processed.Bytes), errors,
)
} else {
var eta, percent string
if secs > 0 && processed.Bytes < total.Bytes {
eta = fmt.Sprintf(" ETA %s", ui.FormatSeconds(secs))
percent = ui.FormatPercent(processed.Bytes, total.Bytes)
percent += " "
}
// include totals
status = fmt.Sprintf("[%s] %s%v files %s, total %v files %v, %d errors%s",
ui.FormatDuration(time.Since(start)),
percent,
processed.Files,
ui.FormatBytes(processed.Bytes),
total.Files,
ui.FormatBytes(total.Bytes),
errors,
eta,
)
}
lines := make([]string, 0, len(currentFiles)+1)
for filename := range currentFiles {
lines = append(lines, filename)
}
sort.Strings(lines)
lines = append([]string{status}, lines...)
b.term.SetStatus(lines)
}
// ScannerError is the error callback function for the scanner, it prints the
// error in verbose mode and returns nil.
func (b *TextProgress) ScannerError(_ string, err error) error {
b.V("scan: %v\n", err)
return nil
}
// Error is the error callback function for the archiver, it prints the error and returns nil.
func (b *TextProgress) Error(_ string, err error) error {
b.E("error: %v\n", err)
return nil
}
// CompleteItem is the status callback function for the archiver when a
// file/dir has been saved successfully.
func (b *TextProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
item = termstatus.Quote(item)
switch messageType {
case "dir new":
b.VV("new %v, saved in %.3fs (%v added, %v stored, %v metadata)",
item, d.Seconds(), ui.FormatBytes(s.DataSize),
ui.FormatBytes(s.DataSizeInRepo), ui.FormatBytes(s.TreeSizeInRepo))
case "dir unchanged":
b.VV("unchanged %v", item)
case "dir modified":
b.VV("modified %v, saved in %.3fs (%v added, %v stored, %v metadata)",
item, d.Seconds(), ui.FormatBytes(s.DataSize),
ui.FormatBytes(s.DataSizeInRepo), ui.FormatBytes(s.TreeSizeInRepo))
case "file new":
b.VV("new %v, saved in %.3fs (%v added)", item,
d.Seconds(), ui.FormatBytes(s.DataSize))
case "file unchanged":
b.VV("unchanged %v", item)
case "file modified":
b.VV("modified %v, saved in %.3fs (%v added, %v stored)", item,
d.Seconds(), ui.FormatBytes(s.DataSize), ui.FormatBytes(s.DataSizeInRepo))
}
}
// ReportTotal sets the total stats up to now
func (b *TextProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
b.V("scan finished in %.3fs: %v files, %s",
time.Since(start).Seconds(),
s.Files, ui.FormatBytes(s.Bytes),
)
}
// Reset status
func (b *TextProgress) Reset() {
if b.term.CanUpdateStatus() {
b.term.SetStatus(nil)
}
}
// Finish prints the finishing messages.
func (b *TextProgress) Finish(id restic.ID, start time.Time, summary *archiver.Summary, dryRun bool) {
b.P("\n")
b.P("Files: %5d new, %5d changed, %5d unmodified\n", summary.Files.New, summary.Files.Changed, summary.Files.Unchanged)
b.P("Dirs: %5d new, %5d changed, %5d unmodified\n", summary.Dirs.New, summary.Dirs.Changed, summary.Dirs.Unchanged)
b.V("Data Blobs: %5d new\n", summary.ItemStats.DataBlobs)
b.V("Tree Blobs: %5d new\n", summary.ItemStats.TreeBlobs)
verb := "Added"
if dryRun {
verb = "Would add"
}
b.P("%s to the repository: %-5s (%-5s stored)\n", verb,
ui.FormatBytes(summary.ItemStats.DataSize+summary.ItemStats.TreeSize),
ui.FormatBytes(summary.ItemStats.DataSizeInRepo+summary.ItemStats.TreeSizeInRepo))
b.P("\n")
b.P("processed %v files, %v in %s",
summary.Files.New+summary.Files.Changed+summary.Files.Unchanged,
ui.FormatBytes(summary.ProcessedBytes),
ui.FormatDuration(time.Since(start)),
)
if !dryRun {
if id.IsNull() {
b.P("skipped creating snapshot\n")
} else {
b.P("snapshot %s saved\n", id.Str())
}
}
}