restic/cmd/restic/progress.go

130 lines
3.5 KiB
Go

package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
)
// calculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS
// or if unset returns an interval for 60fps on interactive terminals and 0 (=disabled)
// for non-interactive terminals or when run using the --quiet flag
func calculateProgressInterval(show bool, json bool) time.Duration {
interval := time.Second / 60
fps, err := strconv.ParseFloat(os.Getenv("RESTIC_PROGRESS_FPS"), 64)
if err == nil && fps > 0 {
if fps > 60 {
fps = 60
}
interval = time.Duration(float64(time.Second) / fps)
} else if !json && !stdoutCanUpdateStatus() || !show {
interval = 0
}
return interval
}
// newTerminalProgressMax returns a progress.Counter that prints to stdout or terminal if provided.
func newGenericProgressMax(show bool, max uint64, description string, print func(status string, final bool)) *progress.Counter {
if !show {
return nil
}
interval := calculateProgressInterval(show, false)
return progress.NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
var status string
if max == 0 {
status = fmt.Sprintf("[%s] %d %s",
ui.FormatDuration(d), v, description)
} else {
status = fmt.Sprintf("[%s] %s %d / %d %s",
ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description)
}
print(status, final)
})
}
func newTerminalProgressMax(show bool, max uint64, description string, term *termstatus.Terminal) *progress.Counter {
return newGenericProgressMax(show, max, description, func(status string, final bool) {
if final {
term.SetStatus([]string{})
term.Print(status)
} else {
term.SetStatus([]string{status})
}
})
}
// newProgressMax calls newTerminalProgress without a terminal (print to stdout)
func newProgressMax(show bool, max uint64, description string) *progress.Counter {
return newGenericProgressMax(show, max, description, printProgress)
}
func printProgress(status string, final bool) {
canUpdateStatus := stdoutCanUpdateStatus()
w := stdoutTerminalWidth()
if w > 0 {
if w < 3 {
status = termstatus.Truncate(status, w)
} else {
trunc := termstatus.Truncate(status, w-3)
if len(trunc) < len(status) {
status = trunc + "..."
}
}
}
var carriageControl, clear string
if canUpdateStatus {
clear = clearLine(w)
}
if !(strings.HasSuffix(status, "\r") || strings.HasSuffix(status, "\n")) {
if canUpdateStatus {
carriageControl = "\r"
} else {
carriageControl = "\n"
}
}
_, _ = os.Stdout.Write([]byte(clear + status + carriageControl))
if final {
_, _ = os.Stdout.Write([]byte("\n"))
}
}
func newIndexProgress(quiet bool, json bool) *progress.Counter {
return newProgressMax(!quiet && !json && stdoutIsTerminal(), 0, "index files loaded")
}
func newIndexTerminalProgress(quiet bool, json bool, term *termstatus.Terminal) *progress.Counter {
return newTerminalProgressMax(!quiet && !json && stdoutIsTerminal(), 0, "index files loaded", term)
}
type terminalProgressPrinter struct {
term *termstatus.Terminal
ui.Message
show bool
}
func (t *terminalProgressPrinter) NewCounter(description string) *progress.Counter {
return newTerminalProgressMax(t.show, 0, description, t.term)
}
func newTerminalProgressPrinter(verbosity uint, term *termstatus.Terminal) progress.Printer {
return &terminalProgressPrinter{
term: term,
Message: *ui.NewMessage(term, verbosity),
show: verbosity > 0,
}
}