From b6db90cc322c1187da7afb108a6171163ad1d85d Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sat, 30 Jun 2018 16:05:31 +0100 Subject: [PATCH] cmd: add --progress/-P flag to show progress Fixes #2347 Fixes #1210 --- cmd/cmd.go | 4 +- cmd/progress.go | 118 +++++++++++++++++++++++++++ docs/content/docs.md | 15 ++++ fs/config.go | 1 + fs/config/configflags/configflags.go | 1 + fs/log/log.go | 5 ++ 6 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 cmd/progress.go diff --git a/cmd/cmd.go b/cmd/cmd.go index eadb3b08e..afa90b713 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -297,7 +297,9 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) { if !showStats && ShowStats() { showStats = true } - if showStats { + if fs.Config.Progress { + stopStats = startProgress() + } else if showStats { stopStats = StartStats() } SigInfoHandler() diff --git a/cmd/progress.go b/cmd/progress.go new file mode 100644 index 000000000..5aa43fc7d --- /dev/null +++ b/cmd/progress.go @@ -0,0 +1,118 @@ +// Show the dynamic progress bar + +package cmd + +import ( + "bytes" + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/ncw/rclone/fs" + "github.com/ncw/rclone/fs/accounting" + "github.com/ncw/rclone/fs/log" + "golang.org/x/crypto/ssh/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 channel which should be closed to stop the stats. +func startProgress() chan struct{} { + stopStats := make(chan struct{}) + oldLogPrint := fs.LogPrint + if !log.Redirected() { + // Intercept the log calls if not logging to file or syslog + fs.LogPrint = func(level fs.LogLevel, text string) { + printProgress(fmt.Sprintf("%s %-6s: %s", time.Now().Format(logTimeFormat), level, text)) + + } + } + go func() { + progressInterval := defaultProgressInterval + if ShowStats() { + progressInterval = *statsInterval + } + ticker := time.NewTicker(progressInterval) + for { + select { + case <-ticker.C: + printProgress("") + case <-stopStats: + ticker.Stop() + fs.LogPrint = oldLogPrint + fmt.Println("") + return + } + } + }() + return stopStats +} + +// VT100 codes +const ( + eraseLine = "\x1b[2K" + moveToStartOfLine = "\x1b[0G" + moveUp = "\x1b[A" +) + +// state for the progress printing +var ( + nlines = 0 // number of lines in the previous stats block + progressMu sync.Mutex +) + +// printProgress prings the progress with an optional log +func printProgress(logMessage string) { + progressMu.Lock() + defer progressMu.Unlock() + + var buf bytes.Buffer + w, h, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + w, h = 80, 25 + } + _ = h + stats := strings.TrimSpace(accounting.Stats.String()) + logMessage = strings.TrimSpace(logMessage) + + out := func(s string) { + buf.WriteString(s) + } + + if logMessage != "" { + out("\n") + out(moveUp) + } + // Move to the start of the block we wrote erasing all the previous lines + for i := 0; i < nlines-1; i++ { + out(eraseLine) + out(moveUp) + } + out(eraseLine) + out(moveToStartOfLine) + if logMessage != "" { + out(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") + } + } + os.Stdout.Write(buf.Bytes()) +} diff --git a/docs/content/docs.md b/docs/content/docs.md index 437000065..390d6ae45 100644 --- a/docs/content/docs.md +++ b/docs/content/docs.md @@ -619,6 +619,21 @@ files if they are incorrect as it would normally. This can be used if the remote is being synced with another tool also (eg the Google Drive client). +### --P, --progress ### + +This flag makes rclone update the stats in a static block in the +terminal providing a realtime overview of the transfer. + +Any log messages will scroll above the static block. Log messages +will push the static block down to the bottom of the terminal where it +will stay. + +Normally this is updated every 500mS but this period can be overridden +with the `--stats` flag. + +This can be used with the `--stats-one-line` flag for a simpler +display. + ### -q, --quiet ### Normally rclone outputs stats and a completion message. If you set diff --git a/fs/config.go b/fs/config.go index 6ffd8f1a5..cf608b25d 100644 --- a/fs/config.go +++ b/fs/config.go @@ -83,6 +83,7 @@ type ConfigInfo struct { MaxTransfer SizeSuffix MaxBacklog int StatsOneLine bool + Progress bool } // NewConfig creates a new config with everything set to the default diff --git a/fs/config/configflags/configflags.go b/fs/config/configflags/configflags.go index 34d414d7d..45a01d547 100644 --- a/fs/config/configflags/configflags.go +++ b/fs/config/configflags/configflags.go @@ -85,6 +85,7 @@ func AddFlags(flagSet *pflag.FlagSet) { flags.FVarP(flagSet, &fs.Config.MaxTransfer, "max-transfer", "", "Maximum size of data to transfer.") flags.IntVarP(flagSet, &fs.Config.MaxBacklog, "max-backlog", "", fs.Config.MaxBacklog, "Maximum number of objects in sync or check backlog.") flags.BoolVarP(flagSet, &fs.Config.StatsOneLine, "stats-one-line", "", fs.Config.StatsOneLine, "Make the stats fit on one line.") + flags.BoolVarP(flagSet, &fs.Config.Progress, "progress", "P", fs.Config.Progress, "Show progress during transfer.") } // SetFlags converts any flags into config which weren't straight foward diff --git a/fs/log/log.go b/fs/log/log.go index 4bafc0146..43814defe 100644 --- a/fs/log/log.go +++ b/fs/log/log.go @@ -88,3 +88,8 @@ func InitLogging() { startSysLog() } } + +// Redirected returns true if the log has been redirected from stdout +func Redirected() bool { + return *useSyslog || *logFile != "" +}