cmd: add --progress/-P flag to show progress

Fixes #2347
Fixes #1210
This commit is contained in:
Nick Craig-Wood 2018-06-30 16:05:31 +01:00
parent b596ccdf0f
commit b6db90cc32
6 changed files with 143 additions and 1 deletions

View file

@ -297,7 +297,9 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
if !showStats && ShowStats() { if !showStats && ShowStats() {
showStats = true showStats = true
} }
if showStats { if fs.Config.Progress {
stopStats = startProgress()
} else if showStats {
stopStats = StartStats() stopStats = StartStats()
} }
SigInfoHandler() SigInfoHandler()

118
cmd/progress.go Normal file
View file

@ -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())
}

View file

@ -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 This can be used if the remote is being synced with another tool also
(eg the Google Drive client). (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 ### ### -q, --quiet ###
Normally rclone outputs stats and a completion message. If you set Normally rclone outputs stats and a completion message. If you set

View file

@ -83,6 +83,7 @@ type ConfigInfo struct {
MaxTransfer SizeSuffix MaxTransfer SizeSuffix
MaxBacklog int MaxBacklog int
StatsOneLine bool StatsOneLine bool
Progress bool
} }
// NewConfig creates a new config with everything set to the default // NewConfig creates a new config with everything set to the default

View file

@ -85,6 +85,7 @@ func AddFlags(flagSet *pflag.FlagSet) {
flags.FVarP(flagSet, &fs.Config.MaxTransfer, "max-transfer", "", "Maximum size of data to transfer.") 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.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.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 // SetFlags converts any flags into config which weren't straight foward

View file

@ -88,3 +88,8 @@ func InitLogging() {
startSysLog() startSysLog()
} }
} }
// Redirected returns true if the log has been redirected from stdout
func Redirected() bool {
return *useSyslog || *logFile != ""
}