first draft at self refreshing loading bar centralized/controlled by Ui

This commit is contained in:
Adrien Delorme 2018-09-04 17:57:21 +02:00
parent 3940cd9c7f
commit ddd96c513b
11 changed files with 211 additions and 58 deletions

View File

@ -9,6 +9,8 @@ import (
"runtime" "runtime"
"strings" "strings"
"time" "time"
"github.com/hashicorp/packer/packer"
) )
// PackerKeyEnv is used to specify the key interval (delay) between keystrokes // PackerKeyEnv is used to specify the key interval (delay) between keystrokes
@ -41,7 +43,7 @@ func SupportedProtocol(u *url.URL) bool {
// build a dummy NewDownloadClient since this is the only place that valid // build a dummy NewDownloadClient since this is the only place that valid
// protocols are actually exposed. // protocols are actually exposed.
cli := NewDownloadClient(&DownloadConfig{}, nil) cli := NewDownloadClient(&DownloadConfig{}, new(packer.NoopProgressBar))
// Iterate through each downloader to see if a protocol was found. // Iterate through each downloader to see if a protocol was found.
ok := false ok := false
@ -173,7 +175,7 @@ func FileExistsLocally(original string) bool {
// First create a dummy downloader so we can figure out which // First create a dummy downloader so we can figure out which
// protocol to use. // protocol to use.
cli := NewDownloadClient(&DownloadConfig{}, nil) cli := NewDownloadClient(&DownloadConfig{}, new(packer.NoopProgressBar))
d, ok := cli.config.DownloaderMap[u.Scheme] d, ok := cli.config.DownloaderMap[u.Scheme]
if !ok { if !ok {
return false return false

View File

@ -10,19 +10,17 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"io"
"log" "log"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
)
// imports related to each Downloader implementation "github.com/hashicorp/packer/packer" // imports related to each Downloader implementation
import (
"io"
"net/http"
"path/filepath"
) )
// DownloadConfig is the configuration given to instantiate a new // DownloadConfig is the configuration given to instantiate a new
@ -81,14 +79,9 @@ func HashForType(t string) hash.Hash {
// NewDownloadClient returns a new DownloadClient for the given // NewDownloadClient returns a new DownloadClient for the given
// configuration. // configuration.
func NewDownloadClient(c *DownloadConfig, bar ProgressBar) *DownloadClient { func NewDownloadClient(c *DownloadConfig, bar packer.ProgressBar) *DownloadClient {
const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */ const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */
// If bar is nil, then use a dummy progress bar that doesn't do anything
if bar == nil {
bar = GetDummyProgressBar()
}
// Create downloader map if it hasn't been specified already. // Create downloader map if it hasn't been specified already.
if c.DownloaderMap == nil { if c.DownloaderMap == nil {
c.DownloaderMap = map[string]Downloader{ c.DownloaderMap = map[string]Downloader{
@ -237,7 +230,7 @@ type HTTPDownloader struct {
total uint64 total uint64
userAgent string userAgent string
progress ProgressBar progress packer.ProgressBar
} }
func (d *HTTPDownloader) Cancel() { func (d *HTTPDownloader) Cancel() {
@ -331,9 +324,10 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
d.total = d.current + uint64(resp.ContentLength) d.total = d.current + uint64(resp.ContentLength)
bar := d.progress bar := d.progress
bar.SetTotal64(int64(d.total)) log.Printf("this %#v", bar)
progressBar := bar.Start() log.Printf("that")
progressBar.Set64(int64(d.current)) bar.Start(d.total)
bar.Set(d.current)
var buffer [4096]byte var buffer [4096]byte
for { for {
@ -343,7 +337,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
} }
d.current += uint64(n) d.current += uint64(n)
progressBar.Set64(int64(d.current)) bar.Set(d.current)
if _, werr := dst.Write(buffer[:n]); werr != nil { if _, werr := dst.Write(buffer[:n]); werr != nil {
return werr return werr
@ -353,7 +347,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
break break
} }
} }
progressBar.Finish() bar.Finish()
return nil return nil
} }
@ -374,7 +368,7 @@ type FileDownloader struct {
current uint64 current uint64
total uint64 total uint64
progress ProgressBar progress packer.ProgressBar
} }
func (d *FileDownloader) Progress() uint64 { func (d *FileDownloader) Progress() uint64 {
@ -466,9 +460,9 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
d.total = uint64(fi.Size()) d.total = uint64(fi.Size())
bar := d.progress bar := d.progress
bar.SetTotal64(int64(d.total))
progressBar := bar.Start() bar.Start(d.total)
progressBar.Set64(int64(d.current)) bar.Set(d.current)
// no bufferSize specified, so copy synchronously. // no bufferSize specified, so copy synchronously.
if d.bufferSize == nil { if d.bufferSize == nil {
@ -477,7 +471,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
d.active = false d.active = false
d.current += uint64(n) d.current += uint64(n)
progressBar.Set64(int64(d.current)) bar.Set(d.current)
// use a goro in case someone else wants to enable cancel/resume // use a goro in case someone else wants to enable cancel/resume
} else { } else {
@ -490,7 +484,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
} }
d.current += uint64(n) d.current += uint64(n)
progressBar.Set64(int64(d.current)) bar.Set(d.current)
} }
d.active = false d.active = false
e <- err e <- err
@ -499,7 +493,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
// ...and we spin until it's done // ...and we spin until it's done
err = <-errch err = <-errch
} }
progressBar.Finish() bar.Finish()
f.Close() f.Close()
return err return err
} }
@ -513,7 +507,7 @@ type SMBDownloader struct {
current uint64 current uint64
total uint64 total uint64
progress ProgressBar progress packer.ProgressBar
} }
func (d *SMBDownloader) Progress() uint64 { func (d *SMBDownloader) Progress() uint64 {
@ -587,9 +581,8 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
d.total = uint64(fi.Size()) d.total = uint64(fi.Size())
bar := d.progress bar := d.progress
bar.SetTotal64(int64(d.total))
progressBar := bar.Start() bar.Start(d.current)
progressBar.Set64(int64(d.current))
// no bufferSize specified, so copy synchronously. // no bufferSize specified, so copy synchronously.
if d.bufferSize == nil { if d.bufferSize == nil {
@ -598,7 +591,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
d.active = false d.active = false
d.current += uint64(n) d.current += uint64(n)
progressBar.Set64(int64(d.current)) bar.Set(d.current)
// use a goro in case someone else wants to enable cancel/resume // use a goro in case someone else wants to enable cancel/resume
} else { } else {
@ -611,7 +604,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
} }
d.current += uint64(n) d.current += uint64(n)
progressBar.Set64(int64(d.current)) bar.Set(d.current)
} }
d.active = false d.active = false
e <- err e <- err
@ -620,7 +613,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
// ...and as usual we spin until it's done // ...and as usual we spin until it's done
err = <-errch err = <-errch
} }
progressBar.Finish() bar.Finish()
f.Close() f.Close()
return err return err
} }

