Merge pull request #5851 from arizvisa/multi-proto-progressbar

Add progress-bar to common/step_download.go
This commit is contained in:
Megan Marsh 2018-08-21 15:08:22 -07:00 committed by GitHub
commit c1b50d365b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1830 additions and 41 deletions

View File

@ -54,7 +54,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{}) cli := NewDownloadClient(&DownloadConfig{}, nil)
// 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
@ -186,7 +186,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{}) cli := NewDownloadClient(&DownloadConfig{}, nil)
d, ok := cli.config.DownloaderMap[u.Scheme] d, ok := cli.config.DownloaderMap[u.Scheme]
if !ok { if !ok {
return false return false

View File

@ -81,22 +81,28 @@ 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) *DownloadClient { func NewDownloadClient(c *DownloadConfig, bar 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{
"file": &FileDownloader{bufferSize: nil}, "file": &FileDownloader{progress: bar, bufferSize: nil},
"http": &HTTPDownloader{userAgent: c.UserAgent}, "http": &HTTPDownloader{progress: bar, userAgent: c.UserAgent},
"https": &HTTPDownloader{userAgent: c.UserAgent}, "https": &HTTPDownloader{progress: bar, userAgent: c.UserAgent},
"smb": &SMBDownloader{bufferSize: nil}, "smb": &SMBDownloader{progress: bar, bufferSize: nil},
} }
} }
return &DownloadClient{config: c} return &DownloadClient{config: c}
} }
// A downloader implements the ability to transfer, cancel, or resume a file. // A downloader implements the ability to transfer a file, and cancel or resume
// it.
type Downloader interface { type Downloader interface {
Resume() Resume()
Cancel() Cancel()
@ -205,14 +211,6 @@ func (d *DownloadClient) Get() (string, error) {
return finalPath, err return finalPath, err
} }
func (d *DownloadClient) PercentProgress() int {
if d.downloader == nil {
return -1
}
return int((float64(d.downloader.Progress()) / float64(d.downloader.Total())) * 100)
}
// VerifyChecksum tests that the path matches the checksum for the // VerifyChecksum tests that the path matches the checksum for the
// download. // download.
func (d *DownloadClient) VerifyChecksum(path string) (bool, error) { func (d *DownloadClient) VerifyChecksum(path string) (bool, error) {
@ -238,6 +236,8 @@ type HTTPDownloader struct {
current uint64 current uint64
total uint64 total uint64
userAgent string userAgent string
progress ProgressBar
} }
func (d *HTTPDownloader) Cancel() { func (d *HTTPDownloader) Cancel() {
@ -330,6 +330,11 @@ 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.SetTotal64(int64(d.total))
progressBar := bar.Start()
progressBar.Set64(int64(d.current))
var buffer [4096]byte var buffer [4096]byte
for { for {
n, err := resp.Body.Read(buffer[:]) n, err := resp.Body.Read(buffer[:])
@ -338,6 +343,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))
if _, werr := dst.Write(buffer[:n]); werr != nil { if _, werr := dst.Write(buffer[:n]); werr != nil {
return werr return werr
@ -347,6 +353,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
break break
} }
} }
progressBar.Finish()
return nil return nil
} }
@ -366,6 +373,8 @@ type FileDownloader struct {
active bool active bool
current uint64 current uint64
total uint64 total uint64
progress ProgressBar
} }
func (d *FileDownloader) Progress() uint64 { func (d *FileDownloader) Progress() uint64 {
@ -456,6 +465,11 @@ 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.SetTotal64(int64(d.total))
progressBar := bar.Start()
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 {
var n int64 var n int64
@ -463,6 +477,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))
// 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 {
@ -475,6 +490,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))
} }
d.active = false d.active = false
e <- err e <- err
@ -483,6 +499,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()
f.Close() f.Close()
return err return err
} }
@ -495,6 +512,8 @@ type SMBDownloader struct {
active bool active bool
current uint64 current uint64
total uint64 total uint64
progress ProgressBar
} }
func (d *SMBDownloader) Progress() uint64 { func (d *SMBDownloader) Progress() uint64 {
@ -567,6 +586,11 @@ 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.SetTotal64(int64(d.total))
progressBar := bar.Start()
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 {
var n int64 var n int64
@ -574,6 +598,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))
// 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 {
@ -586,6 +611,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))
} }
d.active = false d.active = false
e <- err e <- err
@ -594,6 +620,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()
f.Close() f.Close()
return err return err
} }

View File

@ -36,7 +36,7 @@ func TestDownloadClientVerifyChecksum(t *testing.T) {
Checksum: checksum, Checksum: checksum,
} }
d := NewDownloadClient(config) d := NewDownloadClient(config, nil)
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 +59,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)
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
@ -95,7 +95,7 @@ func TestDownloadClient_checksumBad(t *testing.T) {
Hash: HashForType("md5"), Hash: HashForType("md5"),
Checksum: checksum, Checksum: checksum,
CopyFile: true, CopyFile: true,
}) }, nil)
if _, err := client.Get(); err == nil { if _, err := client.Get(); err == nil {
t.Fatal("should error") t.Fatal("should error")
@ -121,7 +121,7 @@ func TestDownloadClient_checksumGood(t *testing.T) {
Hash: HashForType("md5"), Hash: HashForType("md5"),
Checksum: checksum, Checksum: checksum,
CopyFile: true, CopyFile: true,
}) }, nil)
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
@ -153,7 +153,7 @@ func TestDownloadClient_checksumNoDownload(t *testing.T) {
Hash: HashForType("md5"), Hash: HashForType("md5"),
Checksum: checksum, Checksum: checksum,
CopyFile: true, CopyFile: true,
}) }, nil)
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 +183,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)
if _, err := client.Get(); err == nil { if _, err := client.Get(); err == nil {
t.Fatal("should error") t.Fatal("should error")
@ -211,7 +211,7 @@ func TestDownloadClient_resume(t *testing.T) {
Url: ts.URL, Url: ts.URL,
TargetPath: tf.Name(), TargetPath: tf.Name(),
CopyFile: true, CopyFile: true,
}) }, nil)
path, err := client.Get() path, err := client.Get()
if err != nil { if err != nil {
@ -273,7 +273,7 @@ func TestDownloadClient_usesDefaultUserAgent(t *testing.T) {
CopyFile: true, CopyFile: true,
} }
client := NewDownloadClient(config) client := NewDownloadClient(config, nil)
_, err = client.Get() _, err = client.Get()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -306,7 +306,7 @@ func TestDownloadClient_setsUserAgent(t *testing.T) {
CopyFile: true, CopyFile: true,
} }
client := NewDownloadClient(config) client := NewDownloadClient(config, nil)
_, err = client.Get() _, err = client.Get()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -405,7 +405,7 @@ func TestDownloadFileUrl(t *testing.T) {
CopyFile: false, CopyFile: false,
} }
client := NewDownloadClient(config) client := NewDownloadClient(config, nil)
// Verify that we fail to match the checksum // Verify that we fail to match the checksum
_, err = client.Get() _, err = client.Get()
@ -436,7 +436,7 @@ func SimulateFileUriDownload(t *testing.T, uri string) (string, error) {
} }
// go go go // go go go
client := NewDownloadClient(config) client := NewDownloadClient(config, nil)
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

