mirror of
https://github.com/restic/restic.git
synced 2025-10-09 13:49:58 +00:00
cmd, ui: Deduplicate formatting utilities
This commit is contained in:
@@ -37,26 +37,26 @@ func (b *TextProgress) Update(total, processed Counter, errors uint, currentFile
|
||||
if total.Files == 0 && total.Dirs == 0 {
|
||||
// no total count available yet
|
||||
status = fmt.Sprintf("[%s] %v files, %s, %d errors",
|
||||
formatDuration(time.Since(start)),
|
||||
processed.Files, formatBytes(processed.Bytes), 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", formatSeconds(secs))
|
||||
percent = formatPercent(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",
|
||||
formatDuration(time.Since(start)),
|
||||
ui.FormatDuration(time.Since(start)),
|
||||
percent,
|
||||
processed.Files,
|
||||
formatBytes(processed.Bytes),
|
||||
ui.FormatBytes(processed.Bytes),
|
||||
total.Files,
|
||||
formatBytes(total.Bytes),
|
||||
ui.FormatBytes(total.Bytes),
|
||||
errors,
|
||||
eta,
|
||||
)
|
||||
@@ -85,69 +85,28 @@ func (b *TextProgress) Error(item string, err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatPercent(numerator uint64, denominator uint64) string {
|
||||
if denominator == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
percent := 100.0 * float64(numerator) / float64(denominator)
|
||||
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%3.2f%%", percent)
|
||||
}
|
||||
|
||||
func formatSeconds(sec uint64) string {
|
||||
hours := sec / 3600
|
||||
sec -= hours * 3600
|
||||
min := sec / 60
|
||||
sec -= min * 60
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d:%02d", min, sec)
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
sec := uint64(d / time.Second)
|
||||
return formatSeconds(sec)
|
||||
}
|
||||
|
||||
func formatBytes(c uint64) string {
|
||||
b := float64(c)
|
||||
switch {
|
||||
case c > 1<<40:
|
||||
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
||||
case c > 1<<30:
|
||||
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
||||
case c > 1<<20:
|
||||
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
||||
case c > 1<<10:
|
||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
||||
default:
|
||||
return fmt.Sprintf("%d B", c)
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteItem is the status callback function for the archiver when a
|
||||
// file/dir has been saved successfully.
|
||||
func (b *TextProgress) CompleteItem(messageType, item string, previous, current *restic.Node, s archiver.ItemStats, d time.Duration) {
|
||||
switch messageType {
|
||||
case "dir new":
|
||||
b.VV("new %v, saved in %.3fs (%v added, %v stored, %v metadata)", item, d.Seconds(), formatBytes(s.DataSize), formatBytes(s.DataSizeInRepo), formatBytes(s.TreeSizeInRepo))
|
||||
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(), formatBytes(s.DataSize), formatBytes(s.DataSizeInRepo), formatBytes(s.TreeSizeInRepo))
|
||||
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(), formatBytes(s.DataSize))
|
||||
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(), formatBytes(s.DataSize), formatBytes(s.DataSizeInRepo))
|
||||
b.VV("modified %v, saved in %.3fs (%v added, %v stored)", item,
|
||||
d.Seconds(), ui.FormatBytes(s.DataSize), ui.FormatBytes(s.DataSizeInRepo))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +114,7 @@ func (b *TextProgress) CompleteItem(messageType, item string, previous, current
|
||||
func (b *TextProgress) ReportTotal(item string, start time.Time, s archiver.ScanStats) {
|
||||
b.V("scan finished in %.3fs: %v files, %s",
|
||||
time.Since(start).Seconds(),
|
||||
s.Files, formatBytes(s.Bytes),
|
||||
s.Files, ui.FormatBytes(s.Bytes),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -177,11 +136,13 @@ func (b *TextProgress) Finish(snapshotID restic.ID, start time.Time, summary *Su
|
||||
if dryRun {
|
||||
verb = "Would add"
|
||||
}
|
||||
b.P("%s to the repository: %-5s (%-5s stored)\n", verb, formatBytes(summary.ItemStats.DataSize+summary.ItemStats.TreeSize), formatBytes(summary.ItemStats.DataSizeInRepo+summary.ItemStats.TreeSizeInRepo))
|
||||
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,
|
||||
formatBytes(summary.ProcessedBytes),
|
||||
formatDuration(time.Since(start)),
|
||||
ui.FormatBytes(summary.ProcessedBytes),
|
||||
ui.FormatDuration(time.Since(start)),
|
||||
)
|
||||
}
|
||||
|
55
internal/ui/format.go
Normal file
55
internal/ui/format.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func FormatBytes(c uint64) string {
|
||||
b := float64(c)
|
||||
switch {
|
||||
case c > 1<<40:
|
||||
return fmt.Sprintf("%.3f TiB", b/(1<<40))
|
||||
case c > 1<<30:
|
||||
return fmt.Sprintf("%.3f GiB", b/(1<<30))
|
||||
case c > 1<<20:
|
||||
return fmt.Sprintf("%.3f MiB", b/(1<<20))
|
||||
case c > 1<<10:
|
||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
||||
default:
|
||||
return fmt.Sprintf("%d B", c)
|
||||
}
|
||||
}
|
||||
|
||||
// FormatPercent formats numerator/denominator as a percentage.
|
||||
func FormatPercent(numerator uint64, denominator uint64) string {
|
||||
if denominator == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
percent := 100.0 * float64(numerator) / float64(denominator)
|
||||
if percent > 100 {
|
||||
percent = 100
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%3.2f%%", percent)
|
||||
}
|
||||
|
||||
// FormatDuration formats d as FormatSeconds would.
|
||||
func FormatDuration(d time.Duration) string {
|
||||
sec := uint64(d / time.Second)
|
||||
return FormatSeconds(sec)
|
||||
}
|
||||
|
||||
// FormatSeconds formats sec as MM:SS, or HH:MM:SS if sec seconds
|
||||
// is at least an hour.
|
||||
func FormatSeconds(sec uint64) string {
|
||||
hours := sec / 3600
|
||||
sec -= hours * 3600
|
||||
min := sec / 60
|
||||
sec -= min * 60
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%d:%02d:%02d", hours, min, sec)
|
||||
}
|
||||
return fmt.Sprintf("%d:%02d", min, sec)
|
||||
}
|
33
internal/ui/format_test.go
Normal file
33
internal/ui/format_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package ui
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatBytes(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
size uint64
|
||||
want string
|
||||
}{
|
||||
{0, "0 B"},
|
||||
{1025, "1.001 KiB"},
|
||||
{1<<30 + 7, "1.000 GiB"},
|
||||
} {
|
||||
if got := FormatBytes(c.size); got != c.want {
|
||||
t.Errorf("want %q, got %q", c.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatPercent(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
num, denom uint64
|
||||
want string
|
||||
}{
|
||||
{0, 5, "0.00%"},
|
||||
{3, 7, "42.86%"},
|
||||
{99, 99, "100.00%"},
|
||||
} {
|
||||
if got := FormatPercent(c.num, c.denom); got != c.want {
|
||||
t.Errorf("want %q, got %q", c.want, got)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user