View File

@ -12,6 +12,8 @@ import (
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/packer/packer"
) )
func TestDownloadClientVerifyChecksum(t *testing.T) { func TestDownloadClientVerifyChecksum(t *testing.T) {
@ -36,7 +38,7 @@ func TestDownloadClientVerifyChecksum(t *testing.T) {
Checksum: checksum, Checksum: checksum,
} }
d := NewDownloadClient(config, nil) d := NewDownloadClient(config, new(packer.NoopProgressBar))
result, err := d.VerifyChecksum(tf.Name()) result, err := d.VerifyChecksum(tf.Name())
if err != nil { if err != nil {
t.Fatalf("Verify err: %s", err) t.Fatalf("Verify err: %s", err)
@ -59,7 +61,7 @@ func TestDownloadClient_basic(t *testing.T) {
Url: ts.URL + "/basic.txt", Url: ts.URL + "/basic.txt",
TargetPath: tf.Name(), TargetPath: tf.Name(),
CopyFile: true, CopyFile: true,
}, nil) }, new(packer.NoopProgressBar))
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
@ -95,7 +97,7 @@ func TestDownloadClient_checksumBad(t *testing.T) {
Hash: HashForType("md5"), Hash: HashForType("md5"),
Checksum: checksum, Checksum: checksum,
CopyFile: true, CopyFile: true,
}, nil) }, new(packer.NoopProgressBar))
if _, err := client.Get(); err == nil { if _, err := client.Get(); err == nil {
t.Fatal("should error") t.Fatal("should error")
@ -121,7 +123,7 @@ func TestDownloadClient_checksumGood(t *testing.T) {
Hash: HashForType("md5"), Hash: HashForType("md5"),
Checksum: checksum, Checksum: checksum,
CopyFile: true, CopyFile: true,
}, nil) }, new(packer.NoopProgressBar))
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
@ -153,7 +155,7 @@ func TestDownloadClient_checksumNoDownload(t *testing.T) {
Hash: HashForType("md5"), Hash: HashForType("md5"),
Checksum: checksum, Checksum: checksum,
CopyFile: true, CopyFile: true,
}, nil) }, new(packer.NoopProgressBar))
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@ -183,7 +185,7 @@ func TestDownloadClient_notFound(t *testing.T) {
client := NewDownloadClient(&DownloadConfig{ client := NewDownloadClient(&DownloadConfig{
Url: ts.URL + "/not-found.txt", Url: ts.URL + "/not-found.txt",
TargetPath: tf.Name(), TargetPath: tf.Name(),
}, nil) }, new(packer.NoopProgressBar))
if _, err := client.Get(); err == nil { if _, err := client.Get(); err == nil {
t.Fatal("should error") t.Fatal("should error")
@ -211,7 +213,7 @@ func TestDownloadClient_resume(t *testing.T) {
Url: ts.URL, Url: ts.URL,
TargetPath: tf.Name(), TargetPath: tf.Name(),
CopyFile: true, CopyFile: true,
}, nil) }, new(packer.NoopProgressBar))
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
@ -273,7 +275,7 @@ func TestDownloadClient_usesDefaultUserAgent(t *testing.T) {
CopyFile: true, CopyFile: true,
} }
client := NewDownloadClient(config, nil) client := NewDownloadClient(config, new(packer.NoopProgressBar))
_, err = client.Get() _, err = client.Get()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -306,7 +308,7 @@ func TestDownloadClient_setsUserAgent(t *testing.T) {
CopyFile: true, CopyFile: true,
} }
client := NewDownloadClient(config, nil) client := NewDownloadClient(config, new(packer.NoopProgressBar))
_, err = client.Get() _, err = client.Get()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -405,7 +407,7 @@ func TestDownloadFileUrl(t *testing.T) {
CopyFile: false, CopyFile: false,
} }
client := NewDownloadClient(config, nil) client := NewDownloadClient(config, new(packer.NoopProgressBar))
// Verify that we fail to match the checksum // Verify that we fail to match the checksum
_, err = client.Get() _, err = client.Get()
@ -436,7 +438,7 @@ func SimulateFileUriDownload(t *testing.T, uri string) (string, error) {
} }
// go go go // go go go
client := NewDownloadClient(config, nil) client := NewDownloadClient(config, new(packer.NoopProgressBar))
path, err := client.Get() path, err := client.Get()
// ignore any non-important checksum errors if it's not a unc path // ignore any non-important checksum errors if it's not a unc path