145
common/progress.go Normal file
View File

@ -0,0 +1,145 @@
package common
import (
"fmt"
"github.com/cheggaaa/pb"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/rpc"
"log"
"reflect"
"time"
)
// This is the arrow from packer/ui.go -> TargetedUI.prefixLines
const targetedUIArrowText = "==>"
// The ProgressBar interface is used for abstracting cheggaaa's progress-
// bar, or any other progress bar. If a UI does not support a progress-
// bar, then it must return a null progress bar.
const (
DefaultProgressBarWidth = 80
)
type ProgressBar = *pb.ProgressBar
// Figure out the terminal dimensions and use it to calculate the available rendering space
func calculateProgressBarWidth(length int) int {
// If the UI's width is signed, then this is an interface that doesn't really benefit from a progress bar
if length < 0 {
log.Println("Refusing to render progress-bar for unsupported UI.")
return length
}
// Figure out the terminal width if possible
width, _, err := GetTerminalDimensions()
if err != nil {
newerr := fmt.Errorf("Unable to determine terminal dimensions: %v", err)
log.Printf("Using default width (%d) for progress-bar due to error: %s", DefaultProgressBarWidth, newerr)
return DefaultProgressBarWidth
}
// If the terminal width is smaller than the requested length, then complain
if width < length {
newerr := fmt.Errorf("Terminal width (%d) is smaller than UI message width (%d).", width, length)
log.Printf("Using default width (%d) for progress-bar due to error: %s", DefaultProgressBarWidth, newerr)
return DefaultProgressBarWidth
}
// Otherwise subtract the minimum length and return it
return width - length
}
// Get a progress bar with the default appearance
func GetDefaultProgressBar() ProgressBar {
bar := pb.New64(0)
bar.ShowPercent = true
bar.ShowCounters = true
bar.ShowSpeed = false
bar.ShowBar = true
bar.ShowTimeLeft = false
bar.ShowFinalTime = false
bar.SetUnits(pb.U_BYTES)
bar.Format("[=>-]")
bar.SetRefreshRate(1 * time.Second)
return bar
}
// Return a dummy progress bar that doesn't do anything
func GetDummyProgressBar() ProgressBar {
bar := pb.New64(0)
bar.ManualUpdate = true
return bar
}
// Given a packer.Ui, calculate the number of characters that a packer.Ui will
// prefix a message with. Then we can use this to calculate the progress bar's width.
func calculateUiPrefixLength(ui packer.Ui) int {
var recursiveCalculateUiPrefixLength func(packer.Ui, int) int
// Define a recursive closure that traverses through all the known packer.Ui types
// and aggregates the length of the message prefix from each particular type
recursiveCalculateUiPrefixLength = func(ui packer.Ui, agg int) int {
switch ui.(type) {
case *packer.ColoredUi:
// packer.ColoredUi is simply a wrapper around .Ui
u := ui.(*packer.ColoredUi)
return recursiveCalculateUiPrefixLength(u.Ui, agg)
case *packer.TargetedUI:
// A TargetedUI adds the .Target and an arrow by default
u := ui.(*packer.TargetedUI)
res := fmt.Sprintf("%s %s: ", targetedUIArrowText, u.Target)
return recursiveCalculateUiPrefixLength(u.Ui, agg+len(res))
case *packer.BasicUi:
// The standard BasicUi appends only a newline
return agg + len("\n")
// packer.rpc.Ui returns 0 here to trigger the hack described later
case *rpc.Ui:
return 0
case *packer.MachineReadableUi:
// MachineReadableUi doesn't emit anything...like at all
return 0
}
log.Printf("Calculating the message prefix length for packer.Ui type (%T) is not implemented. Using the current aggregated length of %d.", ui, agg)
return agg
}
return recursiveCalculateUiPrefixLength(ui, 0)
}
func GetPackerConfigFromStateBag(state multistep.StateBag) *PackerConfig {
config := state.Get("config")
rConfig := reflect.Indirect(reflect.ValueOf(config))
iPackerConfig := rConfig.FieldByName("PackerConfig").Interface()
packerConfig := iPackerConfig.(PackerConfig)
return &packerConfig
}
func GetProgressBar(ui packer.Ui, config *PackerConfig) ProgressBar {
// Figure out the prefix length by quering the UI
uiPrefixLength := calculateUiPrefixLength(ui)
// hack to deal with packer.rpc.Ui courtesy of @Swampdragons
if _, ok := ui.(*rpc.Ui); uiPrefixLength == 0 && config != nil && ok {
res := fmt.Sprintf("%s %s: \n", targetedUIArrowText, config.PackerBuildName)
uiPrefixLength = len(res)
}
// Now we can use the prefix length to calculate the progress bar width
width := calculateProgressBarWidth(uiPrefixLength)
log.Printf("ProgressBar: Using progress bar width: %d\n", width)
// Get a default progress bar and set some output defaults
bar := GetDefaultProgressBar()
bar.SetWidth(width)
bar.Callback = func(message string) {
ui.Message(message)
}
return bar
}

142
common/progress_test.go Normal file
View File

