rclone/cmd/progress.go
Nick Craig-Wood 401cf81034 build: modernize Go usage
This commit modernizes Go usage. This was done with:

go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...

Then files needed to be `go fmt`ed and a few comments needed to be
restored.

The modernizations include replacing

- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-28 11:31:14 +00:00

123 lines
2.6 KiB
Go

// Show the dynamic progress bar
package cmd
import (
"bytes"
"fmt"
"strings"
"sync"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/log"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/lib/terminal"
)
const (
// interval between progress prints
defaultProgressInterval = 500 * time.Millisecond
// time format for logging
logTimeFormat = "2006/01/02 15:04:05"
)
// startProgress starts the progress bar printing
//
// It returns a func which should be called to stop the stats.
func startProgress() func() {
stopStats := make(chan struct{})
oldLogOutput := fs.LogOutput
oldSyncPrint := operations.SyncPrintf
if !log.Redirected() {
// Intercept the log calls if not logging to file or syslog
fs.LogOutput = func(level fs.LogLevel, text string) {
printProgress(fmt.Sprintf("%s %-6s: %s", time.Now().Format(logTimeFormat), level, text))
}
}
// Intercept output from functions such as HashLister to stdout
operations.SyncPrintf = func(format string, a ...any) {
printProgress(fmt.Sprintf(format, a...))
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
progressInterval := defaultProgressInterval
if ShowStats() && *statsInterval > 0 {
progressInterval = *statsInterval
}
ticker := time.NewTicker(progressInterval)
for {
select {
case <-ticker.C:
printProgress("")
case <-stopStats:
ticker.Stop()
printProgress("")
fs.LogOutput = oldLogOutput
operations.SyncPrintf = oldSyncPrint
fmt.Println("")
return
}
}
}()
return func() {
close(stopStats)
wg.Wait()
}
}
// state for the progress printing
var (
nlines = 0 // number of lines in the previous stats block
)
// printProgress prints the progress with an optional log
func printProgress(logMessage string) {
operations.StdoutMutex.Lock()
defer operations.StdoutMutex.Unlock()
var buf bytes.Buffer
w, _ := terminal.GetSize()
stats := strings.TrimSpace(accounting.GlobalStats().String())
logMessage = strings.TrimSpace(logMessage)
out := func(s string) {
buf.WriteString(s)
}
if logMessage != "" {
out("\n")
out(terminal.MoveUp)
}
// Move to the start of the block we wrote erasing all the previous lines
for range nlines - 1 {
out(terminal.EraseLine)
out(terminal.MoveUp)
}
out(terminal.EraseLine)
out(terminal.MoveToStartOfLine)
if logMessage != "" {
out(terminal.EraseLine)
out(logMessage + "\n")
}
fixedLines := strings.Split(stats, "\n")
nlines = len(fixedLines)
for i, line := range fixedLines {
if len(line) > w {
line = line[:w]
}
out(line)
if i != nlines-1 {
out("\n")
}
}
terminal.Write(buf.Bytes())
}