View File

@ -2,13 +2,14 @@ package common
import ( import (
"fmt" "fmt"
"log"
"reflect"
"time"
"github.com/cheggaaa/pb" "github.com/cheggaaa/pb"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/rpc" "github.com/hashicorp/packer/packer/rpc"
"log"
"reflect"
"time"
) )
// This is the arrow from packer/ui.go -> TargetedUI.prefixLines // This is the arrow from packer/ui.go -> TargetedUI.prefixLines

View File

@ -64,7 +64,7 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste
ui.Say(fmt.Sprintf("Retrieving %s", s.Description)) ui.Say(fmt.Sprintf("Retrieving %s", s.Description))
// Get a progress bar from the ui so we can hand it off to the download client // Get a progress bar from the ui so we can hand it off to the download client
bar := GetProgressBar(ui, GetPackerConfigFromStateBag(state)) bar := ui.ProgressBar()
// First try to use any already downloaded file // First try to use any already downloaded file
// If it fails, proceed to regular download logic // If it fails, proceed to regular download logic
@ -144,7 +144,8 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
// Get a progress bar and hand it off to the download client // Get a progress bar and hand it off to the download client
bar := GetProgressBar(ui, GetPackerConfigFromStateBag(state)) bar := ui.ProgressBar()
log.Printf("new progress bar: %#v, %t", bar, bar == nil)
// Create download client with config and progress bar // Create download client with config and progress bar
download := NewDownloadClient(config, bar) download := NewDownloadClient(config, bar)

39
packer/progressbar.go Normal file
View File