@ -0,0 +1,142 @@
package common
import (
"github.com/hashicorp/packer/packer"
"testing"
)
// test packer.Ui implementation to verify that progress bar is being written
type testProgressBarUi struct {
messageCalled bool
messageMessage string
}
func (u *testProgressBarUi) Say(string) {}
func (u *testProgressBarUi) Error(string) {}
func (u *testProgressBarUi) Machine(string, ...string) {}
func (u *testProgressBarUi) Ask(string) (string, error) {
return "", nil
}
func (u *testProgressBarUi) Message(message string) {
u.messageCalled = true
u.messageMessage = message
}
// ..and now let's begin our actual tests
func TestCalculateUiPrefixLength_Unknown(t *testing.T) {
ui := &testProgressBarUi{}
expected := 0
if res := calculateUiPrefixLength(ui); res != expected {
t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected)
}
}
func TestCalculateUiPrefixLength_BasicUi(t *testing.T) {
ui := &packer.BasicUi{}
expected := 1
if res := calculateUiPrefixLength(ui); res != expected {
t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected)
}
}
func TestCalculateUiPrefixLength_TargetedUI(t *testing.T) {
ui := &packer.TargetedUI{}
ui.Target = "TestTarget"
arrowText := "==>"
expected := len(arrowText + " " + ui.Target + ": ")
if res := calculateUiPrefixLength(ui); res != expected {
t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected)
}
}
func TestCalculateUiPrefixLength_TargetedUIWrappingBasicUi(t *testing.T) {
ui := &packer.TargetedUI{}
ui.Target = "TestTarget"
ui.Ui = &packer.BasicUi{}
arrowText := "==>"
expected := len(arrowText + " " + ui.Target + ": " + "\n")
if res := calculateUiPrefixLength(ui); res != expected {
t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected)
}
}
func TestCalculateUiPrefixLength_TargetedUIWrappingMachineUi(t *testing.T) {
ui := &packer.TargetedUI{}
ui.Target = "TestTarget"
ui.Ui = &packer.MachineReadableUi{}
expected := 0
if res := calculateUiPrefixLength(ui); res != expected {
t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected)
}
}
func TestDefaultProgressBar(t *testing.T) {
var callbackCalled bool
// Initialize the default progress bar
bar := GetDefaultProgressBar()
bar.Callback = func(state string) {
callbackCalled = true
t.Logf("TestDefaultProgressBar emitted %#v", state)
}
bar.SetTotal64(1)
// Set it off
progressBar := bar.Start()
progressBar.Set64(1)
// Check to see that the callback was hit
if !callbackCalled {
t.Fatalf("TestDefaultProgressBar.Callback should be called")
}
}
func TestDummyProgressBar(t *testing.T) {
var callbackCalled bool
// Initialize the dummy progress bar
bar := GetDummyProgressBar()
bar.Callback = func(state string) {
callbackCalled = true
t.Logf("TestDummyProgressBar emitted %#v", state)
}
bar.SetTotal64(1)
// Now we can go
progressBar := bar.Start()
progressBar.Set64(1)
// Check to see that the callback was hit
if callbackCalled {
t.Fatalf("TestDummyProgressBar.Callback should not be called")
}
}
func TestUiProgressBar(t *testing.T) {
ui := &testProgressBarUi{}
// Initialize the Ui progress bar
bar := GetProgressBar(ui, nil)
bar.SetTotal64(1)
// Ensure that callback has been set to something
if bar.Callback == nil {
t.Fatalf("TestUiProgressBar.Callback should be initialized")
}
// Now we can go
progressBar := bar.Start()
progressBar.Set64(1)
// Check to see that the callback was hit
if !ui.messageCalled {
t.Fatalf("TestUiProgressBar.messageCalled should be called")
}
t.Logf("TestUiProgressBar emitted %#v", ui.messageMessage)
}

View File

