Golang动态进度条

5

我正在尝试使用Golang进行文件下载。

我可以成功下载文件。之后,我使用了cheggaaa的progressbar库。但是我无法实现动态效果。

请问如何实现动态进度条?

以下是我的代码:

package main

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!")

我该如何解决进度条问题?

1
你甚至在开始进度条之前就下载了文件。 - JimB
@JimB 我已经尝试了,但是不起作用,因为我得到了fileSize未定义的错误。 - Ali
3个回答

8

不需要更多的goroutine,只需从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)

5
我写了以下内容,一般情况下是正确的,与进度条无关,但是这个库 正好 设计用来解决这个问题,具有专门的支持,并提供一个 下载文件的明确示例
你需要同时运行下载和更新进度条,目前你是先下载整个文件再更新进度条。这有点不太好,但应该能让你朝着正确的方向前进:
首先,获取预期文件大小:
    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。这可能更好,因为您可以避免睡眠调用。


太好了!谢谢。现在源代码在GitHub上:https://gist.github.com/aligoren/9426249fa8cd04c4633e - Ali

1
实际上,您可以使用以下代码自己实现进度条。
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)
}

这里的关键是使用转义符\r,它会在原地替换当前进度以更新进度条,从而产生动态效果。
可以在这里找到详细解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接