diff --git a/bar.go b/bar.go index ad5bd7f..c4bcdbe 100644 --- a/bar.go +++ b/bar.go @@ -1,34 +1,16 @@ package mpb import ( - "fmt" "io" - "strconv" "sync" "time" ) -type decoratorFuncType uint - -const ( - decoratorAppend decoratorFuncType = iota - decoratorPrepend -) - -// DecoratorFunc is a function that can be prepended and appended to the progress bar -type DecoratorFunc func(s *Statistics) string - -type decorator struct { - kind decoratorFuncType - f DecoratorFunc -} - // Bar represents a progress Bar type Bar struct { - total int - width int - alpha float64 - stopped bool + total int + width int + alpha float64 fill byte empty byte @@ -36,13 +18,14 @@ leftEnd byte rightEnd byte - incrCh chan int - redrawReqCh chan chan []byte - statusReqCh chan chan int - decoratorCh chan *decorator - flushedCh chan struct{} - stopCh chan struct{} - done chan struct{} + incrCh chan int + redrawReqCh chan chan []byte + currentReqCh chan chan int + statusReqCh chan chan int + decoratorCh chan *decorator + flushedCh chan struct{} + stopCh chan struct{} + done chan struct{} } type Statistics struct { @@ -56,21 +39,22 @@ func newBar(total, width int, wg *sync.WaitGroup) *Bar { b := &Bar{ - fill: '=', - empty: '-', - tip: '>', - leftEnd: '[', - rightEnd: ']', - alpha: 0.25, - total: total, - width: width, - incrCh: make(chan int), - redrawReqCh: make(chan chan []byte), - statusReqCh: make(chan chan int), - decoratorCh: make(chan *decorator), - flushedCh: make(chan struct{}), - stopCh: make(chan struct{}), - done: make(chan struct{}), + fill: '=', + empty: '-', + tip: '>', + leftEnd: '[', + rightEnd: ']', + alpha: 0.25, + total: total, + width: width, + incrCh: make(chan int), + redrawReqCh: make(chan chan []byte), + currentReqCh: make(chan chan int), + statusReqCh: make(chan chan int), + decoratorCh: make(chan *decorator), + flushedCh: make(chan struct{}), + stopCh: make(chan struct{}), + done: make(chan struct{}), } go b.server(wg) return b @@ -132,93 +116,53 @@ return &Reader{r, b} } +// Incr increments progress bar +func (b *Bar) Incr(n int) { + if !b.isDone() { + b.incrCh <- n + } +} + +// Current returns the actual current. +// If bar was stopped by Stop(), subsequent calls to Current will return -1 +func (b *Bar) Current() int { + if !b.isDone() { + respCh := make(chan int) + b.currentReqCh <- respCh + return <-respCh + } + return -1 +} + +// Stop stops rendering the bar +func (b *Bar) Stop() { + if !b.isDone() { + b.stopCh <- struct{}{} + } +} + +func (b *Bar) InProgress() bool { + return !b.isDone() +} + +func (b *Bar) PrependFunc(f DecoratorFunc) *Bar { + b.decoratorCh <- &decorator{decoratorPrepend, f} + return b +} + +func (b *Bar) AppendFunc(f DecoratorFunc) *Bar { + b.decoratorCh <- &decorator{decoratorAppend, f} + return b +} + // String returns the string representation of the bar func (b *Bar) String() string { - respCh := make(chan []byte) - b.redrawReqCh <- respCh - return string(<-respCh) -} - -func (b *Bar) Incr(n int) { - if !b.isDone() { - b.incrCh <- n - } -} - -func (b *Bar) Stop() { - if !b.stopped { - b.stopCh <- struct{}{} - b.stopped = true - } -} - -func (b *Bar) InProgress() bool { - return !b.isDone() -} - -func (b *Bar) PrependFunc(f DecoratorFunc) *Bar { - b.decoratorCh <- &decorator{decoratorPrepend, f} - return b -} - -func (b *Bar) AppendFunc(f DecoratorFunc) *Bar { - b.decoratorCh <- &decorator{decoratorAppend, f} - return b -} - -func (b *Bar) PrependName(name string, padding int) *Bar { - layout := "%" + strconv.Itoa(padding) + "s" - b.PrependFunc(func(s *Statistics) string { - return fmt.Sprintf(layout, name) - }) - return b -} - -func (b *Bar) PrependETA(padding int) *Bar { - layout := "ETA%" + strconv.Itoa(padding) + "s" - b.PrependFunc(func(s *Statistics) string { - return fmt.Sprintf(layout, time.Duration(s.eta().Seconds())*time.Second) - }) - return b -} - -func (b *Bar) AppendETA() *Bar { - b.AppendFunc(func(s *Statistics) string { - return fmt.Sprintf("ETA %s", time.Duration(s.eta().Seconds())*time.Second) - }) - return b -} - -func (b *Bar) PrependElapsed(padding int) *Bar { - layout := "%" + strconv.Itoa(padding) + "s" - b.PrependFunc(func(s *Statistics) string { - return fmt.Sprintf(layout, time.Duration(s.TimeElapsed.Seconds())*time.Second) - }) - return b -} - -func (b *Bar) AppendElapsed() *Bar { - b.AppendFunc(func(s *Statistics) string { - return fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) - }) - return b -} - -func (b *Bar) AppendPercentage() *Bar { - b.AppendFunc(func(s *Statistics) string { - completed := int(100 * float64(s.Current) / float64(s.Total)) - return fmt.Sprintf("%3d %%", completed) - }) - return b -} - -func (b *Bar) PrependPercentage(padding int) *Bar { - layout := "%" + strconv.Itoa(padding) + "d %%" - b.PrependFunc(func(s *Statistics) string { - completed := int(100 * float64(s.Current) / float64(s.Total)) - return fmt.Sprintf(layout, completed) - }) - return b + if !b.isDone() { + respCh := make(chan []byte) + b.redrawReqCh <- respCh + return string(<-respCh) + } + return "" } func (b *Bar) server(wg *sync.WaitGroup) { @@ -254,6 +198,8 @@ case decoratorPrepend: prependFuncs = append(prependFuncs, d.f) } + case respCh := <-b.currentReqCh: + respCh <- current case respCh := <-b.redrawReqCh: stat := &Statistics{b.total, current, timeElapsed, tpie} respCh <- b.draw(stat, buf, appendFuncs, prependFuncs) @@ -265,8 +211,8 @@ wg.Done() } case <-b.stopCh: + close(b.done) if !completed { - close(b.done) wg.Done() } return diff --git a/decorators.go b/decorators.go new file mode 100644 index 0000000..bda0023 --- /dev/null +++ b/decorators.go @@ -0,0 +1,88 @@ +package mpb + +import ( + "fmt" + "strconv" + "time" +) + +type decoratorFuncType uint + +const ( + decoratorAppend decoratorFuncType = iota + decoratorPrepend +) + +// DecoratorFunc is a function that can be prepended and appended to the progress bar +type DecoratorFunc func(s *Statistics) string + +type decorator struct { + kind decoratorFuncType + f DecoratorFunc +} + +func (b *Bar) PrependName(name string, padding int) *Bar { + layout := "%" + strconv.Itoa(padding) + "s" + b.PrependFunc(func(s *Statistics) string { + return fmt.Sprintf(layout, name) + }) + return b +} + +func (b *Bar) PrependCounters(unit Units, padding int) *Bar { + layout := "%" + strconv.Itoa(padding) + "s" + b.PrependFunc(func(s *Statistics) string { + current := Format(s.Current).To(unit) + total := Format(s.Total).To(unit) + str := fmt.Sprintf("%s / %s", current, total) + return fmt.Sprintf(layout, str) + }) + return b +} + +func (b *Bar) PrependETA(padding int) *Bar { + layout := "ETA%" + strconv.Itoa(padding) + "s" + b.PrependFunc(func(s *Statistics) string { + return fmt.Sprintf(layout, time.Duration(s.eta().Seconds())*time.Second) + }) + return b +} + +func (b *Bar) AppendETA() *Bar { + b.AppendFunc(func(s *Statistics) string { + return fmt.Sprintf("ETA %s", time.Duration(s.eta().Seconds())*time.Second) + }) + return b +} + +func (b *Bar) PrependElapsed(padding int) *Bar { + layout := "%" + strconv.Itoa(padding) + "s" + b.PrependFunc(func(s *Statistics) string { + return fmt.Sprintf(layout, time.Duration(s.TimeElapsed.Seconds())*time.Second) + }) + return b +} + +func (b *Bar) AppendElapsed() *Bar { + b.AppendFunc(func(s *Statistics) string { + return fmt.Sprint(time.Duration(s.TimeElapsed.Seconds()) * time.Second) + }) + return b +} + +func (b *Bar) AppendPercentage() *Bar { + b.AppendFunc(func(s *Statistics) string { + completed := int(100 * float64(s.Current) / float64(s.Total)) + return fmt.Sprintf("%3d %%", completed) + }) + return b +} + +func (b *Bar) PrependPercentage(padding int) *Bar { + layout := "%" + strconv.Itoa(padding) + "d %%" + b.PrependFunc(func(s *Statistics) string { + completed := int(100 * float64(s.Current) / float64(s.Total)) + return fmt.Sprintf(layout, completed) + }) + return b +} diff --git a/example/hang/main.go b/example/hang/main.go index 3cead15..d45ac02 100644 --- a/example/hang/main.go +++ b/example/hang/main.go @@ -20,6 +20,7 @@ } p := mpb.New() + p.Wg.Add(1) bar := p.AddBar(totalItem).AppendETA().PrependFunc(decor) blockSize := rand.Intn(maxBlockSize) + 1 diff --git a/example/io/multiple/main.go b/example/io/multiple/main.go new file mode 100644 index 0000000..d962ef5 --- /dev/null +++ b/example/io/multiple/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/vbauerster/mpb" +) + +func main() { + log.SetOutput(os.Stderr) + + url1 := "https://homebrew.bintray.com/bottles/youtube-dl-2016.12.12.sierra.bottle.tar.gz" + url2 := "https://homebrew.bintray.com/bottles/libtiff-4.0.7.sierra.bottle.tar.gz" + + p := mpb.New().SetWidth(60) + + for i, url := range [...]string{url1, url2} { + p.Wg.Add(1) // if you omit this line, main will return without waiting for download goroutines + name := fmt.Sprintf("url%d:", i+1) + go download(p, name, url) + } + + p.WaitAndStop() + fmt.Println("Finished") +} + +func download(p *mpb.Progress, name, url string) { + resp, err := http.Get(url) + if err != nil { + log.Printf("%s: %v", name, err) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("non-200 status: %s", resp.Status) + log.Printf("%s: %v", name, err) + return + } + + size := resp.ContentLength + + // create dest + destName := filepath.Base(url) + dest, err := os.Create(destName) + if err != nil { + err = fmt.Errorf("Can't create %s: %v", destName, err) + log.Printf("%s: %v", name, err) + return + } + + // create bar with appropriate decorators + bar := p.AddBar(int(size)). + PrependCounters(mpb.UnitBytes, 19). + PrependName(name, len(name)). + AppendETA() + // create proxy reader + reader := bar.ProxyReader(resp.Body) + // and copy from reader + _, err = io.Copy(dest, reader) + + if closeErr := dest.Close(); err == nil { + err = closeErr + } + if err != nil { + log.Printf("%s: %v", name, err) + } +} diff --git a/example/io/single/main.go b/example/io/single/main.go new file mode 100644 index 0000000..bd09e24 --- /dev/null +++ b/example/io/single/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" + + "github.com/vbauerster/mpb" +) + +func main() { + url := "https://homebrew.bintray.com/bottles/libtiff-4.0.7.sierra.bottle.tar.gz" + + resp, err := http.Get(url) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + fmt.Printf("Server return non-200 status: %s\n", resp.Status) + return + } + + size := resp.ContentLength + + // create dest + destName := filepath.Base(url) + dest, err := os.Create(destName) + if err != nil { + fmt.Printf("Can't create %s: %v\n", destName, err) + return + } + defer dest.Close() + + p := mpb.New().SetWidth(64) + // if you omit following line, download will complete fine, but rendering bar + // may not complete, thus better always use even in single thread. + p.Wg.Add(1) + + bar := p.AddBar(int(size)).PrependCounters(mpb.UnitBytes, 19).AppendETA() + + // create proxy reader + reader := bar.ProxyReader(resp.Body) + + // and copy from reader, ignoring errors + io.Copy(dest, reader) + + p.WaitAndStop() + fmt.Println("Finished") +} diff --git a/example/multi/multi.go b/example/multi/multi.go deleted file mode 100644 index 4e3ff5f..0000000 --- a/example/multi/multi.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "fmt" - "math/rand" - "time" - - "github.com/vbauerster/mpb" -) - -const ( - maxBlockSize = 12 -) - -func main() { - decor := func(s *mpb.Statistics) string { - str := fmt.Sprintf("%d/%d", s.Current, s.Total) - return fmt.Sprintf("%-7s", str) - } - - p := mpb.New().RefreshRate(80 * time.Millisecond) - - bar1 := p.AddBar(50).AppendETA().PrependFunc(decor) - go func() { - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 50; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar1.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - }() - - bar2 := p.AddBar(100).AppendETA().PrependFunc(decor) - go func() { - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 100; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar2.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - }() - - bar3 := p.AddBar(80).AppendETA().PrependFunc(decor) - go func() { - blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; i < 80; i++ { - time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar3.Incr(1) - blockSize = rand.Intn(maxBlockSize) + 1 - } - }() - - // time.Sleep(time.Second) - // p.RemoveBar(bar2) - - p.WaitAndStop() - bar2.Incr(2) - fmt.Println("stop") - // p.AddBar(1) // panic: send on closed channnel -} diff --git a/example/prependETA/main.go b/example/prependETA/main.go index f177355..03c2ac0 100644 --- a/example/prependETA/main.go +++ b/example/prependETA/main.go @@ -14,11 +14,12 @@ func main() { + p := mpb.New().SetWidth(64) // p := mpb.New().RefreshRate(80 * time.Millisecond).SetWidth(64) - p := mpb.New().SetWidth(64) name1 := "Bar#1:" bar1 := p.AddBar(50).AppendPercentage().PrependETA(4).PrependName(name1, len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 50; i++ { @@ -29,6 +30,7 @@ }() bar2 := p.AddBar(100).AppendPercentage().PrependETA(4).PrependName("", 0-len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 100; i++ { @@ -39,6 +41,7 @@ }() bar3 := p.AddBar(80).AppendPercentage().PrependETA(4).PrependName("Bar#3:", 0) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 80; i++ { diff --git a/example/prependElapsed/main.go b/example/prependElapsed/main.go index 273fac9..6359c02 100644 --- a/example/prependElapsed/main.go +++ b/example/prependElapsed/main.go @@ -15,9 +15,11 @@ func main() { p := mpb.New().SetWidth(64) + // p := mpb.New().RefreshRate(80 * time.Millisecond).SetWidth(64) name1 := "Bar#1:" bar1 := p.AddBar(50).AppendPercentage().PrependElapsed(3).PrependName(name1, len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 50; i++ { @@ -28,6 +30,7 @@ }() bar2 := p.AddBar(100).AppendPercentage().PrependElapsed(3).PrependName("", 0-len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 100; i++ { @@ -38,6 +41,7 @@ }() bar3 := p.AddBar(80).AppendPercentage().PrependElapsed(3).PrependName("Bar#3:", 0) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 80; i++ { diff --git a/example/prependPercent/main.go b/example/prependPercent/main.go index 5de767b..f1e2ed0 100644 --- a/example/prependPercent/main.go +++ b/example/prependPercent/main.go @@ -14,11 +14,12 @@ func main() { + p := mpb.New().SetWidth(64) // p := mpb.New().RefreshRate(100 * time.Millisecond).SetWidth(64) - p := mpb.New().SetWidth(64) name1 := "Bar#1:" bar1 := p.AddBar(50).AppendETA().PrependPercentage(3).PrependName(name1, len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 50; i++ { @@ -29,6 +30,7 @@ }() bar2 := p.AddBar(100).AppendETA().PrependPercentage(3).PrependName("", 0-len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 100; i++ { @@ -39,6 +41,7 @@ }() bar3 := p.AddBar(80).AppendETA().PrependPercentage(3).PrependName("Bar#3:", 0) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 80; i++ { diff --git a/example/remove/main.go b/example/remove/main.go index bce4755..858367c 100644 --- a/example/remove/main.go +++ b/example/remove/main.go @@ -3,32 +3,31 @@ import ( "fmt" "math/rand" - "runtime" "time" - "github.com/vbauerster/uiprogress" + "github.com/vbauerster/mpb" ) const ( totalItem = 100 - maxBlockSize = 20 + maxBlockSize = 14 ) func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - decor := func(s *uiprogress.Statistics) string { - str := fmt.Sprintf("%d/%d", s.Completed, s.Total) + decor := func(s *mpb.Statistics) string { + str := fmt.Sprintf("%d/%d", s.Current, s.Total) return fmt.Sprintf("%-7s", str) } - p := uiprogress.New() + p := mpb.New() bar := p.AddBar(totalItem).AppendETA().PrependFunc(decor) + p.Wg.Add(1) blockSize := rand.Intn(maxBlockSize) + 1 - for i := 0; !bar.IsCompleted(); i += 1 { + for i := 0; bar.InProgress(); i++ { time.Sleep(time.Duration(blockSize) * (50*time.Millisecond + time.Duration(rand.Intn(5*int(time.Millisecond))))) - bar.Incr(1) - if i == 42 { + bar.Incr(blockSize) + if bar.Current() > 42 { p.RemoveBar(bar) } blockSize = rand.Intn(maxBlockSize) + 1 diff --git a/example/sort/main.go b/example/sort/main.go index 391d9fa..b158546 100644 --- a/example/sort/main.go +++ b/example/sort/main.go @@ -18,6 +18,7 @@ name1 := "Bar#1:" bar1 := p.AddBar(100).AppendETA().PrependFunc(getDecor()).PrependName(name1, len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 100; i++ { @@ -28,6 +29,7 @@ }() bar2 := p.AddBar(60).AppendETA().PrependFunc(getDecor()).PrependName("", 0-len(name1)) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 60; i++ { @@ -38,6 +40,7 @@ }() bar3 := p.AddBar(80).AppendETA().PrependFunc(getDecor()).PrependName("Bar#3:", 0) + p.Wg.Add(1) go func() { blockSize := rand.Intn(maxBlockSize) + 1 for i := 0; i < 80; i++ { diff --git a/format.go b/format.go new file mode 100644 index 0000000..7e10ff3 --- /dev/null +++ b/format.go @@ -0,0 +1,67 @@ +package mpb + +import ( + "fmt" + "strings" +) + +const ( + _ = iota + bytesInKiB = 1 << (iota * 10) + bytesInMiB + bytesInGiB + bytesInTiB +) + +type Units uint + +const ( + UnitNone Units = iota + UnitBytes +) + +func Format(i int) *formatter { + return &formatter{n: i} +} + +type formatter struct { + n int + unit Units + width int +} + +func (f *formatter) To(unit Units) *formatter { + f.unit = unit + return f +} + +func (f *formatter) Width(width int) *formatter { + f.width = width + return f +} + +func (f *formatter) String() string { + switch f.unit { + case UnitBytes: + return formatBytes(f.n) + default: + return fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) + } +} + +func formatBytes(i int) (result string) { + switch { + case i > bytesInTiB: + result = fmt.Sprintf("%.02fTiB", float64(i)/bytesInTiB) + case i > bytesInGiB: + result = fmt.Sprintf("%.02fGiB", float64(i)/bytesInGiB) + case i > bytesInMiB: + result = fmt.Sprintf("%.02fMiB", float64(i)/bytesInMiB) + case i > bytesInKiB: + result = fmt.Sprintf("%.02fKiB", float64(i)/bytesInKiB) + default: + result = fmt.Sprintf("%db", i) + } + result = strings.Trim(result, " ") + return +} diff --git a/progress.go b/progress.go index 8cefe4e..531ad7a 100644 --- a/progress.go +++ b/progress.go @@ -31,17 +31,18 @@ // Progress represents the container that renders Progress bars type Progress struct { - out io.Writer - width int - sort SortType - stopped bool + Wg *sync.WaitGroup + + out io.Writer + width int + sort SortType + // stopped bool op chan *operation rrChangeReqCh chan time.Duration outChangeReqCh chan io.Writer countReqCh chan chan int - - wg *sync.WaitGroup + allDone chan struct{} } type operation struct { @@ -58,12 +59,14 @@ rrChangeReqCh: make(chan time.Duration), outChangeReqCh: make(chan io.Writer), countReqCh: make(chan chan int), - wg: new(sync.WaitGroup), + allDone: make(chan struct{}), + Wg: new(sync.WaitGroup), } go p.server(cwriter.New(os.Stdout), time.NewTicker(rr*time.Millisecond)) return p } +// SetWidth sets the width for all underlying bars func (p *Progress) SetWidth(n int) *Progress { if n <= 0 { return p @@ -88,6 +91,7 @@ return p } +// WithSort sorts the bars, while redering func (p *Progress) WithSort(sort SortType) *Progress { p.sort = sort return p @@ -95,18 +99,19 @@ // AddBar creates a new progress bar and adds to the container func (p *Progress) AddBar(total int) *Bar { - p.wg.Add(1) - bar := newBar(total, p.width, p.wg) + bar := newBar(total, p.width, p.Wg) p.op <- &operation{opBarAdd, bar, nil} return bar } +// RemoveBar removes bar at any time func (p *Progress) RemoveBar(b *Bar) bool { result := make(chan bool) p.op <- &operation{opBarRemove, b, result} return <-result } +// BarsCount returns bars count in the container func (p *Progress) BarsCount() int { respCh := make(chan int) p.countReqCh <- respCh @@ -115,10 +120,9 @@ // WaitAndStop stops listening func (p *Progress) WaitAndStop() { - if !p.stopped { - // fmt.Fprintln(os.Stderr, "p.WaitAndStop") - p.stopped = true - p.wg.Wait() + if !p.isAllDone() { + close(p.allDone) + p.Wg.Wait() close(p.op) } } @@ -133,7 +137,6 @@ cw = cwriter.New(w) case op, ok := <-p.op: if !ok { - // fmt.Fprintln(os.Stderr, "Sopping bars") for _, b := range bars { b.Stop() } @@ -180,3 +183,12 @@ } } } + +func (p *Progress) isAllDone() bool { + select { + case <-p.allDone: + return true + default: + return false + } +}