@ -63,6 +63,9 @@ 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
bar := GetProgressBar(ui, GetPackerConfigFromStateBag(state))
// 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
@ -96,7 +99,7 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste
} }
downloadConfigs[i] = config downloadConfigs[i] = config
if match, _ := NewDownloadClient(config).VerifyChecksum(config.TargetPath); match { if match, _ := NewDownloadClient(config, bar).VerifyChecksum(config.TargetPath); match {
ui.Message(fmt.Sprintf("Found already downloaded, initial checksum matched, no download needed: %s", url)) ui.Message(fmt.Sprintf("Found already downloaded, initial checksum matched, no download needed: %s", url))
finalPath = config.TargetPath finalPath = config.TargetPath
break break
@ -139,7 +142,12 @@ func (s *StepDownload) Cleanup(multistep.StateBag) {}
func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) { func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) {
var path string var path string
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
download := NewDownloadClient(config)
// Get a progress bar and hand it off to the download client
bar := GetProgressBar(ui, GetPackerConfigFromStateBag(state))
// Create download client with config and progress bar
download := NewDownloadClient(config, bar)
downloadCompleteCh := make(chan error, 1) downloadCompleteCh := make(chan error, 1)
go func() { go func() {
@ -148,12 +156,11 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag
downloadCompleteCh <- err downloadCompleteCh <- err
}() }()
progressTicker := time.NewTicker(5 * time.Second)
defer progressTicker.Stop()
for { for {
select { select {
case err := <-downloadCompleteCh: case err := <-downloadCompleteCh:
bar.Finish()
if err != nil { if err != nil {
return "", err, true return "", err, true
} }
@ -164,13 +171,10 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag
} }
return path, nil, true return path, nil, true
case <-progressTicker.C:
progress := download.PercentProgress()
if progress >= 0 {
ui.Message(fmt.Sprintf("Download progress: %d%%", progress))
}
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok { if _, ok := state.GetOk(multistep.StateCancelled); ok {
bar.Finish()
ui.Say("Interrupt received. Cancelling download...") ui.Say("Interrupt received. Cancelling download...")
return "", nil, false return "", nil, false
} }

6
common/terminal.go Normal file
View File

@ -0,0 +1,6 @@
package common
// call into one of the platform-specific implementations to get the current terminal dimensions
func GetTerminalDimensions() (width, height int, err error) {
return platformGetTerminalDimensions()
}

38
common/terminal_posix.go Normal file
View File

@ -0,0 +1,38 @@
// +build !windows
package common
// Imports for determining terminal information across platforms
import (
"golang.org/x/sys/unix"
"os"
)
// posix api
func platformGetTerminalDimensions() (width, height int, err error) {
// grab the handle to stdin
// XXX: in some cases, packer closes stdin, so the following can't be guaranteed
/*
tty := os.Stdin
*/
// open up a handle to the current tty
tty, err := os.Open("/dev/tty")
if err != nil {
return 0, 0, err
}
defer tty.Close()
// convert the handle into a file descriptor
fd := int(tty.Fd())
// use it to make an Ioctl
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err
}
// return the width and height
return int(ws.Col), int(ws.Row), nil
}

9
common/terminal_test.go Normal file
View File

@ -0,0 +1,9 @@
package common
import "testing"
func TestGetTerminalDimensions(t *testing.T) {
if _, _, err := GetTerminalDimensions(); err != nil {
t.Fatalf("Unable to get terminal dimensions: %s", err)
}
}

View File

@ -0,0 +1,86 @@
// +build windows
package common
import (
"syscall"
"unsafe"
)
// windows constants and structures pulled from msdn
const (
_STD_INPUT_HANDLE = -10
_STD_OUTPUT_HANDLE = -11
_STD_ERROR_HANDLE = -12
)
type (
_SHORT int16
_WORD uint16
_SMALL_RECT struct {
Left, Top, Right, Bottom _SHORT
}
_COORD struct {
X, Y _SHORT
}
_CONSOLE_SCREEN_BUFFER_INFO struct {
dwSize, dwCursorPosition _COORD
wAttributes _WORD
srWindow _SMALL_RECT
dwMaximumWindowSize _COORD
}
)
// Low-level functions that call into Windows API for getting console info
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var kernel32_GetStdHandleProc = kernel32.NewProc("GetStdHandle")
var kernel32_GetConsoleScreenBufferInfoProc = kernel32.NewProc("GetConsoleScreenBufferInfo")
func kernel32_GetStdHandle(nStdHandle int32) (syscall.Handle, error) {
res, _, err := kernel32_GetStdHandleProc.Call(uintptr(nStdHandle))
if res == uintptr(syscall.InvalidHandle) {
return syscall.InvalidHandle, error(err)
}
return syscall.Handle(res), nil
}
func kernel32_GetConsoleScreenBufferInfo(hConsoleOutput syscall.Handle, info *_CONSOLE_SCREEN_BUFFER_INFO) error {
ok, _, err := kernel32_GetConsoleScreenBufferInfoProc.Call(uintptr(hConsoleOutput), uintptr(unsafe.Pointer(info)))
if int(ok) == 0 {
return error(err)
}
return nil
}
// windows api to get the console screen buffer info
func getConsoleScreenBufferInfo(csbi *_CONSOLE_SCREEN_BUFFER_INFO) (err error) {
var (
bi _CONSOLE_SCREEN_BUFFER_INFO
fd syscall.Handle
)
// Re-open CONOUT$ as in some instances, stdout may be closed and guaranteed an stdout
if fd, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0); err != nil {
return err
}
defer syscall.Close(fd)
// grab the dimensions for the console
if err = kernel32_GetConsoleScreenBufferInfo(fd, &bi); err != nil {
return err
}
*csbi = bi
return nil
}
func platformGetTerminalDimensions() (width, height int, err error) {
var csbi _CONSOLE_SCREEN_BUFFER_INFO
if err = getConsoleScreenBufferInfo(&csbi); err != nil {
return 0, 0, err
}
return int(csbi.dwSize.X), int(csbi.dwSize.Y), nil
}

View File

@ -3,6 +3,7 @@ package file
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -125,8 +126,16 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator)
} }
defer f.Close() defer f.Close()
err = comm.Download(src, f) // Get a default progress bar
if err != nil { pb := common.GetProgressBar(ui, &p.config.PackerConfig)
bar := pb.Start()
defer bar.Finish()
// Create MultiWriter for the current progress
pf := io.MultiWriter(f, bar)
// Download the file
if err = comm.Download(src, pf); err != nil {
ui.Error(fmt.Sprintf("Download failed: %s", err)) ui.Error(fmt.Sprintf("Download failed: %s", err))
return err return err
} }
@ -166,8 +175,16 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er
dst = filepath.Join(dst, filepath.Base(src)) dst = filepath.Join(dst, filepath.Base(src))
} }
err = comm.Upload(dst, f, &fi) // Get a default progress bar
if err != nil { pb := common.GetProgressBar(ui, &p.config.PackerConfig)
bar := pb.Start()
defer bar.Finish()
// Create ProxyReader for the current progress
pf := bar.NewProxyReader(f)
// Upload the file
if err = comm.Upload(dst, pf, &fi); err != nil {
ui.Error(fmt.Sprintf("Upload failed: %s", err)) ui.Error(fmt.Sprintf("Upload failed: %s", err))
return err return err
} }

12
vendor/github.com/cheggaaa/pb/LICENSE generated vendored Normal file
View File

@ -0,0 +1,12 @@
Copyright (c) 2012-2015, Sergey Cherepanov
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

179
vendor/github.com/cheggaaa/pb/README.md generated vendored Normal file
View File

