diff --git a/bar.go b/bar.go index b6e29e1..5a3e93e 100644 --- a/bar.go +++ b/bar.go @@ -2,6 +2,7 @@ import ( "fmt" + "sync" "time" ) @@ -65,13 +66,15 @@ Alpha float64 - currentIncrCh chan int + incrRequestCh chan *incrRequest redrawRequestCh chan *redrawRequest decoratorCh chan *decorator timePerItemEstimate time.Duration + + flushedCh chan *sync.WaitGroup } type Statistics struct { @@ -80,11 +83,16 @@ } type redrawRequest struct { - bufch chan []byte + bufCh chan []byte +} + +type incrRequest struct { + amount int + result chan bool } // NewBar returns a new progress bar -func NewBar(total int) *Bar { +func newBar(total int) *Bar { b := &Bar{ Alpha: 0.25, total: total, @@ -94,16 +102,13 @@ Head: Head, Fill: Fill, Empty: Empty, - currentIncrCh: make(chan int), + incrRequestCh: make(chan *incrRequest), redrawRequestCh: make(chan *redrawRequest), decoratorCh: make(chan *decorator), + flushedCh: make(chan *sync.WaitGroup), } go b.server() return b -} - -func (b *Bar) Incr(n int) { - b.currentIncrCh <- n } func (b *Bar) PrependFunc(f DecoratorFunc) *Bar { @@ -132,29 +137,56 @@ return b } +func (b *Bar) PrependPercentage() *Bar { + b.PrependFunc(func(s *Statistics) string { + completed := int(100 * float64(s.Completed) / float64(s.Total)) + return fmt.Sprintf("%3d %%", completed) + }) + return b +} + // String returns the string representation of the bar func (b *Bar) String() string { - bufch := make(chan []byte) - b.redrawRequestCh <- &redrawRequest{bufch} - return string(<-bufch) + bufCh := make(chan []byte) + b.redrawRequestCh <- &redrawRequest{bufCh} + return string(<-bufCh) +} + +// func (b *Bar) bytes() []byte { +// bufch := make(chan []byte) +// b.redrawRequestCh <- &redrawRequest{bufch} +// return <-bufch +// } + +func (b *Bar) flushed(wg *sync.WaitGroup) { + b.flushedCh <- wg +} + +func (b *Bar) Incr(n int) bool { + result := make(chan bool) + b.incrRequestCh <- &incrRequest{n, result} + return <-result } func (b *Bar) server() { - var current int + var completed int blockStartTime := time.Now() buf := make([]byte, b.Width, b.Width+24) var appendFuncs []DecoratorFunc var prependFuncs []DecoratorFunc for { select { - case i := <-b.currentIncrCh: - n := current + i + case r := <-b.incrRequestCh: + n := completed + r.amount if n > b.total { - return + completed = b.total + r.result <- false + break // breaks out of select, not for } - b.updateTimePerItemEstimate(i, blockStartTime) - current = n + b.updateTimePerItemEstimate(r.amount, blockStartTime) + completed = n blockStartTime = time.Now() + r.result <- true case d := <-b.decoratorCh: switch d.kind { case decoratorAppend: @@ -163,7 +195,13 @@ prependFuncs = append(prependFuncs, d.f) } case r := <-b.redrawRequestCh: - r.bufch <- b.draw(buf, current, appendFuncs, prependFuncs) + r.bufCh <- b.draw(buf, completed, appendFuncs, prependFuncs) + case wg := <-b.flushedCh: + if completed == b.total { + close(b.incrRequestCh) + wg.Done() + return + } } } } diff --git a/progress.go b/progress.go index 231d165..675e43a 100644 --- a/progress.go +++ b/progress.go @@ -4,19 +4,20 @@ "fmt" "io" "os" + "sync" "time" - "github.com/gosuri/uilive" + "github.com/vbauerster/uilive" ) type opType uint const ( - add opType = iota - remove + barAdd opType = iota + barRemove ) -const refreshRate = 100 +const refreshRate = 50 // progress represents the container that renders progress bars type progress struct { @@ -32,12 +33,14 @@ // new refresh interval to be send over this channel interval chan time.Duration + + wg *sync.WaitGroup } type operation struct { - kind opType - bar *Bar - ok chan bool + kind opType + bar *Bar + result chan bool } // New returns a new progress bar with defaults @@ -47,9 +50,25 @@ lw: uilive.New(), op: make(chan *operation), interval: make(chan time.Duration), + wg: new(sync.WaitGroup), } go p.server() return p +} + +// 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) + // bar.Width = p.Width + p.op <- &operation{barAdd, bar, nil} + return bar +} + +func (p *progress) RemoveBar(b *Bar) bool { + result := make(chan bool) + p.op <- &operation{barRemove, b, result} + return <-result } // RefreshRate overrides default (30ms) refreshRate value @@ -65,20 +84,6 @@ return p } -// AddBar creates a new progress bar and adds to the container -func (p *progress) AddBar(total int) *Bar { - bar := NewBar(total) - // bar.Width = p.Width - p.op <- &operation{add, bar, nil} - return bar -} - -func (p *progress) RemoveBar(b *Bar) bool { - result := make(chan bool) - p.op <- &operation{remove, b, result} - return <-result -} - // Bypass returns a writer which allows non-buffered data to be written to the underlying output func (p *progress) Bypass() io.Writer { return p.lw.Bypass() @@ -86,6 +91,7 @@ // Stop stops listening func (p *progress) Stop() { + p.wg.Wait() close(p.op) } @@ -100,12 +106,13 @@ if !ok { t.Stop() close(p.interval) + p.lw.Stop() return } switch op.kind { - case add: + case barAdd: bars = append(bars, op.bar) - case remove: + case barRemove: var ok bool for i, b := range bars { if b == op.bar { @@ -114,13 +121,16 @@ break } } - op.ok <- ok + op.result <- ok } case <-t.C: for _, b := range bars { fmt.Fprintln(p.lw, b.String()) } p.lw.Flush() + for _, b := range bars { + b.flushed(p.wg) + } case d := <-p.interval: t.Stop() t = time.NewTicker(d)