github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/cmd/progress.go (about) 1 // Show the dynamic progress bar 2 3 package cmd 4 5 import ( 6 "bytes" 7 "fmt" 8 "os" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/ncw/rclone/fs" 14 "github.com/ncw/rclone/fs/accounting" 15 "github.com/ncw/rclone/fs/log" 16 "golang.org/x/crypto/ssh/terminal" 17 ) 18 19 const ( 20 // interval between progress prints 21 defaultProgressInterval = 500 * time.Millisecond 22 // time format for logging 23 logTimeFormat = "2006-01-02 15:04:05" 24 ) 25 26 // startProgress starts the progress bar printing 27 // 28 // It returns a func which should be called to stop the stats. 29 func startProgress() func() { 30 err := initTerminal() 31 if err != nil { 32 fs.Errorf(nil, "Failed to start progress: %v", err) 33 return func() {} 34 } 35 stopStats := make(chan struct{}) 36 oldLogPrint := fs.LogPrint 37 if !log.Redirected() { 38 // Intercept the log calls if not logging to file or syslog 39 fs.LogPrint = func(level fs.LogLevel, text string) { 40 printProgress(fmt.Sprintf("%s %-6s: %s", time.Now().Format(logTimeFormat), level, text)) 41 42 } 43 } 44 var wg sync.WaitGroup 45 wg.Add(1) 46 go func() { 47 defer wg.Done() 48 progressInterval := defaultProgressInterval 49 if ShowStats() && *statsInterval > 0 { 50 progressInterval = *statsInterval 51 } 52 ticker := time.NewTicker(progressInterval) 53 for { 54 select { 55 case <-ticker.C: 56 printProgress("") 57 case <-stopStats: 58 ticker.Stop() 59 printProgress("") 60 fs.LogPrint = oldLogPrint 61 fmt.Println("") 62 return 63 } 64 } 65 }() 66 return func() { 67 close(stopStats) 68 wg.Wait() 69 } 70 } 71 72 // VT100 codes 73 const ( 74 eraseLine = "\x1b[2K" 75 moveToStartOfLine = "\x1b[0G" 76 moveUp = "\x1b[A" 77 ) 78 79 // state for the progress printing 80 var ( 81 nlines = 0 // number of lines in the previous stats block 82 progressMu sync.Mutex 83 ) 84 85 // printProgress prints the progress with an optional log 86 func printProgress(logMessage string) { 87 progressMu.Lock() 88 defer progressMu.Unlock() 89 90 var buf bytes.Buffer 91 w, h, err := terminal.GetSize(int(os.Stdout.Fd())) 92 if err != nil { 93 w, h = 80, 25 94 } 95 _ = h 96 stats := strings.TrimSpace(accounting.Stats.String()) 97 logMessage = strings.TrimSpace(logMessage) 98 99 out := func(s string) { 100 buf.WriteString(s) 101 } 102 103 if logMessage != "" { 104 out("\n") 105 out(moveUp) 106 } 107 // Move to the start of the block we wrote erasing all the previous lines 108 for i := 0; i < nlines-1; i++ { 109 out(eraseLine) 110 out(moveUp) 111 } 112 out(eraseLine) 113 out(moveToStartOfLine) 114 if logMessage != "" { 115 out(eraseLine) 116 out(logMessage + "\n") 117 } 118 fixedLines := strings.Split(stats, "\n") 119 nlines = len(fixedLines) 120 for i, line := range fixedLines { 121 if len(line) > w { 122 line = line[:w] 123 } 124 out(line) 125 if i != nlines-1 { 126 out("\n") 127 } 128 } 129 writeToTerminal(buf.Bytes()) 130 }