@ -0,0 +1,179 @@
# Terminal progress bar for Go
[![Join the chat at https://gitter.im/cheggaaa/pb](https://badges.gitter.im/cheggaaa/pb.svg)](https://gitter.im/cheggaaa/pb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Simple progress bar for console programs.
Please check the new version https://github.com/cheggaaa/pb/tree/v2 (currently, it's beta)
## Installation
```
go get gopkg.in/cheggaaa/pb.v1
```
## Usage
```Go
package main
import (
"gopkg.in/cheggaaa/pb.v1"
"time"
)
func main() {
count := 100000
bar := pb.StartNew(count)
for i := 0; i < count; i++ {
bar.Increment()
time.Sleep(time.Millisecond)
}
bar.FinishPrint("The End!")
}
```
Result will be like this:
```
> go run test.go
37158 / 100000 [================>_______________________________] 37.16% 1m11s
```
## Customization
```Go
// create bar
bar := pb.New(count)
// refresh info every second (default 200ms)
bar.SetRefreshRate(time.Second)
// show percents (by default already true)
bar.ShowPercent = true
// show bar (by default already true)
bar.ShowBar = true
// no counters
bar.ShowCounters = false
// show "time left"
bar.ShowTimeLeft = true
// show average speed
bar.ShowSpeed = true
// sets the width of the progress bar
bar.SetWidth(80)
// sets the width of the progress bar, but if terminal size smaller will be ignored
bar.SetMaxWidth(80)
// convert output to readable format (like KB, MB)
bar.SetUnits(pb.U_BYTES)
// and start
bar.Start()
```
## Progress bar for IO Operations
```go
// create and start bar
bar := pb.New(myDataLen).SetUnits(pb.U_BYTES)
bar.Start()
// my io.Reader
r := myReader
// my io.Writer
w := myWriter
// create proxy reader
reader := bar.NewProxyReader(r)
// and copy from pb reader
io.Copy(w, reader)
```
```go
// create and start bar
bar := pb.New(myDataLen).SetUnits(pb.U_BYTES)
bar.Start()
// my io.Reader
r := myReader
// my io.Writer
w := myWriter
// create multi writer
writer := io.MultiWriter(w, bar)
// and copy
io.Copy(writer, r)
bar.Finish()
```
## Custom Progress Bar Look-and-feel
```go
bar.Format("<.- >")
```
## Multiple Progress Bars (experimental and unstable)
Do not print to terminal while pool is active.
```go
package main
import (
"math/rand"
"sync"
"time"
"gopkg.in/cheggaaa/pb.v1"
)
func main() {
// create bars
first := pb.New(200).Prefix("First ")
second := pb.New(200).Prefix("Second ")
third := pb.New(200).Prefix("Third ")
// start pool
pool, err := pb.StartPool(first, second, third)
if err != nil {
panic(err)
}
// update bars
wg := new(sync.WaitGroup)
for _, bar := range []*pb.ProgressBar{first, second, third} {
wg.Add(1)
go func(cb *pb.ProgressBar) {
for n := 0; n < 200; n++ {
cb.Increment()
time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
}
cb.Finish()
wg.Done()
}(bar)
}
wg.Wait()
// close pool
pool.Stop()
}
```
The result will be as follows:
```
$ go run example/multiple.go
First 34 / 200 [=========>---------------------------------------------] 17.00% 00m08s
Second 42 / 200 [===========>------------------------------------------] 21.00% 00m06s
Third 36 / 200 [=========>---------------------------------------------] 18.00% 00m08s
```

125
vendor/github.com/cheggaaa/pb/format.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
package pb
import (
"fmt"
"time"
)
type Units int
const (
// U_NO are default units, they represent a simple value and are not formatted at all.
U_NO Units = iota
// U_BYTES units are formatted in a human readable way (B, KiB, MiB, ...)
U_BYTES
// U_BYTES_DEC units are like U_BYTES, but base 10 (B, KB, MB, ...)
U_BYTES_DEC
// U_DURATION units are formatted in a human readable way (3h14m15s)
U_DURATION
)
const (
KiB = 1024
MiB = 1048576
GiB = 1073741824
TiB = 1099511627776
KB = 1e3
MB = 1e6
GB = 1e9
TB = 1e12
)
func Format(i int64) *formatter {
return &formatter{n: i}
}
type formatter struct {
n int64
unit Units
width int
perSec bool
}
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) PerSec() *formatter {
f.perSec = true
return f
}
func (f *formatter) String() (out string) {
switch f.unit {
case U_BYTES:
out = formatBytes(f.n)
case U_BYTES_DEC:
out = formatBytesDec(f.n)
case U_DURATION:
out = formatDuration(f.n)
default:
out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n)
}
if f.perSec {
out += "/s"
}
return
}
// Convert bytes to human readable string. Like 2 MiB, 64.2 KiB, 52 B
func formatBytes(i int64) (result string) {
switch {
case i >= TiB:
result = fmt.Sprintf("%.02f TiB", float64(i)/TiB)
case i >= GiB:
result = fmt.Sprintf("%.02f GiB", float64(i)/GiB)
case i >= MiB:
result = fmt.Sprintf("%.02f MiB", float64(i)/MiB)
case i >= KiB:
result = fmt.Sprintf("%.02f KiB", float64(i)/KiB)
default:
result = fmt.Sprintf("%d B", i)
}
return
}
// Convert bytes to base-10 human readable string. Like 2 MB, 64.2 KB, 52 B
func formatBytesDec(i int64) (result string) {
switch {
case i >= TB:
result = fmt.Sprintf("%.02f TB", float64(i)/TB)
case i >= GB:
result = fmt.Sprintf("%.02f GB", float64(i)/GB)
case i >= MB:
result = fmt.Sprintf("%.02f MB", float64(i)/MB)
case i >= KB:
result = fmt.Sprintf("%.02f KB", float64(i)/KB)
default:
result = fmt.Sprintf("%d B", i)
}
return
}
func formatDuration(n int64) (result string) {
d := time.Duration(n)
if d > time.Hour*24 {
result = fmt.Sprintf("%dd", d/24/time.Hour)
d -= (d / time.Hour / 24) * (time.Hour * 24)
}
if d > time.Hour {
result = fmt.Sprintf("%s%dh", result, d/time.Hour)
d -= d / time.Hour * time.Hour
}
m := d / time.Minute
d -= m * time.Minute
s := d / time.Second
result = fmt.Sprintf("%s%02dm%02ds", result, m, s)
return
}

500
vendor/github.com/cheggaaa/pb/pb.go generated vendored Normal file
View File

