Correct PopCompletedMode with multiple bars
Limit number of bars by terminal height
Address issues #111 #109 #107
Vladimir Bauer
3 years ago
| 28 | 28 | recoveredPanic interface{} |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | type extenderFunc func(in io.Reader, reqWidth int, st decor.Statistics) (out io.Reader, lines int) | |
| 31 | type extenderFunc func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader | |
| 32 | 32 | |
| 33 | 33 | // bState is actual bar's state. |
| 34 | 34 | type bState struct { |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | 63 | type renderFrame struct { |
| 64 | reader io.Reader | |
| 65 | lines int | |
| 66 | shutdown bool | |
| 64 | rows []io.Reader | |
| 65 | shutdown int | |
| 67 | 66 | } |
| 68 | 67 | |
| 69 | 68 | func newBar(container *Progress, bs *bState) *Bar { |
| 358 | 357 | func (b *Bar) render(tw int) { |
| 359 | 358 | select { |
| 360 | 359 | case b.operateState <- func(s *bState) { |
| 361 | var reader io.Reader | |
| 362 | var lines int | |
| 360 | var rows []io.Reader | |
| 363 | 361 | stat := newStatistics(tw, s) |
| 364 | 362 | defer func() { |
| 365 | 363 | // recovering if user defined decorator panics for example |
| 370 | 368 | } |
| 371 | 369 | s.aborted = !s.completed |
| 372 | 370 | s.extender = makePanicExtender(p) |
| 373 | reader, lines = s.extender(nil, s.reqWidth, stat) | |
| 374 | 371 | b.recoveredPanic = p |
| 375 | 372 | } |
| 376 | frame := renderFrame{ | |
| 377 | reader: reader, | |
| 378 | lines: lines + 1, | |
| 379 | shutdown: s.completed || s.aborted, | |
| 380 | } | |
| 381 | if frame.shutdown { | |
| 373 | if s.extender != nil { | |
| 374 | rows = s.extender(rows, s.reqWidth, stat) | |
| 375 | } | |
| 376 | frame := &renderFrame{ | |
| 377 | rows: rows, | |
| 378 | } | |
| 379 | if s.completed || s.aborted { | |
| 382 | 380 | b.cancel() |
| 383 | } | |
| 384 | b.frameCh <- &frame | |
| 381 | frame.shutdown++ | |
| 382 | } | |
| 383 | b.frameCh <- frame | |
| 385 | 384 | }() |
| 386 | 385 | if b.recoveredPanic == nil { |
| 387 | reader = s.draw(stat) | |
| 388 | } | |
| 389 | reader, lines = s.extender(reader, s.reqWidth, stat) | |
| 390 | }: | |
| 391 | case <-b.done: | |
| 392 | var reader io.Reader | |
| 393 | var lines int | |
| 394 | stat, s := newStatistics(tw, b.bs), b.bs | |
| 386 | rows = append(rows, s.draw(stat)) | |
| 387 | } | |
| 388 | }: | |
| 389 | case <-b.done: | |
| 390 | var rows []io.Reader | |
| 391 | s, stat := b.bs, newStatistics(tw, b.bs) | |
| 395 | 392 | if b.recoveredPanic == nil { |
| 396 | reader = s.draw(stat) | |
| 397 | } | |
| 398 | reader, lines = s.extender(reader, s.reqWidth, stat) | |
| 399 | b.frameCh <- &renderFrame{ | |
| 400 | reader: reader, | |
| 401 | lines: lines + 1, | |
| 402 | } | |
| 393 | rows = append(rows, s.draw(stat)) | |
| 394 | } | |
| 395 | if s.extender != nil { | |
| 396 | rows = s.extender(rows, s.reqWidth, stat) | |
| 397 | } | |
| 398 | frame := &renderFrame{ | |
| 399 | rows: rows, | |
| 400 | } | |
| 401 | b.frameCh <- frame | |
| 403 | 402 | } |
| 404 | 403 | } |
| 405 | 404 | |
| 595 | 594 | |
| 596 | 595 | func makePanicExtender(p interface{}) extenderFunc { |
| 597 | 596 | pstr := fmt.Sprint(p) |
| 598 | return func(_ io.Reader, _ int, st decor.Statistics) (io.Reader, int) { | |
| 599 | mr := io.MultiReader( | |
| 600 | strings.NewReader(runewidth.Truncate(pstr, st.AvailableWidth, "…")), | |
| 601 | strings.NewReader("\n"), | |
| 597 | return func(rows []io.Reader, _ int, stat decor.Statistics) []io.Reader { | |
| 598 | r := io.MultiReader( | |
| 599 | strings.NewReader(runewidth.Truncate(pstr, stat.AvailableWidth, "…")), | |
| 600 | bytes.NewReader([]byte("\n")), | |
| 602 | 601 | ) |
| 603 | return mr, 0 | |
| 604 | } | |
| 605 | } | |
| 602 | return append(rows, r) | |
| 603 | } | |
| 604 | } | |
| 130 | 130 | |
| 131 | 131 | func makeExtenderFunc(filler BarFiller) extenderFunc { |
| 132 | 132 | buf := new(bytes.Buffer) |
| 133 | return func(r io.Reader, reqWidth int, st decor.Statistics) (io.Reader, int) { | |
| 134 | filler.Fill(buf, reqWidth, st) | |
| 135 | return io.MultiReader(r, buf), bytes.Count(buf.Bytes(), []byte("\n")) | |
| 133 | return func(rows []io.Reader, width int, stat decor.Statistics) []io.Reader { | |
| 134 | buf.Reset() | |
| 135 | filler.Fill(buf, width, stat) | |
| 136 | for { | |
| 137 | b, err := buf.ReadBytes('\n') | |
| 138 | if err != nil { | |
| 139 | break | |
| 140 | } | |
| 141 | rows = append(rows, bytes.NewReader(b)) | |
| 142 | } | |
| 143 | return rows | |
| 136 | 144 | } |
| 137 | 145 | } |
| 138 | 146 |
| 5 | 5 | func (pq priorityQueue) Len() int { return len(pq) } |
| 6 | 6 | |
| 7 | 7 | func (pq priorityQueue) Less(i, j int) bool { |
| 8 | return pq[i].priority < pq[j].priority | |
| 8 | // less priority pops first | |
| 9 | return pq[i].priority > pq[j].priority | |
| 9 | 10 | } |
| 10 | 11 | |
| 11 | 12 | func (pq priorityQueue) Swap(i, j int) { |
| 11 | 11 | "time" |
| 12 | 12 | |
| 13 | 13 | "github.com/vbauerster/mpb/v7/cwriter" |
| 14 | "github.com/vbauerster/mpb/v7/decor" | |
| 15 | 14 | ) |
| 16 | 15 | |
| 17 | 16 | const ( |
| 40 | 39 | // following are provided/overrided by user |
| 41 | 40 | idCount int |
| 42 | 41 | reqWidth int |
| 42 | popPriority int | |
| 43 | 43 | popCompleted bool |
| 44 | 44 | outputDiscarded bool |
| 45 | 45 | rr time.Duration |
| 63 | 63 | // method has been called. |
| 64 | 64 | func NewWithContext(ctx context.Context, options ...ContainerOption) *Progress { |
| 65 | 65 | s := &pState{ |
| 66 | bHeap: priorityQueue{}, | |
| 67 | rr: prr, | |
| 68 | queueBars: make(map[*Bar]*Bar), | |
| 69 | output: os.Stdout, | |
| 66 | bHeap: priorityQueue{}, | |
| 67 | rr: prr, | |
| 68 | queueBars: make(map[*Bar]*Bar), | |
| 69 | output: os.Stdout, | |
| 70 | popPriority: math.MinInt32, | |
| 70 | 71 | } |
| 71 | 72 | |
| 72 | 73 | for _, opt := range options { |
| 238 | 239 | syncWidth(s.pMatrix) |
| 239 | 240 | syncWidth(s.aMatrix) |
| 240 | 241 | |
| 241 | tw, err := cw.GetWidth() | |
| 242 | width, height, err := cw.GetTermSize() | |
| 242 | 243 | if err != nil { |
| 243 | tw = s.reqWidth | |
| 244 | width = s.reqWidth | |
| 244 | 245 | } |
| 245 | 246 | for i := 0; i < s.bHeap.Len(); i++ { |
| 246 | 247 | bar := s.bHeap[i] |
| 247 | go bar.render(tw) | |
| 248 | } | |
| 249 | ||
| 250 | return s.flush(cw) | |
| 251 | } | |
| 252 | ||
| 253 | func (s *pState) flush(cw *cwriter.Writer) error { | |
| 254 | var lines int | |
| 248 | go bar.render(width) | |
| 249 | } | |
| 250 | ||
| 251 | return s.flush(cw, height) | |
| 252 | } | |
| 253 | ||
| 254 | func (s *pState) flush(cw *cwriter.Writer, height int) error { | |
| 255 | var popCount int | |
| 256 | rows := make([]io.Reader, 0, s.bHeap.Len()) | |
| 255 | 257 | pool := make([]*Bar, 0, s.bHeap.Len()) |
| 256 | 258 | for s.bHeap.Len() > 0 { |
| 259 | var frameRowsUsed int | |
| 257 | 260 | b := heap.Pop(&s.bHeap).(*Bar) |
| 258 | 261 | frame := <-b.frameCh |
| 259 | lines += frame.lines | |
| 260 | _, err := cw.ReadFrom(frame.reader) | |
| 261 | if err != nil { | |
| 262 | return err | |
| 263 | } | |
| 264 | if frame.shutdown { | |
| 262 | for i := len(frame.rows) - 1; i >= 0; i-- { | |
| 263 | if len(rows) == height { | |
| 264 | break | |
| 265 | } | |
| 266 | rows = append(rows, frame.rows[i]) | |
| 267 | frameRowsUsed++ | |
| 268 | } | |
| 269 | if frame.shutdown != 0 { | |
| 265 | 270 | b.Wait() // waiting for b.done, so it's safe to read b.bs |
| 266 | var toDrop bool | |
| 271 | drop := b.bs.dropOnComplete | |
| 267 | 272 | if qb, ok := s.queueBars[b]; ok { |
| 268 | 273 | delete(s.queueBars, b) |
| 269 | 274 | qb.priority = b.priority |
| 275 | qb.bs.dropOnComplete = drop | |
| 270 | 276 | pool = append(pool, qb) |
| 271 | toDrop = true | |
| 277 | drop = true | |
| 272 | 278 | } else if s.popCompleted && !b.bs.noPop { |
| 273 | lines -= frame.lines | |
| 274 | toDrop = true | |
| 275 | } | |
| 276 | if toDrop || b.bs.dropOnComplete { | |
| 279 | if frame.shutdown > 1 { | |
| 280 | popCount += frameRowsUsed | |
| 281 | drop = true | |
| 282 | } else { | |
| 283 | s.popPriority++ | |
| 284 | b.priority = s.popPriority | |
| 285 | } | |
| 286 | } | |
| 287 | if drop { | |
| 277 | 288 | s.heapUpdated = true |
| 278 | 289 | continue |
| 279 | 290 | } |
| 285 | 296 | heap.Push(&s.bHeap, b) |
| 286 | 297 | } |
| 287 | 298 | |
| 288 | return cw.Flush(lines) | |
| 299 | for i := len(rows) - 1; i >= 0; i-- { | |
| 300 | _, err := cw.ReadFrom(rows[i]) | |
| 301 | if err != nil { | |
| 302 | return err | |
| 303 | } | |
| 304 | } | |
| 305 | ||
| 306 | return cw.Flush(len(rows) - popCount) | |
| 289 | 307 | } |
| 290 | 308 | |
| 291 | 309 | func (s *pState) newTicker(done <-chan struct{}) chan time.Time { |
| 351 | 369 | reqWidth: s.reqWidth, |
| 352 | 370 | total: total, |
| 353 | 371 | filler: filler, |
| 354 | extender: func(r io.Reader, _ int, _ decor.Statistics) (io.Reader, int) { return r, 0 }, | |
| 355 | 372 | debugOut: s.debugOut, |
| 356 | 373 | } |
| 357 | 374 | |
| 368 | 385 | if bs.middleware != nil { |
| 369 | 386 | bs.filler = bs.middleware(filler) |
| 370 | 387 | bs.middleware = nil |
| 371 | } | |
| 372 | ||
| 373 | if s.popCompleted && !bs.noPop { | |
| 374 | bs.priority = -(math.MaxInt32 - s.idCount) | |
| 375 | 388 | } |
| 376 | 389 | |
| 377 | 390 | for i := 0; i < len(bs.buffers); i++ { |