Golang动态进度条

huangapple go评论90阅读模式
英文:

golang dynamic progressbar

问题

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

我成功下载了文件。然后我使用了cheggaaa的progressbar库。但是我无法实现动态进度条。

我该如何实现动态进度条?

以下是我的代码:

package main

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

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() {
	cliUrl = flag.String("url", "", usage)
	cliVersion = flag.Bool("version", false, version)
	cliHelp = flag.Bool("help", false, help)
}

func main() {
	flag.Parse()

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

		fileUrl, err := url.Parse(*cliUrl)
		if err != nil {
			panic(err)
		}

		filePath := fileUrl.Path
		segments := strings.Split(filePath, "/")
		fileName := segments[len(segments)-1]

		file, err := os.Create(fileName)
		if err != nil {
			fmt.Println(err)
			panic(err)
		}
		defer file.Close()

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

		response, err := checkStatus.Get(*cliUrl)
		if err != nil {
			fmt.Println(err)
			panic(err)
		}
		defer response.Body.Close()
		fmt.Println(response.Status)

		fileSize, err := io.Copy(file, response.Body)

		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 {
		fmt.Println(flag.Lookup("version").Usage)
	} else if *cliHelp {
		fmt.Println(flag.Lookup("help").Usage)
	} else {
		fmt.Println(flag.Lookup("help").Usage)
	}
}

在程序运行时:

Downloading file
200 OK

下载完成后,进度条工作如下:

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

我的进度条代码如下:

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

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

英文:

I'm trying file download with golang.

I'm downloading file it's okay. After I'm using cheggaaa's progressbar library. But I can't dynamic.

How can I do dynamic progressbar?

My code below:

package main

import (
&quot;flag&quot;
&quot;fmt&quot;
&quot;io&quot;
&quot;net/http&quot;
&quot;net/url&quot;
&quot;os&quot;
&quot;strings&quot;
&quot;github.com/cheggaaa/pb&quot;
&quot;time&quot;
)
/*
usage = usage text
version = current number
help use Sprintf
*cliUrl from cmd
*cliVersion from cmd
*cliHelp * from cmd
*/
var (
usage      = &quot;Usage: ./gofret -url=http://some/do.zip&quot;
version    = &quot;Version: 0.1&quot;
help       = fmt.Sprintf(&quot;\n\n  %s\n\n\n  %s&quot;, usage, version)
cliUrl     *string
cliVersion *bool
cliHelp    *bool
)
func init() {
/*
if *cliUrl != &quot;&quot; {
fmt.Println(*cliUrl)
}
./gofret -url=http://somesite.com/somefile.zip
./gofret -url=https://github.com/aligoren/syspy/archive/master.zip
*/
cliUrl = flag.String(&quot;url&quot;, &quot;&quot;, usage)
/*
else if *cliVersion{
fmt.Println(flag.Lookup(&quot;version&quot;).Usage)
}
./gofret -version
*/
cliVersion = flag.Bool(&quot;version&quot;, false, version)
/*
if *cliHelp {
fmt.Println(flag.Lookup(&quot;help&quot;).Usage)
}
./gofret -help
*/
cliHelp = flag.Bool(&quot;help&quot;, false, help)
}
func main() {
/*
Parse all flags
*/
flag.Parse()
if *cliUrl != &quot;&quot; {
fmt.Println(&quot;Downloading file&quot;)
/* 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, &quot;/&quot;)
/*
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 &lt; count; i++ {
bar.Increment()
time.Sleep(time.Millisecond)
}
bar.FinishPrint(&quot;The End!&quot;)
if err != nil {
panic(err)
}
fmt.Printf(&quot;%s with %v bytes downloaded&quot;, fileName, count)
} else if *cliVersion {
/*
lookup version flag&#39;s usage text
*/
fmt.Println(flag.Lookup(&quot;version&quot;).Usage)
} else if *cliHelp {
/*
lookup help flag&#39;s usage text
*/
fmt.Println(flag.Lookup(&quot;help&quot;).Usage)
} else {
/*
using help&#39;s usage text for handling other status
*/
fmt.Println(flag.Lookup(&quot;help&quot;).Usage)
}
}

While the my program is running:

Downloading file
200 OK

After download working progressbar:

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

My progressbar code below:

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

How can I solve progressbar problem?

答案1

得分: 8

更多的goroutine是不需要的。只需要从bar中读取。

// 开始新的进度条
bar := pb.New(fileSize).SetUnits(pb.U_BYTES)
bar.Start()
// 创建代理读取器
rd := bar.NewProxyReader(response.Body)
// 从读取器中复制数据
io.Copy(file, rd)
英文:

More goroutines is not needed. Just read from bar

<!-- language: golang -->

// 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)

答案2

得分: 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。这可能更好,因为你可以避免使用 sleep 调用。

英文:

I wrote the below stuff, and it's right in the general case, unrelated to progressbar, but this library is exactly designed to handle this problem, has specialized support for it, and gives an explicit example of downloading a file.


You need to run the download in parallel with updating the progressbar, currently you're downloading the whole file and then updating the progressbar.

This is a little sloppy, but should get you going in the right direction:

First, grab the expected filesize:

	filesize := response.ContentLength

Then start your download in a goroutine:

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

Then update your progressbar as it goes along:

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

Like I say, this is a little sloppy; you probably want to scale the bar better depending on the size of the file, and the log.Fatal calls are ugly. But it handles the core of the issue.

Alternately, you can do this without a goroutine just by writing your own version of io.Copy. Read a block from response.Body, update the progress bar, and then write a block to file. That's arguably better because you can avoid the sleep call.

答案3

得分: 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,它会将当前的进度替换为更新后的进度,从而创建出动态效果。

你可以在这里找到详细的解释。

英文:

Actually you can implement progress bar by yourself with below piece of code.

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

The key here is to use escape \r which will replace the current progress with updated one in place which creates a dynamic effect.

A detailed explanation can be found at here.

huangapple
  • 本文由 发表于 2015年5月29日 23:00:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/30532886.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定