@ -0,0 +1,500 @@
// Simple console progress bars
package pb
import (
"fmt"
"io"
"math"
"strings"
"sync"
"sync/atomic"
"time"
"unicode/utf8"
)
// Current version
const Version = "1.0.25"
const (
// Default refresh rate - 200ms
DEFAULT_REFRESH_RATE = time.Millisecond * 200
FORMAT = "[=>-]"
)
// DEPRECATED
// variables for backward compatibility, from now do not work
// use pb.Format and pb.SetRefreshRate
var (
DefaultRefreshRate = DEFAULT_REFRESH_RATE
BarStart, BarEnd, Empty, Current, CurrentN string
)
// Create new progress bar object
func New(total int) *ProgressBar {
return New64(int64(total))
}
// Create new progress bar object using int64 as total
func New64(total int64) *ProgressBar {
pb := &ProgressBar{
Total: total,
RefreshRate: DEFAULT_REFRESH_RATE,
ShowPercent: true,
ShowCounters: true,
ShowBar: true,
ShowTimeLeft: true,
ShowElapsedTime: false,
ShowFinalTime: true,
Units: U_NO,
ManualUpdate: false,
finish: make(chan struct{}),
}
return pb.Format(FORMAT)
}
// Create new object and start
func StartNew(total int) *ProgressBar {
return New(total).Start()
}
// Callback for custom output
// For example:
// bar.Callback = func(s string) {
// mySuperPrint(s)
// }
//
type Callback func(out string)
type ProgressBar struct {
current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)
previous int64
Total int64
RefreshRate time.Duration
ShowPercent, ShowCounters bool
ShowSpeed, ShowTimeLeft, ShowBar bool
ShowFinalTime, ShowElapsedTime bool
Output io.Writer
Callback Callback
NotPrint bool
Units Units
Width int
ForceWidth bool
ManualUpdate bool
AutoStat bool
// Default width for the time box.
UnitsWidth int
TimeBoxWidth int
finishOnce sync.Once //Guards isFinish
finish chan struct{}
isFinish bool
startTime time.Time
startValue int64
changeTime time.Time
prefix, postfix string
mu sync.Mutex
lastPrint string
BarStart string
BarEnd string
Empty string
Current string
CurrentN string
AlwaysUpdate bool
}
// Start print
func (pb *ProgressBar) Start() *ProgressBar {
pb.startTime = time.Now()
pb.startValue = atomic.LoadInt64(&pb.current)
if atomic.LoadInt64(&pb.Total) == 0 {
pb.ShowTimeLeft = false
pb.ShowPercent = false
pb.AutoStat = false
}
if !pb.ManualUpdate {
pb.Update() // Initial printing of the bar before running the bar refresher.
go pb.refresher()
}
return pb
}
// Increment current value
func (pb *ProgressBar) Increment() int {
return pb.Add(1)
}
// Get current value
func (pb *ProgressBar) Get() int64 {
c := atomic.LoadInt64(&pb.current)
return c
}
// Set current value
func (pb *ProgressBar) Set(current int) *ProgressBar {
return pb.Set64(int64(current))
}
// Set64 sets the current value as int64
func (pb *ProgressBar) Set64(current int64) *ProgressBar {
atomic.StoreInt64(&pb.current, current)
return pb
}
// Add to current value
func (pb *ProgressBar) Add(add int) int {
return int(pb.Add64(int64(add)))
}
func (pb *ProgressBar) Add64(add int64) int64 {
return atomic.AddInt64(&pb.current, add)
}
// Set prefix string
func (pb *ProgressBar) Prefix(prefix string) *ProgressBar {
pb.prefix = prefix
return pb
}
// Set postfix string
func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
pb.postfix = postfix
return pb
}
// Set custom format for bar
// Example: bar.Format("[=>_]")
// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
func (pb *ProgressBar) Format(format string) *ProgressBar {
var formatEntries []string
if utf8.RuneCountInString(format) == 5 {
formatEntries = strings.Split(format, "")
} else {
formatEntries = strings.Split(format, "\x00")
}
if len(formatEntries) == 5 {
pb.BarStart = formatEntries[0]
pb.BarEnd = formatEntries[4]
pb.Empty = formatEntries[3]
pb.Current = formatEntries[1]
pb.CurrentN = formatEntries[2]
}
return pb
}
// Set bar refresh rate
func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar {
pb.RefreshRate = rate
return pb
}
// Set units
// bar.SetUnits(U_NO) - by default
// bar.SetUnits(U_BYTES) - for Mb, Kb, etc
func (pb *ProgressBar) SetUnits(units Units) *ProgressBar {
pb.Units = units
return pb
}
// Set max width, if width is bigger than terminal width, will be ignored
func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar {
pb.Width = width
pb.ForceWidth = false
return pb
}
// Set bar width
func (pb *ProgressBar) SetWidth(width int) *ProgressBar {
pb.Width = width
pb.ForceWidth = true
return pb
}
// End print
func (pb *ProgressBar) Finish() {
//Protect multiple calls
pb.finishOnce.Do(func() {
close(pb.finish)
pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current))
pb.mu.Lock()
defer pb.mu.Unlock()
switch {
case pb.Output != nil:
fmt.Fprintln(pb.Output)
case !pb.NotPrint:
fmt.Println()
}
pb.isFinish = true
})
}
// IsFinished return boolean
func (pb *ProgressBar) IsFinished() bool {
pb.mu.Lock()
defer pb.mu.Unlock()
return pb.isFinish
}
// End print and write string 'str'
func (pb *ProgressBar) FinishPrint(str string) {
pb.Finish()
if pb.Output != nil {
fmt.Fprintln(pb.Output, str)
} else {
fmt.Println(str)
}
}
// implement io.Writer
func (pb *ProgressBar) Write(p []byte) (n int, err error) {
n = len(p)
pb.Add(n)
return
}
// implement io.Reader
func (pb *ProgressBar) Read(p []byte) (n int, err error) {
n = len(p)
pb.Add(n)
return
}
// Create new proxy reader over bar
// Takes io.Reader or io.ReadCloser
func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader {
return &Reader{r, pb}
}
func (pb *ProgressBar) write(total, current int64) {
width := pb.GetWidth()
var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string
// percents
if pb.ShowPercent {
var percent float64
if total > 0 {
percent = float64(current) / (float64(total) / float64(100))
} else {
percent = float64(current) / float64(100)
}
percentBox = fmt.Sprintf(" %6.02f%%", percent)
}
// counters
if pb.ShowCounters {
current := Format(current).To(pb.Units).Width(pb.UnitsWidth)
if total > 0 {
totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth)
countersBox = fmt.Sprintf(" %s / %s ", current, totalS)
} else {
countersBox = fmt.Sprintf(" %s / ? ", current)
}
}
// time left
pb.mu.Lock()
currentFromStart := current - pb.startValue
fromStart := time.Now().Sub(pb.startTime)
lastChangeTime := pb.changeTime
fromChange := lastChangeTime.Sub(pb.startTime)
pb.mu.Unlock()
if pb.ShowElapsedTime {
timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second)
}
select {
case <-pb.finish:
if pb.ShowFinalTime {
var left time.Duration
left = (fromStart / time.Second) * time.Second
timeLeftBox = fmt.Sprintf(" %s", left.String())
}
default:
if pb.ShowTimeLeft && currentFromStart > 0 {
perEntry := fromChange / time.Duration(currentFromStart)
var left time.Duration
if total > 0 {
left = time.Duration(total-currentFromStart) * perEntry
left -= time.Since(lastChangeTime)
left = (left / time.Second) * time.Second
} else {
left = time.Duration(currentFromStart) * perEntry
left = (left / time.Second) * time.Second
}
if left > 0 {
timeLeft := Format(int64(left)).To(U_DURATION).String()
timeLeftBox = fmt.Sprintf(" %s", timeLeft)
}
}
}
if len(timeLeftBox) < pb.TimeBoxWidth {
timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox)
}
// speed
if pb.ShowSpeed && currentFromStart > 0 {
fromStart := time.Now().Sub(pb.startTime)
speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second))
speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String()
}
barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix)
// bar
if pb.ShowBar {
size := width - barWidth
if size > 0 {
if total > 0 {
curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size)))
emptySize := size - curSize
barBox = pb.BarStart
if emptySize < 0 {
emptySize = 0
}
if curSize > size {
curSize = size
}
cursorLen := escapeAwareRuneCountInString(pb.Current)
if emptySize <= 0 {
barBox += strings.Repeat(pb.Current, curSize/cursorLen)
} else if curSize > 0 {
cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN)
cursorRepetitions := (curSize - cursorEndLen) / cursorLen
barBox += strings.Repeat(pb.Current, cursorRepetitions)
barBox += pb.CurrentN
}
emptyLen := escapeAwareRuneCountInString(pb.Empty)
barBox += strings.Repeat(pb.Empty, emptySize/emptyLen)
barBox += pb.BarEnd
} else {
pos := size - int(current)%int(size)
barBox = pb.BarStart
if pos-1 > 0 {
barBox += strings.Repeat(pb.Empty, pos-1)
}
barBox += pb.Current
if size-pos-1 > 0 {
barBox += strings.Repeat(pb.Empty, size-pos-1)
}
barBox += pb.BarEnd
}
}
}
// check len
out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
if cl := escapeAwareRuneCountInString(out); cl < width {
end = strings.Repeat(" ", width-cl)
}
// and print!
pb.mu.Lock()
defer pb.mu.Unlock()
pb.lastPrint = out + end
isFinish := pb.isFinish
switch {
case isFinish:
return
case pb.Output != nil:
fmt.Fprint(pb.Output, "\r"+out+end)
case pb.Callback != nil:
pb.Callback(out + end)
case !pb.NotPrint:
fmt.Print("\r" + out + end)
}
}
// GetTerminalWidth - returns terminal width for all platforms.
func GetTerminalWidth() (int, error) {
return terminalWidth()
}
func (pb *ProgressBar) GetWidth() int {
if pb.ForceWidth {
return pb.Width
}
width := pb.Width
termWidth, _ := terminalWidth()
if width == 0 || termWidth <= width {
width = termWidth
}
return width
}
// Write the current state of the progressbar
func (pb *ProgressBar) Update() {
c := atomic.LoadInt64(&pb.current)
p := atomic.LoadInt64(&pb.previous)
t := atomic.LoadInt64(&pb.Total)
if p != c {
pb.mu.Lock()
pb.changeTime = time.Now()
pb.mu.Unlock()
atomic.StoreInt64(&pb.previous, c)
}
pb.write(t, c)
if pb.AutoStat {
if c == 0 {
pb.startTime = time.Now()
pb.startValue = 0
} else if c >= t && pb.isFinish != true {
pb.Finish()
}
}
}
// String return the last bar print
func (pb *ProgressBar) String() string {
pb.mu.Lock()
defer pb.mu.Unlock()
return pb.lastPrint
}
// SetTotal atomically sets new total count
func (pb *ProgressBar) SetTotal(total int) *ProgressBar {
return pb.SetTotal64(int64(total))
}
// SetTotal64 atomically sets new total count
func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar {
atomic.StoreInt64(&pb.Total, total)
return pb
}
// Reset bar and set new total count
// Does effect only on finished bar
func (pb *ProgressBar) Reset(total int) *ProgressBar {
pb.mu.Lock()
defer pb.mu.Unlock()
if pb.isFinish {
pb.SetTotal(total).Set(0)
atomic.StoreInt64(&pb.previous, 0)
}
return pb
}
// Internal loop for refreshing the progressbar
func (pb *ProgressBar) refresher() {
for {
select {
case <-pb.finish:
return
case <-time.After(pb.RefreshRate):
pb.Update()
}
}
}

