package packer import ( "fmt" "io" "sync" "github.com/cheggaaa/pb" ) // ProgressBar allows to graphically display // a self refreshing progress bar. type ProgressBar interface { Start(total int64) Add(current int64) NewProxyReader(r io.Reader) (proxy io.Reader) Finish() } // StackableProgressBar is a progress bar that // allows to track multiple downloads at once. // Every call to Start increments a counter that // will display the number of current loadings. // Every call to Start will add total to an internal // total that is the total displayed. // First call to Start will start a goroutine // that is waiting for every download to be finished. // Last call to Finish triggers a cleanup. // When all active downloads are finished // StackableProgressBar will clean itself to a default // state. type StackableProgressBar struct { mtx sync.Mutex // locks in Start, Finish, Add & NewProxyReader Bar BasicProgressBar items int32 total int64 started bool ConfigProgressbarFN func(*pb.ProgressBar) } var _ ProgressBar = new(StackableProgressBar) func defaultProgressbarConfigFn(bar *pb.ProgressBar) { bar.SetUnits(pb.U_BYTES) } func (spb *StackableProgressBar) start() { bar := pb.New(0) if spb.ConfigProgressbarFN == nil { spb.ConfigProgressbarFN = defaultProgressbarConfigFn } spb.ConfigProgressbarFN(bar) bar.Start() spb.Bar.ProgressBar = bar spb.started = true } func (spb *StackableProgressBar) Start(total int64) { spb.mtx.Lock() spb.total += total spb.items++ if !spb.started { spb.start() } spb.Bar.SetTotal64(spb.total) spb.prefix() spb.mtx.Unlock() } func (spb *StackableProgressBar) Add(total int64) { spb.mtx.Lock() defer spb.mtx.Unlock() if spb.Bar.ProgressBar != nil { spb.Bar.Add(total) } } func (spb *StackableProgressBar) NewProxyReader(r io.Reader) io.Reader { spb.mtx.Lock() defer spb.mtx.Unlock() return spb.Bar.NewProxyReader(r) } func (spb *StackableProgressBar) prefix() { spb.Bar.ProgressBar.Prefix(fmt.Sprintf("%d items: ", spb.items)) } func (spb *StackableProgressBar) Finish() { spb.mtx.Lock() defer spb.mtx.Unlock() if spb.items > 0 { spb.items-- } if spb.items == 0 && spb.Bar.ProgressBar != nil { // slef cleanup spb.Bar.ProgressBar.Finish() spb.Bar.ProgressBar = nil spb.started = false spb.total = 0 return } } // BasicProgressBar is packer's basic progress bar. // Current implementation will always try to keep // itself at the bottom of a terminal. type BasicProgressBar struct { *pb.ProgressBar } var _ ProgressBar = new(BasicProgressBar) func (bpb *BasicProgressBar) Start(total int64) { bpb.SetTotal64(total) bpb.ProgressBar.Start() } func (bpb *BasicProgressBar) Add(current int64) { bpb.ProgressBar.Add64(current) } func (bpb *BasicProgressBar) NewProxyReader(r io.Reader) io.Reader { return &ProxyReader{ Reader: r, ProgressBar: bpb, } } func (bpb *BasicProgressBar) NewProxyReadCloser(r io.ReadCloser) io.ReadCloser { return &ProxyReader{ Reader: r, ProgressBar: bpb, } } // NoopProgressBar is a silent progress bar. type NoopProgressBar struct { } var _ ProgressBar = new(NoopProgressBar) func (npb *NoopProgressBar) Start(int64) {} func (npb *NoopProgressBar) Add(int64) {} func (npb *NoopProgressBar) Finish() {} func (npb *NoopProgressBar) NewProxyReader(r io.Reader) io.Reader { return r } func (npb *NoopProgressBar) NewProxyReadCloser(r io.ReadCloser) io.ReadCloser { return r } // ProxyReader implements io.ReadCloser but sends // count of read bytes to a progress bar type ProxyReader struct { io.Reader ProgressBar } func (r *ProxyReader) Read(p []byte) (n int, err error) { n, err = r.Reader.Read(p) r.ProgressBar.Add(int64(n)) return } // Close the reader if it implements io.Closer func (r *ProxyReader) Close() (err error) { if closer, ok := r.Reader.(io.Closer); ok { return closer.Close() } return }