| 0 | 0 |
package mpb
|
| 1 | 1 |
|
| 2 | 2 |
import (
|
|
3 |
"fmt"
|
| 3 | 4 |
"io"
|
| 4 | 5 |
"math"
|
| 5 | 6 |
"sync"
|
|
| 15 | 16 |
rRight
|
| 16 | 17 |
)
|
| 17 | 18 |
|
| 18 | |
type barFmtRunes [numFmtRunes]rune
|
| 19 | |
type barFmtBytes [numFmtRunes][]byte
|
|
19 |
const (
|
|
20 |
formatLen = 5
|
|
21 |
etaAlpha = 0.25
|
|
22 |
)
|
|
23 |
|
|
24 |
type barFmtRunes [formatLen]rune
|
|
25 |
type barFmtBytes [formatLen][]byte
|
| 20 | 26 |
|
| 21 | 27 |
// Bar represents a progress Bar
|
| 22 | 28 |
type Bar struct {
|
| 23 | |
stateCh chan state
|
| 24 | 29 |
incrCh chan incrReq
|
| 25 | |
flushedCh chan struct{}
|
| 26 | 30 |
completeReqCh chan struct{}
|
| 27 | |
removeReqCh chan struct{}
|
| 28 | 31 |
done chan struct{}
|
| 29 | 32 |
inProgress chan struct{}
|
| 30 | |
cancel <-chan struct{}
|
|
33 |
ops chan func(*state)
|
| 31 | 34 |
|
| 32 | 35 |
// following are used after (*Bar.done) is closed
|
| 33 | 36 |
width int
|
|
| 40 | 43 |
ID int
|
| 41 | 44 |
Completed bool
|
| 42 | 45 |
Aborted bool
|
| 43 | |
Total int64
|
| 44 | |
Current int64
|
|
46 |
Total int
|
|
47 |
Current int
|
| 45 | 48 |
StartTime time.Time
|
| 46 | 49 |
TimeElapsed time.Duration
|
| 47 | 50 |
TimePerItemEstimate time.Duration
|
| 48 | 51 |
}
|
| 49 | 52 |
|
| 50 | 53 |
// Refil is a struct for b.IncrWithReFill
|
| 51 | |
type Refill struct {
|
| 52 | |
Char rune
|
| 53 | |
till int64
|
|
54 |
type refill struct {
|
|
55 |
char rune
|
|
56 |
till int
|
| 54 | 57 |
}
|
| 55 | 58 |
|
| 56 | 59 |
// Eta returns exponential-weighted-moving-average ETA estimator
|
|
| 61 | 64 |
type (
|
| 62 | 65 |
incrReq struct {
|
| 63 | 66 |
amount int64
|
| 64 | |
refill *Refill
|
|
67 |
refill *refill
|
| 65 | 68 |
}
|
| 66 | 69 |
state struct {
|
| 67 | 70 |
id int
|
| 68 | 71 |
width int
|
| 69 | 72 |
format barFmtRunes
|
| 70 | 73 |
etaAlpha float64
|
| 71 | |
total int64
|
| 72 | |
current int64
|
|
74 |
total int
|
|
75 |
current int
|
| 73 | 76 |
trimLeftSpace bool
|
| 74 | 77 |
trimRightSpace bool
|
| 75 | 78 |
completed bool
|
| 76 | 79 |
aborted bool
|
| 77 | 80 |
startTime time.Time
|
| 78 | 81 |
timeElapsed time.Duration
|
|
82 |
blockStartTime time.Time
|
| 79 | 83 |
timePerItem time.Duration
|
| 80 | 84 |
appendFuncs []DecoratorFunc
|
| 81 | 85 |
prependFuncs []DecoratorFunc
|
| 82 | 86 |
simpleSpinner func() byte
|
| 83 | |
refill *Refill
|
|
87 |
refill *refill
|
|
88 |
flushed chan struct{}
|
| 84 | 89 |
}
|
| 85 | 90 |
)
|
| 86 | 91 |
|
| 87 | |
func newBar(total int64, wg *sync.WaitGroup, cancel <-chan struct{}, options ...BarOption) *Bar {
|
|
92 |
func newBar(total int, wg *sync.WaitGroup, cancel <-chan struct{}, options ...BarOption) *Bar {
|
|
93 |
s := state{
|
|
94 |
total: total,
|
|
95 |
etaAlpha: etaAlpha,
|
|
96 |
}
|
|
97 |
|
|
98 |
if total <= 0 {
|
|
99 |
s.simpleSpinner = getSpinner()
|
|
100 |
}
|
|
101 |
|
|
102 |
for _, opt := range options {
|
|
103 |
opt(&s)
|
|
104 |
}
|
|
105 |
|
| 88 | 106 |
b := &Bar{
|
| 89 | |
// width: width,
|
| 90 | |
stateCh: make(chan state),
|
| 91 | 107 |
incrCh: make(chan incrReq),
|
| 92 | |
flushedCh: make(chan struct{}),
|
| 93 | |
removeReqCh: make(chan struct{}),
|
| 94 | 108 |
completeReqCh: make(chan struct{}),
|
| 95 | 109 |
done: make(chan struct{}),
|
| 96 | 110 |
inProgress: make(chan struct{}),
|
| 97 | |
}
|
| 98 | |
|
| 99 | |
s := state{
|
| 100 | |
total: total,
|
| 101 | |
etaAlpha: 0.25,
|
| 102 | |
}
|
| 103 | |
|
| 104 | |
if total <= 0 {
|
| 105 | |
s.simpleSpinner = getSpinner()
|
| 106 | |
}
|
| 107 | |
|
| 108 | |
for _, opt := range options {
|
| 109 | |
opt(&s)
|
| 110 | |
}
|
| 111 | |
|
|
111 |
ops: make(chan func(*state)),
|
|
112 |
}
|
| 112 | 113 |
b.width = s.width
|
| 113 | 114 |
|
| 114 | 115 |
go b.server(s, wg, cancel)
|
| 115 | 116 |
return b
|
| 116 | 117 |
}
|
| 117 | 118 |
|
| 118 | |
// SetWidth overrides width of individual bar
|
| 119 | |
func (b *Bar) SetWidth(n int) *Bar {
|
| 120 | |
if n < 2 {
|
| 121 | |
return b
|
| 122 | |
}
|
| 123 | |
b.updateState(func(s *state) {
|
| 124 | |
s.width = n
|
| 125 | |
})
|
| 126 | |
return b
|
| 127 | |
}
|
| 128 | |
|
| 129 | |
// TrimLeftSpace removes space befor LeftEnd charater
|
| 130 | |
func (b *Bar) TrimLeftSpace() *Bar {
|
| 131 | |
b.updateState(func(s *state) {
|
| 132 | |
s.trimLeftSpace = true
|
| 133 | |
})
|
| 134 | |
return b
|
| 135 | |
}
|
| 136 | |
|
| 137 | |
// TrimRightSpace removes space after RightEnd charater
|
| 138 | |
func (b *Bar) TrimRightSpace() *Bar {
|
| 139 | |
b.updateState(func(s *state) {
|
| 140 | |
s.trimRightSpace = true
|
| 141 | |
})
|
| 142 | |
return b
|
| 143 | |
}
|
| 144 | |
|
| 145 | |
// Format overrides format of individual bar
|
| 146 | |
func (b *Bar) Format(format string) *Bar {
|
| 147 | |
if utf8.RuneCountInString(format) != numFmtRunes {
|
| 148 | |
return b
|
| 149 | |
}
|
| 150 | |
b.updateState(func(s *state) {
|
| 151 | |
s.updateFormat(format)
|
| 152 | |
})
|
| 153 | |
return b
|
| 154 | |
}
|
| 155 | |
|
| 156 | |
// SetEtaAlpha sets alfa for exponential-weighted-moving-average ETA estimator
|
| 157 | |
// Defaults to 0.25
|
| 158 | |
// Normally you shouldn't touch this
|
| 159 | |
func (b *Bar) SetEtaAlpha(a float64) *Bar {
|
| 160 | |
b.updateState(func(s *state) {
|
| 161 | |
s.etaAlpha = a
|
| 162 | |
})
|
| 163 | |
return b
|
| 164 | |
}
|
| 165 | |
|
| 166 | |
// PrependFunc prepends DecoratorFunc
|
| 167 | |
func (b *Bar) PrependFunc(f DecoratorFunc) *Bar {
|
| 168 | |
b.updateState(func(s *state) {
|
| 169 | |
s.prependFuncs = append(s.prependFuncs, f)
|
| 170 | |
})
|
| 171 | |
return b
|
| 172 | |
}
|
| 173 | |
|
| 174 | |
// AppendFunc appends DecoratorFunc
|
| 175 | |
func (b *Bar) AppendFunc(f DecoratorFunc) *Bar {
|
| 176 | |
b.updateState(func(s *state) {
|
| 177 | |
s.appendFuncs = append(s.appendFuncs, f)
|
| 178 | |
})
|
| 179 | |
return b
|
| 180 | |
}
|
| 181 | |
|
| 182 | 119 |
// RemoveAllPrependers removes all prepend functions
|
| 183 | 120 |
func (b *Bar) RemoveAllPrependers() {
|
| 184 | |
b.updateState(func(s *state) {
|
|
121 |
select {
|
|
122 |
case b.ops <- func(s *state) {
|
| 185 | 123 |
s.prependFuncs = nil
|
| 186 | |
})
|
|
124 |
}:
|
|
125 |
case <-b.done:
|
|
126 |
return
|
|
127 |
}
|
| 187 | 128 |
}
|
| 188 | 129 |
|
| 189 | 130 |
// RemoveAllAppenders removes all append functions
|
| 190 | 131 |
func (b *Bar) RemoveAllAppenders() {
|
| 191 | |
b.updateState(func(s *state) {
|
|
132 |
select {
|
|
133 |
case b.ops <- func(s *state) {
|
| 192 | 134 |
s.appendFuncs = nil
|
| 193 | |
})
|
|
135 |
}:
|
|
136 |
case <-b.done:
|
|
137 |
return
|
|
138 |
}
|
| 194 | 139 |
}
|
| 195 | 140 |
|
| 196 | 141 |
// ProxyReader wrapper for io operations, like io.Copy
|
|
| 200 | 145 |
|
| 201 | 146 |
// Incr increments progress bar
|
| 202 | 147 |
func (b *Bar) Incr(n int) {
|
| 203 | |
b.IncrWithReFill(n, nil)
|
| 204 | |
}
|
| 205 | |
|
| 206 | |
// IncrWithReFill increments pb with different fill character
|
| 207 | |
func (b *Bar) IncrWithReFill(n int, refill *Refill) {
|
| 208 | 148 |
if n < 1 {
|
| 209 | 149 |
return
|
| 210 | 150 |
}
|
| 211 | 151 |
select {
|
| 212 | |
case b.incrCh <- incrReq{int64(n), refill}:
|
|
152 |
case b.ops <- func(s *state) {
|
|
153 |
defer func() {
|
|
154 |
if s.completed {
|
|
155 |
b.Complete()
|
|
156 |
}
|
|
157 |
s.blockStartTime = time.Now()
|
|
158 |
}()
|
|
159 |
if s.current == 0 {
|
|
160 |
s.startTime = time.Now()
|
|
161 |
s.blockStartTime = s.startTime
|
|
162 |
}
|
|
163 |
sum := s.current + n
|
|
164 |
s.timeElapsed = time.Since(s.startTime)
|
|
165 |
s.updateTimePerItemEstimate(n)
|
|
166 |
if s.total > 0 && sum >= s.total {
|
|
167 |
s.current = s.total
|
|
168 |
s.completed = true
|
|
169 |
return
|
|
170 |
}
|
|
171 |
s.current = sum
|
|
172 |
}:
|
|
173 |
case <-b.done:
|
|
174 |
return
|
|
175 |
}
|
|
176 |
}
|
|
177 |
|
|
178 |
// ResumeFill fills bar with different r rune,
|
|
179 |
// from 0 to till amount of progress.
|
|
180 |
func (b *Bar) ResumeFill(r rune, till int) {
|
|
181 |
if till < 1 {
|
|
182 |
return
|
|
183 |
}
|
|
184 |
select {
|
|
185 |
case b.ops <- func(s *state) {
|
|
186 |
s.refill = &refill{r, till}
|
|
187 |
}:
|
| 213 | 188 |
case <-b.done:
|
| 214 | 189 |
return
|
| 215 | 190 |
}
|
| 216 | 191 |
}
|
| 217 | 192 |
|
| 218 | 193 |
func (b *Bar) NumOfAppenders() int {
|
| 219 | |
return len(b.getState().appendFuncs)
|
|
194 |
result := make(chan int, 1)
|
|
195 |
select {
|
|
196 |
case b.ops <- func(s *state) { result <- len(s.appendFuncs) }:
|
|
197 |
return <-result
|
|
198 |
case <-b.done:
|
|
199 |
return len(b.state.appendFuncs)
|
|
200 |
}
|
| 220 | 201 |
}
|
| 221 | 202 |
|
| 222 | 203 |
func (b *Bar) NumOfPrependers() int {
|
| 223 | |
return len(b.getState().prependFuncs)
|
| 224 | |
}
|
| 225 | |
|
| 226 | |
// GetStatistics returs *Statistics, which contains information like
|
|
204 |
result := make(chan int, 1)
|
|
205 |
select {
|
|
206 |
case b.ops <- func(s *state) { result <- len(s.prependFuncs) }:
|
|
207 |
return <-result
|
|
208 |
case <-b.done:
|
|
209 |
return len(b.state.prependFuncs)
|
|
210 |
}
|
|
211 |
}
|
|
212 |
|
|
213 |
// Statistics returs *Statistics, which contains information like
|
| 227 | 214 |
// Tottal, Current, TimeElapsed and TimePerItemEstimate
|
| 228 | |
func (b *Bar) GetStatistics() *Statistics {
|
| 229 | |
s := b.getState()
|
| 230 | |
return newStatistics(&s)
|
|
215 |
func (b *Bar) Statistics() *Statistics {
|
|
216 |
result := make(chan *Statistics, 1)
|
|
217 |
select {
|
|
218 |
case b.ops <- func(s *state) { result <- newStatistics(s) }:
|
|
219 |
return <-result
|
|
220 |
case <-b.done:
|
|
221 |
return newStatistics(&b.state)
|
|
222 |
}
|
| 231 | 223 |
}
|
| 232 | 224 |
|
| 233 | 225 |
// GetID returs id of the bar
|
| 234 | 226 |
func (b *Bar) GetID() int {
|
| 235 | |
return b.getState().id
|
|
227 |
result := make(chan int, 1)
|
|
228 |
select {
|
|
229 |
case b.ops <- func(s *state) { result <- s.id }:
|
|
230 |
return <-result
|
|
231 |
case <-b.done:
|
|
232 |
return b.state.id
|
|
233 |
}
|
| 236 | 234 |
}
|
| 237 | 235 |
|
| 238 | 236 |
// InProgress returns true, while progress is running.
|
| 239 | 237 |
// Can be used as condition in for loop
|
| 240 | 238 |
func (b *Bar) InProgress() bool {
|
| 241 | 239 |
select {
|
| 242 | |
case <-b.inProgress:
|
|
240 |
case <-b.completeReqCh:
|
| 243 | 241 |
return false
|
| 244 | 242 |
default:
|
| 245 | 243 |
return true
|
|
| 252 | 250 |
// implicitly, upon p.Stop() call.
|
| 253 | 251 |
func (b *Bar) Complete() {
|
| 254 | 252 |
select {
|
| 255 | |
case b.completeReqCh <- struct{}{}:
|
| 256 | |
case <-b.done:
|
| 257 | |
return
|
| 258 | |
}
|
| 259 | |
}
|
| 260 | |
|
| 261 | |
// Completed: deprecated! Use b.Complete()
|
| 262 | |
func (b *Bar) Completed() {
|
| 263 | |
b.Complete()
|
| 264 | |
}
|
| 265 | |
|
| 266 | |
func (b *Bar) flushed() {
|
| 267 | |
select {
|
| 268 | |
case b.flushedCh <- struct{}{}:
|
| 269 | |
case <-b.done:
|
| 270 | |
return
|
| 271 | |
}
|
| 272 | |
}
|
| 273 | |
|
| 274 | |
func (b *Bar) remove() {
|
| 275 | |
select {
|
| 276 | |
case b.removeReqCh <- struct{}{}:
|
| 277 | |
case <-b.done:
|
| 278 | |
return
|
| 279 | |
}
|
| 280 | |
}
|
| 281 | |
|
| 282 | |
func (b *Bar) getState() state {
|
| 283 | |
select {
|
| 284 | |
case s := <-b.stateCh:
|
| 285 | |
return s
|
| 286 | |
case <-b.done:
|
| 287 | |
return b.state
|
| 288 | |
}
|
| 289 | |
}
|
| 290 | |
|
| 291 | |
func (b *Bar) updateState(cb func(*state)) {
|
| 292 | |
s := b.getState()
|
| 293 | |
cb(&s)
|
| 294 | |
select {
|
| 295 | |
case b.stateCh <- s:
|
| 296 | |
case <-b.done:
|
| 297 | |
return
|
| 298 | |
}
|
| 299 | |
}
|
|
253 |
case <-b.completeReqCh:
|
|
254 |
return
|
|
255 |
default:
|
|
256 |
close(b.completeReqCh)
|
|
257 |
}
|
|
258 |
}
|
|
259 |
|
|
260 |
// func (b *Bar) getState() state {
|
|
261 |
// result := make(chan state, 1)
|
|
262 |
// select {
|
|
263 |
// case b.ops <- func(s *state) { result <- *s }:
|
|
264 |
// return <-result
|
|
265 |
// case <-b.done:
|
|
266 |
// return b.state
|
|
267 |
// }
|
|
268 |
// }
|
| 300 | 269 |
|
| 301 | 270 |
func (b *Bar) server(s state, wg *sync.WaitGroup, cancel <-chan struct{}) {
|
| 302 | |
var incrStartTime time.Time
|
| 303 | 271 |
|
| 304 | 272 |
defer func() {
|
| 305 | 273 |
b.state = s
|
|
274 |
close(b.done)
|
|
275 |
<-s.flushed
|
|
276 |
// fmt.Fprintf(os.Stderr, "Bar:%d flushed\n", s.id)
|
| 306 | 277 |
wg.Done()
|
| 307 | |
close(b.done)
|
| 308 | 278 |
}()
|
| 309 | 279 |
|
| 310 | 280 |
for {
|
| 311 | 281 |
select {
|
| 312 | |
case b.stateCh <- s:
|
| 313 | |
case s = <-b.stateCh:
|
| 314 | |
case r := <-b.incrCh:
|
| 315 | |
if s.current == 0 {
|
| 316 | |
incrStartTime = time.Now()
|
| 317 | |
s.startTime = incrStartTime
|
| 318 | |
}
|
| 319 | |
n := s.current + r.amount
|
| 320 | |
if s.total > 0 && n > s.total {
|
| 321 | |
s.current = s.total
|
| 322 | |
s.completed = true
|
| 323 | |
break // break out of select
|
| 324 | |
}
|
| 325 | |
s.timeElapsed = time.Since(s.startTime)
|
| 326 | |
s.updateTimePerItemEstimate(incrStartTime, r.amount)
|
| 327 | |
if n == s.total {
|
| 328 | |
s.completed = true
|
| 329 | |
close(b.inProgress)
|
| 330 | |
}
|
| 331 | |
s.current = n
|
| 332 | |
if r.refill != nil {
|
| 333 | |
r.refill.till = n
|
| 334 | |
s.refill = r.refill
|
| 335 | |
}
|
| 336 | |
incrStartTime = time.Now()
|
| 337 | |
case <-b.flushedCh:
|
| 338 | |
if s.completed {
|
| 339 | |
return
|
| 340 | |
}
|
|
282 |
case op := <-b.ops:
|
|
283 |
op(&s)
|
| 341 | 284 |
case <-b.completeReqCh:
|
| 342 | 285 |
s.completed = true
|
| 343 | 286 |
return
|
| 344 | |
case <-b.removeReqCh:
|
| 345 | |
return
|
| 346 | |
case <-b.cancel:
|
|
287 |
case <-cancel:
|
| 347 | 288 |
s.aborted = true
|
| 348 | |
close(b.inProgress)
|
| 349 | |
return
|
| 350 | |
}
|
| 351 | |
}
|
| 352 | |
}
|
| 353 | |
|
| 354 | |
func (b *Bar) render(rFn func(chan []byte), termWidth int, prependWs, appendWs *widthSync) <-chan []byte {
|
|
289 |
cancel = nil
|
|
290 |
b.Complete()
|
|
291 |
}
|
|
292 |
}
|
|
293 |
}
|
|
294 |
|
|
295 |
func (b *Bar) render(tw int, flushed chan struct{}, prependWs, appendWs *widthSync) <-chan []byte {
|
| 355 | 296 |
ch := make(chan []byte)
|
| 356 | 297 |
|
| 357 | 298 |
go func() {
|
| 358 | |
defer rFn(ch)
|
| 359 | |
s := b.getState()
|
| 360 | |
buf := draw(&s, termWidth, prependWs, appendWs)
|
|
299 |
defer func() {
|
|
300 |
// recovering if external decorators panic
|
|
301 |
if p := recover(); p != nil {
|
|
302 |
ch <- []byte(fmt.Sprintln(p))
|
|
303 |
}
|
|
304 |
close(ch)
|
|
305 |
}()
|
|
306 |
var st state
|
|
307 |
result := make(chan state, 1)
|
|
308 |
select {
|
|
309 |
case b.ops <- func(s *state) {
|
|
310 |
s.flushed = flushed
|
|
311 |
result <- *s
|
|
312 |
}:
|
|
313 |
st = <-result
|
|
314 |
case <-b.done:
|
|
315 |
st = b.state
|
|
316 |
}
|
|
317 |
buf := draw(&st, tw, prependWs, appendWs)
|
| 361 | 318 |
buf = append(buf, '\n')
|
| 362 | 319 |
ch <- buf
|
| 363 | 320 |
}()
|
|
| 372 | 329 |
}
|
| 373 | 330 |
}
|
| 374 | 331 |
|
| 375 | |
func (s *state) updateTimePerItemEstimate(incrStartTime time.Time, amount int64) {
|
| 376 | |
lastBlockTime := time.Since(incrStartTime) // shorthand for time.Now().Sub(t)
|
|
332 |
func (s *state) updateTimePerItemEstimate(amount int) {
|
|
333 |
lastBlockTime := time.Since(s.blockStartTime) // shorthand for time.Now().Sub(t)
|
| 377 | 334 |
lastItemEstimate := float64(lastBlockTime) / float64(amount)
|
| 378 | 335 |
s.timePerItem = time.Duration((s.etaAlpha * lastItemEstimate) + (1-s.etaAlpha)*float64(s.timePerItem))
|
| 379 | 336 |
}
|
|
| 446 | 403 |
return buf
|
| 447 | 404 |
}
|
| 448 | 405 |
|
| 449 | |
func fillBar(total, current int64, width int, fmtBytes barFmtBytes, rf *Refill) []byte {
|
|
406 |
func fillBar(total, current, width int, fmtBytes barFmtBytes, rf *refill) []byte {
|
| 450 | 407 |
if width < 2 || total <= 0 {
|
| 451 | 408 |
return []byte{}
|
| 452 | 409 |
}
|
|
| 461 | 418 |
|
| 462 | 419 |
if rf != nil {
|
| 463 | 420 |
till := percentage(total, rf.till, barWidth)
|
| 464 | |
rbytes := make([]byte, utf8.RuneLen(rf.Char))
|
| 465 | |
utf8.EncodeRune(rbytes, rf.Char)
|
|
421 |
rbytes := make([]byte, utf8.RuneLen(rf.char))
|
|
422 |
utf8.EncodeRune(rbytes, rf.char)
|
| 466 | 423 |
// append refill rune
|
| 467 | 424 |
for i := 0; i < till; i++ {
|
| 468 | 425 |
buf = append(buf, rbytes...)
|
|
| 514 | 471 |
return fmtBytes
|
| 515 | 472 |
}
|
| 516 | 473 |
|
| 517 | |
func percentage(total, current int64, ratio int) int {
|
|
474 |
func percentage(total, current, ratio int) int {
|
| 518 | 475 |
if total == 0 || current > total {
|
| 519 | 476 |
return 0
|
| 520 | 477 |
}
|