143
vendor/github.com/cheggaaa/pb/pb_win.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
// +build windows
package pb
import (
"errors"
"fmt"
"os"
"sync"
"syscall"
"unsafe"
)
var tty = os.Stdin
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
// GetConsoleScreenBufferInfo retrieves information about the
// specified console screen buffer.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
// GetConsoleMode retrieves the current input mode of a console's
// input buffer or the current output mode of a console screen buffer.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
getConsoleMode = kernel32.NewProc("GetConsoleMode")
// SetConsoleMode sets the input mode of a console's input buffer
// or the output mode of a console screen buffer.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
setConsoleMode = kernel32.NewProc("SetConsoleMode")
// SetConsoleCursorPosition sets the cursor position in the
// specified console screen buffer.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
)
type (
// Defines the coordinates of the upper left and lower right corners
// of a rectangle.
// See
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx
smallRect struct {
Left, Top, Right, Bottom int16
}
// Defines the coordinates of a character cell in a console screen
// buffer. The origin of the coordinate system (0,0) is at the top, left cell
// of the buffer.
// See
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx
coordinates struct {
X, Y int16
}
word int16
// Contains information about a console screen buffer.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
consoleScreenBufferInfo struct {
dwSize coordinates
dwCursorPosition coordinates
wAttributes word
srWindow smallRect
dwMaximumWindowSize coordinates
}
)
// terminalWidth returns width of the terminal.
func terminalWidth() (width int, err error) {
var info consoleScreenBufferInfo
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0)
if e != 0 {
return 0, error(e)
}
return int(info.dwSize.X) - 1, nil
}
func getCursorPos() (pos coordinates, err error) {
var info consoleScreenBufferInfo
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0)
if e != 0 {
return info.dwCursorPosition, error(e)
}
return info.dwCursorPosition, nil
}
func setCursorPos(pos coordinates) error {
_, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0)
if e != 0 {
return error(e)
}
return nil
}
var ErrPoolWasStarted = errors.New("Bar pool was started")
var echoLocked bool
var echoLockMutex sync.Mutex
var oldState word
func lockEcho() (shutdownCh chan struct{}, err error) {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
if echoLocked {
err = ErrPoolWasStarted
return
}
echoLocked = true
if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 {
err = fmt.Errorf("Can't get terminal settings: %v", e)
return
}
newState := oldState
const ENABLE_ECHO_INPUT = 0x0004
const ENABLE_LINE_INPUT = 0x0002
newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))
if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 {
err = fmt.Errorf("Can't set terminal settings: %v", e)
return
}
shutdownCh = make(chan struct{})
return
}
func unlockEcho() (err error) {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
if !echoLocked {
return
}
echoLocked = false
if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 {
err = fmt.Errorf("Can't set terminal settings")
}
return
}

108
vendor/github.com/cheggaaa/pb/pb_x.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
// +build linux darwin freebsd netbsd openbsd solaris dragonfly
// +build !appengine
package pb
import (
"errors"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"golang.org/x/sys/unix"
)
var ErrPoolWasStarted = errors.New("Bar pool was started")
var (
echoLockMutex sync.Mutex
origTermStatePtr *unix.Termios
tty *os.File
)
func init() {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
var err error
tty, err = os.Open("/dev/tty")
if err != nil {
tty = os.Stdin
}
}
// terminalWidth returns width of the terminal.
func terminalWidth() (int, error) {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
fd := int(tty.Fd())
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, err
}
return int(ws.Col), nil
}
func lockEcho() (shutdownCh chan struct{}, err error) {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
if origTermStatePtr != nil {
return shutdownCh, ErrPoolWasStarted
}
fd := int(tty.Fd())
origTermStatePtr, err = unix.IoctlGetTermios(fd, ioctlReadTermios)
if err != nil {
return nil, fmt.Errorf("Can't get terminal settings: %v", err)
}
oldTermios := *origTermStatePtr
newTermios := oldTermios
newTermios.Lflag &^= syscall.ECHO
newTermios.Lflag |= syscall.ICANON | syscall.ISIG
newTermios.Iflag |= syscall.ICRNL
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil {
return nil, fmt.Errorf("Can't set terminal settings: %v", err)
}
shutdownCh = make(chan struct{})
go catchTerminate(shutdownCh)
return
}
func unlockEcho() error {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
if origTermStatePtr == nil {
return nil
}
fd := int(tty.Fd())
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil {
return fmt.Errorf("Can't set terminal settings: %v", err)
}
origTermStatePtr = nil
return nil
}
// listen exit signals and restore terminal state
func catchTerminate(shutdownCh chan struct{}) {
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL)
defer signal.Stop(sig)
select {
case <-shutdownCh:
unlockEcho()
case <-sig:
unlockEcho()
}
}

