| 0 | 0 |
package mpb
|
| 1 | 1 |
|
| 2 | 2 |
import (
|
|
3 |
"context"
|
| 3 | 4 |
"errors"
|
| 4 | 5 |
"io"
|
| 5 | 6 |
"os"
|
|
| 34 | 35 |
|
| 35 | 36 |
// Progress represents the container that renders Progress bars
|
| 36 | 37 |
type Progress struct {
|
|
38 |
// Context for canceling bars rendering
|
|
39 |
ctx context.Context
|
| 37 | 40 |
// WaitGroup for internal rendering sync
|
| 38 | 41 |
wg *sync.WaitGroup
|
| 39 | 42 |
|
|
| 41 | 44 |
width int
|
| 42 | 45 |
sort SortType
|
| 43 | 46 |
|
| 44 | |
op chan *operation
|
|
47 |
operationCh chan *operation
|
| 45 | 48 |
rrChangeReqCh chan time.Duration
|
| 46 | 49 |
outChangeReqCh chan io.Writer
|
| 47 | |
countReqCh chan chan int
|
| 48 | |
allDone chan struct{}
|
|
50 |
barCountReqCh chan chan int
|
|
51 |
done chan struct{}
|
| 49 | 52 |
}
|
| 50 | 53 |
|
| 51 | 54 |
type operation struct {
|
|
| 54 | 57 |
result chan bool
|
| 55 | 58 |
}
|
| 56 | 59 |
|
| 57 | |
// New returns a new progress bar with defaults
|
| 58 | |
func New() *Progress {
|
|
60 |
// New creates new Progress instance, which will orchestrate bars rendering
|
|
61 |
// process. It acceepts context.Context, for cancellation.
|
|
62 |
// If you don't plan to cancel, it is safe to feed with nil
|
|
63 |
func New(ctx context.Context) *Progress {
|
|
64 |
if ctx == nil {
|
|
65 |
ctx = context.Background()
|
|
66 |
}
|
| 59 | 67 |
p := &Progress{
|
| 60 | 68 |
width: 70,
|
| 61 | |
op: make(chan *operation),
|
|
69 |
operationCh: make(chan *operation),
|
| 62 | 70 |
rrChangeReqCh: make(chan time.Duration),
|
| 63 | 71 |
outChangeReqCh: make(chan io.Writer),
|
| 64 | |
countReqCh: make(chan chan int),
|
| 65 | |
allDone: make(chan struct{}),
|
|
72 |
barCountReqCh: make(chan chan int),
|
|
73 |
done: make(chan struct{}),
|
| 66 | 74 |
wg: new(sync.WaitGroup),
|
|
75 |
ctx: ctx,
|
| 67 | 76 |
}
|
| 68 | 77 |
go p.server(cwriter.New(os.Stdout), time.NewTicker(rr*time.Millisecond))
|
| 69 | 78 |
return p
|
|
| 81 | 90 |
// SetOut sets underlying writer of progress. Default is os.Stdout
|
| 82 | 91 |
// pancis, if called on stopped Progress instance, i.e after Stop()
|
| 83 | 92 |
func (p *Progress) SetOut(w io.Writer) *Progress {
|
| 84 | |
if p.isAllDone() {
|
|
93 |
if p.isDone() {
|
| 85 | 94 |
panic(ErrCallAfterStop)
|
| 86 | 95 |
}
|
| 87 | 96 |
if w == nil {
|
|
| 94 | 103 |
// RefreshRate overrides default (30ms) refreshRate value
|
| 95 | 104 |
// pancis, if called on stopped Progress instance, i.e after Stop()
|
| 96 | 105 |
func (p *Progress) RefreshRate(d time.Duration) *Progress {
|
| 97 | |
if p.isAllDone() {
|
|
106 |
if p.isDone() {
|
| 98 | 107 |
panic(ErrCallAfterStop)
|
| 99 | 108 |
}
|
| 100 | 109 |
p.rrChangeReqCh <- d
|
| 101 | 110 |
return p
|
| 102 | 111 |
}
|
|
112 |
|
|
113 |
// func (p *Progress) WithContext(ctx context.Context) *Progress {
|
|
114 |
// if p.BarCount() > 0 {
|
|
115 |
// panic("cannot apply ctx after AddBar has been called")
|
|
116 |
// }
|
|
117 |
// if ctx == nil {
|
|
118 |
// panic("nil context")
|
|
119 |
// }
|
|
120 |
// p.ctx = ctx
|
|
121 |
// return p
|
|
122 |
// }
|
| 103 | 123 |
|
| 104 | 124 |
// WithSort sorts the bars, while redering
|
| 105 | 125 |
func (p *Progress) WithSort(sort SortType) *Progress {
|
|
| 109 | 129 |
|
| 110 | 130 |
// AddBar creates a new progress bar and adds to the container
|
| 111 | 131 |
// pancis, if called on stopped Progress instance, i.e after Stop()
|
| 112 | |
func (p *Progress) AddBar(total int) *Bar {
|
| 113 | |
if p.isAllDone() {
|
|
132 |
func (p *Progress) AddBar(total int64) *Bar {
|
|
133 |
if p.isDone() {
|
| 114 | 134 |
panic(ErrCallAfterStop)
|
| 115 | 135 |
}
|
| 116 | 136 |
result := make(chan bool)
|
| 117 | |
bar := newBar(total, p.width, p.wg)
|
| 118 | |
p.op <- &operation{opBarAdd, bar, result}
|
|
137 |
bar := newBar(p.ctx, p.wg, total, p.width)
|
|
138 |
p.operationCh <- &operation{opBarAdd, bar, result}
|
| 119 | 139 |
if <-result {
|
| 120 | 140 |
p.wg.Add(1)
|
| 121 | 141 |
}
|
|
| 125 | 145 |
// RemoveBar removes bar at any time
|
| 126 | 146 |
// pancis, if called on stopped Progress instance, i.e after Stop()
|
| 127 | 147 |
func (p *Progress) RemoveBar(b *Bar) bool {
|
| 128 | |
if p.isAllDone() {
|
|
148 |
if p.isDone() {
|
| 129 | 149 |
panic(ErrCallAfterStop)
|
| 130 | 150 |
}
|
| 131 | 151 |
result := make(chan bool)
|
| 132 | |
p.op <- &operation{opBarRemove, b, result}
|
|
152 |
p.operationCh <- &operation{opBarRemove, b, result}
|
| 133 | 153 |
return <-result
|
| 134 | 154 |
}
|
| 135 | 155 |
|
| 136 | |
// BarsCount returns bars count in the container
|
| 137 | |
// pancis, if called on stopped Progress instance, i.e after Stop()
|
| 138 | |
func (p *Progress) BarsCount() int {
|
| 139 | |
if p.isAllDone() {
|
|
156 |
// BarCount returns bars count in the container.
|
|
157 |
// Pancis if called on stopped Progress instance, i.e after Stop()
|
|
158 |
func (p *Progress) BarCount() int {
|
|
159 |
if p.isDone() {
|
| 140 | 160 |
panic(ErrCallAfterStop)
|
| 141 | 161 |
}
|
| 142 | 162 |
respCh := make(chan int)
|
| 143 | |
p.countReqCh <- respCh
|
|
163 |
p.barCountReqCh <- respCh
|
| 144 | 164 |
return <-respCh
|
| 145 | 165 |
}
|
| 146 | 166 |
|
| 147 | 167 |
// Stop waits for bars to finish rendering and stops the rendering goroutine
|
| 148 | 168 |
func (p *Progress) Stop() {
|
| 149 | |
if !p.isAllDone() {
|
| 150 | |
close(p.allDone)
|
| 151 | |
p.wg.Wait()
|
| 152 | |
close(p.op)
|
|
169 |
p.wg.Wait()
|
|
170 |
if !p.isDone() {
|
|
171 |
close(p.done)
|
|
172 |
close(p.operationCh)
|
| 153 | 173 |
}
|
| 154 | 174 |
}
|
| 155 | 175 |
|
|
| 161 | 181 |
case w := <-p.outChangeReqCh:
|
| 162 | 182 |
cw.Flush()
|
| 163 | 183 |
cw = cwriter.New(w)
|
| 164 | |
case op, ok := <-p.op:
|
|
184 |
case op, ok := <-p.operationCh:
|
| 165 | 185 |
if !ok {
|
| 166 | 186 |
t.Stop()
|
| 167 | |
for _, b := range bars {
|
| 168 | |
b.Stop()
|
| 169 | |
}
|
| 170 | 187 |
return
|
| 171 | 188 |
}
|
| 172 | 189 |
switch op.kind {
|
|
| 179 | 196 |
if b == op.bar {
|
| 180 | 197 |
bars = append(bars[:i], bars[i+1:]...)
|
| 181 | 198 |
ok = true
|
| 182 | |
b.Stop()
|
|
199 |
b.removeReqCh <- struct{}{}
|
| 183 | 200 |
break
|
| 184 | 201 |
}
|
| 185 | 202 |
}
|
| 186 | 203 |
op.result <- ok
|
| 187 | 204 |
}
|
| 188 | |
case respCh := <-p.countReqCh:
|
|
205 |
case respCh := <-p.barCountReqCh:
|
| 189 | 206 |
respCh <- len(bars)
|
| 190 | 207 |
case <-t.C:
|
| 191 | 208 |
width, _ := cwriter.TerminalWidth()
|
|
| 209 | 226 |
case d := <-p.rrChangeReqCh:
|
| 210 | 227 |
t.Stop()
|
| 211 | 228 |
t = time.NewTicker(d)
|
|
229 |
case <-p.ctx.Done():
|
|
230 |
t.Stop()
|
|
231 |
close(p.done)
|
|
232 |
return
|
| 212 | 233 |
}
|
| 213 | 234 |
}
|
| 214 | 235 |
}
|
| 215 | 236 |
|
| 216 | |
func (p *Progress) isAllDone() bool {
|
|
237 |
func (p *Progress) isDone() bool {
|
| 217 | 238 |
select {
|
| 218 | |
case <-p.allDone:
|
|
239 |
case <-p.done:
|
| 219 | 240 |
return true
|
| 220 | 241 |
default:
|
| 221 | 242 |
return false
|