@ -0,0 +1,39 @@
package packer
import (
"github.com/cheggaaa/pb"
)
// ProgressBar allows to graphically display
// a self refreshing progress bar.
// No-op When in machine readable mode.
type ProgressBar interface {
Start(total uint64)
Set(current uint64)
Finish()
}
type BasicProgressBar struct {
*pb.ProgressBar
}
func (bpb *BasicProgressBar) Start(total uint64) {
bpb.SetTotal64(int64(total))
bpb.ProgressBar.Start()
}
func (bpb *BasicProgressBar) Set(current uint64) {
bpb.ProgressBar.Set64(int64(current))
}
var _ ProgressBar = new(BasicProgressBar)
// NoopProgressBar is a silent progress bar
type NoopProgressBar struct {
}
func (bpb *NoopProgressBar) Start(_ uint64) {}
func (bpb *NoopProgressBar) Set(_ uint64) {}
func (bpb *NoopProgressBar) Finish() {}
var _ ProgressBar = new(NoopProgressBar)

View File

@ -115,6 +115,7 @@ func (s *Server) RegisterProvisioner(p packer.Provisioner) {
func (s *Server) RegisterUi(ui packer.Ui) { func (s *Server) RegisterUi(ui packer.Ui) {
s.server.RegisterName(DefaultUiEndpoint, &UiServer{ s.server.RegisterName(DefaultUiEndpoint, &UiServer{
ui: ui, ui: ui,
register: s.server.RegisterName,
}) })
} }

View File

@ -2,6 +2,7 @@ package rpc
import ( import (
"log" "log"
"math/rand"
"net/rpc" "net/rpc"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -14,10 +15,13 @@ type Ui struct {
endpoint string endpoint string
} }
var _ packer.Ui = new(Ui)
// UiServer wraps a packer.Ui implementation and makes it exportable // UiServer wraps a packer.Ui implementation and makes it exportable
// as part of a Golang RPC server. // as part of a Golang RPC server.
type UiServer struct { type UiServer struct {
ui packer.Ui ui packer.Ui
register func(name string, rcvr interface{}) error
} }
// The arguments sent to Ui.Machine // The arguments sent to Ui.Machine
@ -60,6 +64,38 @@ func (u *Ui) Say(message string) {
} }
} }
func (u *Ui) ProgressBar() packer.ProgressBar {
var callMeMaybe string
if err := u.client.Call("Ui.ProgressBar", nil, &callMeMaybe); err != nil {
log.Printf("Error in Ui RPC call: %s", err)
return new(packer.NoopProgressBar)
}
return &RemoteProgressBarClient{
id: callMeMaybe,
client: u.client,
}
}
type RemoteProgressBarClient struct {
id string
client *rpc.Client
}
var _ packer.ProgressBar = new(RemoteProgressBarClient)
func (pb *RemoteProgressBarClient) Start(total uint64) {
pb.client.Call(pb.id+".Start", total, new(interface{}))
}
func (pb *RemoteProgressBarClient) Set(current uint64) {
pb.client.Call(pb.id+".Set", current, new(interface{}))
}
func (pb *RemoteProgressBarClient) Finish() {
pb.client.Call(pb.id+".Finish", nil, new(interface{}))
}
func (u *UiServer) Ask(query string, reply *string) (err error) { func (u *UiServer) Ask(query string, reply *string) (err error) {
*reply, err = u.ui.Ask(query) *reply, err = u.ui.Ask(query)
return return
@ -91,3 +127,47 @@ func (u *UiServer) Say(message *string, reply *interface{}) error {
*reply = nil *reply = nil
return nil return nil
} }
func RandStringBytes(n int) string {
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func (u *UiServer) ProgressBar(_ *string, reply *interface{}) error {
bar := u.ui.ProgressBar()
callbackName := RandStringBytes(6)
log.Printf("registering progressbar %s", callbackName)
err := u.register(callbackName, &RemoteProgressBarServer{bar})
if err != nil {
log.Printf("failed to register a new progress bar rpc server, %s", err)
return err
}
*reply = callbackName
return nil
}
type RemoteProgressBarServer struct {
pb packer.ProgressBar
}
func (pb *RemoteProgressBarServer) Finish(_ string, _ *interface{}) error {
pb.pb.Finish()
return nil
}
func (pb *RemoteProgressBarServer) Start(total uint64, _ *interface{}) error {
pb.pb.Start(total)
return nil
}
func (pb *RemoteProgressBarServer) Set(current uint64, _ *interface{}) error {
pb.pb.Set(current)
return nil
}

View File

@ -15,6 +15,8 @@ import (
"syscall" "syscall"
"time" "time"
"unicode" "unicode"
"github.com/cheggaaa/pb"
) )
type UiColor uint type UiColor uint
@ -37,6 +39,7 @@ type Ui interface {
Message(string) Message(string)
Error(string) Error(string)
Machine(string, ...string) Machine(string, ...string)
ProgressBar() ProgressBar
} }
// ColoredUi is a UI that is colored using terminal colors. // ColoredUi is a UI that is colored using terminal colors.
@ -46,6 +49,8 @@ type ColoredUi struct {
Ui Ui Ui Ui
} }
var _ Ui = new(ColoredUi)
// TargetedUI is a UI that wraps another UI implementation and modifies // TargetedUI is a UI that wraps another UI implementation and modifies
// the output to indicate a specific target. Specifically, all Say output // the output to indicate a specific target. Specifically, all Say output
// is prefixed with the target name. Message output is not prefixed but // is prefixed with the target name. Message output is not prefixed but
@ -56,6 +61,8 @@ type TargetedUI struct {
Ui Ui Ui Ui
} }
var _ Ui = new(TargetedUI)
// The BasicUI is a UI that reads and writes from a standard Go reader // The BasicUI is a UI that reads and writes from a standard Go reader
// and writer. It is safe to be called from multiple goroutines. Machine // and writer. It is safe to be called from multiple goroutines. Machine
// readable output is simply logged for this UI. // readable output is simply logged for this UI.
@ -68,12 +75,21 @@ type BasicUi struct {
scanner *bufio.Scanner scanner *bufio.Scanner
} }
var _ Ui = new(BasicUi)
func (bu *BasicUi) ProgressBar() ProgressBar {
log.Printf("hehey !")
return &BasicProgressBar{ProgressBar: pb.New(0)}
}
// MachineReadableUi is a UI that only outputs machine-readable output // MachineReadableUi is a UI that only outputs machine-readable output
// to the given Writer. // to the given Writer.
type MachineReadableUi struct { type MachineReadableUi struct {
Writer io.Writer Writer io.Writer
} }
var _ Ui = new(MachineReadableUi)
func (u *ColoredUi) Ask(query string) (string, error) { func (u *ColoredUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.colorize(query, u.Color, true)) return u.Ui.Ask(u.colorize(query, u.Color, true))
} }
@ -100,6 +116,10 @@ func (u *ColoredUi) Machine(t string, args ...string) {
u.Ui.Machine(t, args...) u.Ui.Machine(t, args...)
} }
func (u *ColoredUi) ProgressBar() ProgressBar {
return u.Ui.ProgressBar() //TODO(adrien): color me
}
func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string { func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
if !u.supportsColors() { if !u.supportsColors() {
return message return message
@ -153,6 +173,10 @@ func (u *TargetedUI) Machine(t string, args ...string) {
u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...) u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...)
} }
func (u *TargetedUI) ProgressBar() ProgressBar {
return u.Ui.ProgressBar()
}
func (u *TargetedUI) prefixLines(arrow bool, message string) string { func (u *TargetedUI) prefixLines(arrow bool, message string) string {
arrowText := "==>" arrowText := "==>"
if !arrow { if !arrow {
@ -305,3 +329,8 @@ func (u *MachineReadableUi) Machine(category string, args ...string) {
} }
} }
} }
func (u *MachineReadableUi) ProgressBar() ProgressBar {
panic("MachineReadableUi")
return nil // no-op
}

