progress.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Show the dynamic progress bar
  2. package cmd
  3. import (
  4. "bytes"
  5. "fmt"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/rclone/rclone/fs"
  10. "github.com/rclone/rclone/fs/accounting"
  11. "github.com/rclone/rclone/fs/log"
  12. "github.com/rclone/rclone/fs/operations"
  13. "github.com/rclone/rclone/lib/terminal"
  14. )
  15. const (
  16. // interval between progress prints
  17. defaultProgressInterval = 500 * time.Millisecond
  18. // time format for logging
  19. logTimeFormat = "2006/01/02 15:04:05"
  20. )
  21. // startProgress starts the progress bar printing
  22. //
  23. // It returns a func which should be called to stop the stats.
  24. func startProgress() func() {
  25. stopStats := make(chan struct{})
  26. oldLogPrint := fs.LogPrint
  27. oldSyncPrint := operations.SyncPrintf
  28. if !log.Redirected() {
  29. // Intercept the log calls if not logging to file or syslog
  30. fs.LogPrint = func(level fs.LogLevel, text string) {
  31. printProgress(fmt.Sprintf("%s %-6s: %s", time.Now().Format(logTimeFormat), level, text))
  32. }
  33. }
  34. // Intercept output from functions such as HashLister to stdout
  35. operations.SyncPrintf = func(format string, a ...interface{}) {
  36. printProgress(fmt.Sprintf(format, a...))
  37. }
  38. var wg sync.WaitGroup
  39. wg.Add(1)
  40. go func() {
  41. defer wg.Done()
  42. progressInterval := defaultProgressInterval
  43. if ShowStats() && *statsInterval > 0 {
  44. progressInterval = *statsInterval
  45. }
  46. ticker := time.NewTicker(progressInterval)
  47. for {
  48. select {
  49. case <-ticker.C:
  50. printProgress("")
  51. case <-stopStats:
  52. ticker.Stop()
  53. printProgress("")
  54. fs.LogPrint = oldLogPrint
  55. operations.SyncPrintf = oldSyncPrint
  56. fmt.Println("")
  57. return
  58. }
  59. }
  60. }()
  61. return func() {
  62. close(stopStats)
  63. wg.Wait()
  64. }
  65. }
  66. // state for the progress printing
  67. var (
  68. nlines = 0 // number of lines in the previous stats block
  69. )
  70. // printProgress prints the progress with an optional log
  71. func printProgress(logMessage string) {
  72. operations.StdoutMutex.Lock()
  73. defer operations.StdoutMutex.Unlock()
  74. var buf bytes.Buffer
  75. w, _ := terminal.GetSize()
  76. stats := strings.TrimSpace(accounting.GlobalStats().String())
  77. logMessage = strings.TrimSpace(logMessage)
  78. out := func(s string) {
  79. buf.WriteString(s)
  80. }
  81. if logMessage != "" {
  82. out("\n")
  83. out(terminal.MoveUp)
  84. }
  85. // Move to the start of the block we wrote erasing all the previous lines
  86. for i := 0; i < nlines-1; i++ {
  87. out(terminal.EraseLine)
  88. out(terminal.MoveUp)
  89. }
  90. out(terminal.EraseLine)
  91. out(terminal.MoveToStartOfLine)
  92. if logMessage != "" {
  93. out(terminal.EraseLine)
  94. out(logMessage + "\n")
  95. }
  96. fixedLines := strings.Split(stats, "\n")
  97. nlines = len(fixedLines)
  98. for i, line := range fixedLines {
  99. if len(line) > w {
  100. line = line[:w]
  101. }
  102. out(line)
  103. if i != nlines-1 {
  104. out("\n")
  105. }
  106. }
  107. terminal.Write(buf.Bytes())
  108. }