102
vendor/github.com/cheggaaa/pb/pool.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows
package pb
import (
"io"
"sync"
"time"
)
// Create and start new pool with given bars
// You need call pool.Stop() after work
func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) {
pool = new(Pool)
if err = pool.Start(); err != nil {
return
}
pool.Add(pbs...)
return
}
// NewPool initialises a pool with progress bars, but
// doesn't start it. You need to call Start manually
func NewPool(pbs ...*ProgressBar) (pool *Pool) {
pool = new(Pool)
pool.Add(pbs...)
return
}
type Pool struct {
Output io.Writer
RefreshRate time.Duration
bars []*ProgressBar
lastBarsCount int
shutdownCh chan struct{}
workerCh chan struct{}
m sync.Mutex
finishOnce sync.Once
}
// Add progress bars.
func (p *Pool) Add(pbs ...*ProgressBar) {
p.m.Lock()
defer p.m.Unlock()
for _, bar := range pbs {
bar.ManualUpdate = true
bar.NotPrint = true
bar.Start()
p.bars = append(p.bars, bar)
}
}
func (p *Pool) Start() (err error) {
p.RefreshRate = DefaultRefreshRate
p.shutdownCh, err = lockEcho()
if err != nil {
return
}
p.workerCh = make(chan struct{})
go p.writer()
return
}
func (p *Pool) writer() {
var first = true
defer func() {
if first == false {
p.print(false)
} else {
p.print(true)
p.print(false)
}
close(p.workerCh)
}()
for {
select {
case <-time.After(p.RefreshRate):
if p.print(first) {
p.print(false)
return
}
first = false
case <-p.shutdownCh:
return
}
}
}
// Restore terminal state and close pool
func (p *Pool) Stop() error {
p.finishOnce.Do(func() {
close(p.shutdownCh)
})
// Wait for the worker to complete
select {
case <-p.workerCh:
}
return unlockEcho()
}

45
vendor/github.com/cheggaaa/pb/pool_win.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
// +build windows
package pb
import (
"fmt"
"log"
)
func (p *Pool) print(first bool) bool {
p.m.Lock()
defer p.m.Unlock()
var out string
if !first {
coords, err := getCursorPos()
if err != nil {
log.Panic(err)
}
coords.Y -= int16(p.lastBarsCount)
if coords.Y < 0 {
coords.Y = 0
}
coords.X = 0
err = setCursorPos(coords)
if err != nil {
log.Panic(err)
}
}
isFinished := true
for _, bar := range p.bars {
if !bar.IsFinished() {
isFinished = false
}
bar.Update()
out += fmt.Sprintf("\r%s\n", bar.String())
}
if p.Output != nil {
fmt.Fprint(p.Output, out)
} else {
fmt.Print(out)
}
p.lastBarsCount = len(p.bars)
return isFinished
}

29
vendor/github.com/cheggaaa/pb/pool_x.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
// +build linux darwin freebsd netbsd openbsd solaris dragonfly
package pb
import "fmt"
func (p *Pool) print(first bool) bool {
p.m.Lock()
defer p.m.Unlock()
var out string
if !first {
out = fmt.Sprintf("\033[%dA", p.lastBarsCount)
}
isFinished := true
for _, bar := range p.bars {
if !bar.IsFinished() {
isFinished = false
}
bar.Update()
out += fmt.Sprintf("\r%s\n", bar.String())
}
if p.Output != nil {
fmt.Fprint(p.Output, out)
} else {
fmt.Print(out)
}
p.lastBarsCount = len(p.bars)
return isFinished
}

25
vendor/github.com/cheggaaa/pb/reader.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package pb
import (
"io"
)
// It's proxy reader, implement io.Reader
type Reader struct {
io.Reader
bar *ProgressBar
}
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
r.bar.Add(n)
return
}
// Close the reader when it implements io.Closer
func (r *Reader) Close() (err error) {
if closer, ok := r.Reader.(io.Closer); ok {
return closer.Close()
}
return
}

17
vendor/github.com/cheggaaa/pb/runecount.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package pb
import (
"github.com/mattn/go-runewidth"
"regexp"
)
// Finds the control character sequences (like colors)
var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d")
func escapeAwareRuneCountInString(s string) int {
n := runewidth.StringWidth(s)
for _, sm := range ctrlFinder.FindAllString(s, -1) {
n -= runewidth.StringWidth(sm)
}
return n
}

9
vendor/github.com/cheggaaa/pb/termios_bsd.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
// +build darwin freebsd netbsd openbsd dragonfly
// +build !appengine
package pb
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
const ioctlWriteTermios = syscall.TIOCSETA

13
vendor/github.com/cheggaaa/pb/termios_sysv.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux solaris
// +build !appengine
package pb
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TCGETS
const ioctlWriteTermios = unix.TCSETS

8
vendor/vendor.json vendored
View File

@ -585,6 +585,14 @@
"path": "github.com/biogo/hts/bgzf", "path": "github.com/biogo/hts/bgzf",
"revision": "50da7d4131a3b5c9d063932461cab4d1fafb20b0" "revision": "50da7d4131a3b5c9d063932461cab4d1fafb20b0"
}, },
{
"checksumSHA1": "7GMQgpfoSQv4QcaREE2GzSZvlQI=",
"path": "github.com/cheggaaa/pb",
"revision": "2af8bbdea9e99e83b3ac400d8f6b6d1b8cbbf338",
"revisionTime": "2018-05-21T09:56:06Z",
"version": "v1.0.25",
"versionExact": "v1.0.25"
},
{ {
"checksumSHA1": "X2/71FBrn4pA3WcA620ySVO0uHU=", "checksumSHA1": "X2/71FBrn4pA3WcA620ySVO0uHU=",
"path": "github.com/creack/goselect", "path": "github.com/creack/goselect",