View File

@ -612,3 +612,8 @@ func (ui *Ui) Machine(t string, args ...string) {
ui.ui.Machine(t, args...) ui.ui.Machine(t, args...)
<-ui.sem <-ui.sem
} }
func (ui *Ui) ProgressBar() packer.ProgressBar {
panic("to implement")
return nil // TODO
}

View File

@ -127,12 +127,12 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator)
defer f.Close() defer f.Close()
// Get a default progress bar // Get a default progress bar
pb := common.GetProgressBar(ui, &p.config.PackerConfig) pb := packer.NoopProgressBar{}
bar := pb.Start() pb.Start(0)
defer bar.Finish() defer pb.Finish()
// Create MultiWriter for the current progress // Create MultiWriter for the current progress
pf := io.MultiWriter(f, bar) pf := io.MultiWriter(f)
// Download the file // Download the file
if err = comm.Download(src, pf); err != nil { if err = comm.Download(src, pf); err != nil {
@ -176,8 +176,8 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er
} }
// Get a default progress bar // Get a default progress bar
pb := common.GetProgressBar(ui, &p.config.PackerConfig) bar := ui.ProgressBar()
bar := pb.Start() bar.Start(0)
defer bar.Finish() defer bar.Finish()
// Create ProxyReader for the current progress // Create ProxyReader for the current progress