github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/progress-bar.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "io" 22 "runtime" 23 "strings" 24 "time" 25 26 "github.com/cheggaaa/pb" 27 "github.com/fatih/color" 28 "github.com/minio/pkg/v2/console" 29 ) 30 31 // progress extender. 32 type progressBar struct { 33 *pb.ProgressBar 34 } 35 36 func newPB(total int64) *pb.ProgressBar { 37 // Progress bar specific theme customization. 38 console.SetColor("Bar", color.New(color.FgGreen, color.Bold)) 39 40 // get the new original progress bar. 41 bar := pb.New64(total) 42 43 // Set new human friendly print units. 44 bar.SetUnits(pb.U_BYTES) 45 46 // Refresh rate for progress bar is set to 125 milliseconds. 47 bar.SetRefreshRate(time.Millisecond * 125) 48 49 // Do not print a newline by default handled, it is handled manually. 50 bar.NotPrint = true 51 52 // Show current speed is true. 53 bar.ShowSpeed = true 54 55 // Custom callback with colorized bar. 56 bar.Callback = func(s string) { 57 console.Print(console.Colorize("Bar", "\r"+s)) 58 } 59 60 // Use different unicodes for Linux, OS X and Windows. 61 switch runtime.GOOS { 62 case "linux": 63 // Need to add '\x00' as delimiter for unicode characters. 64 bar.Format("┃\x00▓\x00█\x00░\x00┃") 65 case "darwin": 66 // Need to add '\x00' as delimiter for unicode characters. 67 bar.Format(" \x00▓\x00 \x00░\x00 ") 68 default: 69 // Default to non unicode characters. 70 bar.Format("[=> ]") 71 } 72 73 // Start the progress bar. 74 return bar.Start() 75 } 76 77 func newProgressReader(r io.Reader, caption string, total int64) *pb.Reader { 78 bar := newPB(total) 79 80 if caption != "" { 81 bar.Prefix(caption) 82 } 83 84 return bar.NewProxyReader(r) 85 } 86 87 // newProgressBar - instantiate a progress bar. 88 func newProgressBar(total int64) *progressBar { 89 bar := newPB(total) 90 91 // Return new progress bar here. 92 return &progressBar{ProgressBar: bar} 93 } 94 95 // Set caption. 96 func (p *progressBar) SetCaption(caption string) *progressBar { 97 caption = fixateBarCaption(caption, getFixedWidth(p.ProgressBar.GetWidth(), 18)) 98 p.ProgressBar.Prefix(caption) 99 return p 100 } 101 102 func (p *progressBar) Finish() { 103 p.ProgressBar.Finish() 104 } 105 106 func (p *progressBar) Set64(length int64) *progressBar { 107 p.ProgressBar = p.ProgressBar.Set64(length) 108 return p 109 } 110 111 func (p *progressBar) Read(buf []byte) (n int, err error) { 112 defer func() { 113 // Upload retry can read one object twice; Avoid read to be greater than Total 114 if n, t := p.ProgressBar.Get(), p.ProgressBar.Total; t > 0 && n > t { 115 p.ProgressBar.Set64(t) 116 } 117 }() 118 119 return p.ProgressBar.Read(buf) 120 } 121 122 func (p *progressBar) SetTotal(total int64) { 123 p.ProgressBar.Total = total 124 } 125 126 // cursorAnimate - returns a animated rune through read channel for every read. 127 func cursorAnimate() <-chan string { 128 cursorCh := make(chan string) 129 var cursors []string 130 131 switch runtime.GOOS { 132 case "linux": 133 // cursors = "➩➪➫➬➭➮➯➱" 134 // cursors = "▁▃▄▅▆▇█▇▆▅▄▃" 135 cursors = []string{"◐", "◓", "◑", "◒"} 136 // cursors = "←↖↑↗→↘↓↙" 137 // cursors = "◴◷◶◵" 138 // cursors = "◰◳◲◱" 139 // cursors = "⣾⣽⣻⢿⡿⣟⣯⣷" 140 case "darwin": 141 cursors = []string{"◐", "◓", "◑", "◒"} 142 default: 143 cursors = []string{"|", "/", "-", "\\"} 144 } 145 go func() { 146 for { 147 for _, cursor := range cursors { 148 cursorCh <- cursor 149 } 150 } 151 }() 152 return cursorCh 153 } 154 155 // fixateBarCaption - fancify bar caption based on the terminal width. 156 func fixateBarCaption(caption string, width int) string { 157 switch { 158 case len(caption) > width: 159 // Trim caption to fit within the screen 160 trimSize := len(caption) - width + 3 161 if trimSize < len(caption) { 162 caption = "..." + caption[trimSize:] 163 } 164 case len(caption) < width: 165 caption += strings.Repeat(" ", width-len(caption)) 166 } 167 return caption 168 } 169 170 // getFixedWidth - get a fixed width based for a given percentage. 171 func getFixedWidth(width, percent int) int { 172 return width * percent / 100 173 }