github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/cmd/progress.go (about)

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