golang动态进度条

golang dynamic progressbar

我正在尝试使用 golang 下载文件。

我正在下载文件,没问题。在我使用 cheggaaa 的进度条库之后。但是我不能动态。

如何实现动态进度条?

我的代码如下:

主要包

import (
    "flag"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strings"
    "github.com/cheggaaa/pb"
    "time"
)

/*
    usage = usage text
    version = current number
    help use Sprintf
    *cliUrl from cmd
    *cliVersion from cmd
    *cliHelp * from cmd
*/
var (
    usage      = "Usage: ./gofret -url=http://some/do.zip"
    version    = "Version: 0.1"
    help       = fmt.Sprintf("\n\n  %s\n\n\n  %s", usage, version)
    cliUrl     *string
    cliVersion *bool
    cliHelp    *bool
)

func init() {
    /*
        if *cliUrl != "" {
            fmt.Println(*cliUrl)
        }

        ./gofret -url=http://somesite.com/somefile.zip
        ./gofret -url=https://github.com/aligoren/syspy/archive/master.zip
    */
    cliUrl = flag.String("url", "", usage)

    /*
        else if *cliVersion{
            fmt.Println(flag.Lookup("version").Usage)
        }

        ./gofret -version
    */
    cliVersion = flag.Bool("version", false, version)

    /*
        if *cliHelp {
            fmt.Println(flag.Lookup("help").Usage)
        }

        ./gofret -help
    */
    cliHelp = flag.Bool("help", false, help)
}

func main() {

    /*
        Parse all flags
    */
    flag.Parse()

    if *cliUrl != "" {
        fmt.Println("Downloading file")

        /* parse url from *cliUrl */
        fileUrl, err := url.Parse(*cliUrl)

        if err != nil {
            panic(err)
        }

        /* get path from *cliUrl */
        filePath := fileUrl.Path

        /*
            seperate file.
            http://+site.com/+(file.zip)
        */
        segments := strings.Split(filePath, "/")

        /*
            file.zip filename lenth -1
        */
        fileName := segments[len(segments)-1]

        /*
            Create new file.
            Filename from fileName variable
        */
        file, err := os.Create(fileName)

        if err != nil {
            fmt.Println(err)
            panic(err)
        }
        defer file.Close()

        /*
            check status and CheckRedirect
        */
        checkStatus := http.Client{
            CheckRedirect: func(r *http.Request, via []*http.Request) error {
                r.URL.Opaque = r.URL.Path
                return nil
            },
        }

        /*
            Get Response: 200 OK?
        */
        response, err := checkStatus.Get(*cliUrl)

        if err != nil {
            fmt.Println(err)
            panic(err)
        }
        defer response.Body.Close()
        fmt.Println(response.Status) // Example: 200 OK

        /*
            fileSize example: 12572 bytes
        */
        fileSize, err := io.Copy(file, response.Body)
        /*
            progressbar worked after download :(
        */
        var countSize int = int(fileSize/1000)
        count := countSize
        bar := pb.StartNew(count)
        for i := 0; i < count; i++ {
            bar.Increment()
            time.Sleep(time.Millisecond)
        }
        bar.FinishPrint("The End!")

        if err != nil {
            panic(err)
        }


        fmt.Printf("%s with %v bytes downloaded", fileName, count)

    } else if *cliVersion {
        /*
            lookup version flag's usage text
        */
        fmt.Println(flag.Lookup("version").Usage)
    } else if *cliHelp {
        /*
            lookup help flag's usage text
        */
        fmt.Println(flag.Lookup("help").Usage)
    } else {
        /*
            using help's usage text for handling other status
        */
        fmt.Println(flag.Lookup("help").Usage)
    }
}

而我的程序是运行:

Downloading file
200 OK

下载后工作进度条:

6612 / 6612 [=====================================================] 100.00 % 7s
The End!
master.zip with 6612 bytes downloaded

我的进度条代码如下:

/*
    progressbar worked after download :(
*/
var countSize int = int(fileSize/1000)
count := countSize
bar := pb.StartNew(count)
for i := 0; i < count; i++ {
    bar.Increment()
    time.Sleep(time.Millisecond)
}
bar.FinishPrint("The End!")

如何解决进度条问题?

我写了下面的东西,在一般情况下是正确的,与进度条无关,但是这个库完全设计用来处理这个问题,有专门的支持,并给出 explicit example of downloading a file.


您需要运行在更新进度条的同时下载,目前您正在下载整个文件,然后更新进度条。

这有点草率,但应该让你朝着正确的方向前进:

首先,获取预期的文件大小:

    filesize := response.ContentLength

然后在 goroutine 中开始下载:

    go func() {
        n, err := io.Copy(file, response.Body)
        if n != filesize {
            log.Fatal("Truncated")
        }
        if err != nil {
            log.Fatalf("Error: %v", err)
        }
    }()

然后更新进度条:

    countSize := int(filesize / 1000)
    bar := pb.StartNew(countSize)
    var fi os.FileInfo
    for fi == nil || fi.Size() < filesize {
        fi, _ = file.Stat()
        bar.Set(int(fi.Size() / 1000))
        time.Sleep(time.Millisecond)
    }
    bar.FinishPrint("The End!")

就像我说的,这有点草率;您可能希望根据文件的大小更好地缩放条形图,并且 log.Fatal 调用很难看。但它处理了问题的核心。

或者,您可以通过编写自己的 io.Copy 版本在没有 goroutine 的情况下做到这一点。从 response.Body 读取一个块,更新进度条,然后将一个块写入 file。这可以说更好,因为您可以避免睡眠调用。

不需要更多的协程。刚刚从 bar

读取
// start new bar
bar := pb.New(fileSize).SetUnits(pb.U_BYTES)
bar.Start()
// create proxy reader
rd := bar.NewProxyReader(response.Body)
// and copy from reader
io.Copy(file, rd)

其实你可以通过下面的代码自己实现进度条。

func (bar *Bar) Play(cur int64) {
 bar.cur = cur
 last := bar.percent
 bar.percent = bar.getPercent()
 if bar.percent != last && bar.percent%2 == 0 {
   bar.rate += bar.graph
 }
 fmt.Printf("\r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent, bar.cur, bar.total)
}

这里的关键是使用 escape \r 这将用创建动态效果的更新进度替换当前进度。

可以在 here 找到详细的解释。