Golang动态进度条

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

golang dynamic progressbar

问题

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

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

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

以下是我的代码:

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/url"
  8. "os"
  9. "strings"
  10. "github.com/cheggaaa/pb"
  11. "time"
  12. )
  13. var (
  14. usage = "Usage: ./gofret -url=http://some/do.zip"
  15. version = "Version: 0.1"
  16. help = fmt.Sprintf("\n\n %s\n\n\n %s", usage, version)
  17. cliUrl *string
  18. cliVersion *bool
  19. cliHelp *bool
  20. )
  21. func init() {
  22. cliUrl = flag.String("url", "", usage)
  23. cliVersion = flag.Bool("version", false, version)
  24. cliHelp = flag.Bool("help", false, help)
  25. }
  26. func main() {
  27. flag.Parse()
  28. if *cliUrl != "" {
  29. fmt.Println("Downloading file")
  30. fileUrl, err := url.Parse(*cliUrl)
  31. if err != nil {
  32. panic(err)
  33. }
  34. filePath := fileUrl.Path
  35. segments := strings.Split(filePath, "/")
  36. fileName := segments[len(segments)-1]
  37. file, err := os.Create(fileName)
  38. if err != nil {
  39. fmt.Println(err)
  40. panic(err)
  41. }
  42. defer file.Close()
  43. checkStatus := http.Client{
  44. CheckRedirect: func(r *http.Request, via []*http.Request) error {
  45. r.URL.Opaque = r.URL.Path
  46. return nil
  47. },
  48. }
  49. response, err := checkStatus.Get(*cliUrl)
  50. if err != nil {
  51. fmt.Println(err)
  52. panic(err)
  53. }
  54. defer response.Body.Close()
  55. fmt.Println(response.Status)
  56. fileSize, err := io.Copy(file, response.Body)
  57. var countSize int = int(fileSize/1000)
  58. count := countSize
  59. bar := pb.StartNew(count)
  60. for i := 0; i < count; i++ {
  61. bar.Increment()
  62. time.Sleep(time.Millisecond)
  63. }
  64. bar.FinishPrint("The End!")
  65. if err != nil {
  66. panic(err)
  67. }
  68. fmt.Printf("%s with %v bytes downloaded", fileName, count)
  69. } else if *cliVersion {
  70. fmt.Println(flag.Lookup("version").Usage)
  71. } else if *cliHelp {
  72. fmt.Println(flag.Lookup("help").Usage)
  73. } else {
  74. fmt.Println(flag.Lookup("help").Usage)
  75. }
  76. }

在程序运行时:

  1. Downloading file
  2. 200 OK

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

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

我的进度条代码如下:

  1. var countSize int = int(fileSize/1000)
  2. count := countSize
  3. bar := pb.StartNew(count)
  4. for i := 0; i < count; i++ {
  5. bar.Increment()
  6. time.Sleep(time.Millisecond)
  7. }
  8. 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

  1. import (
  2. &quot;flag&quot;
  3. &quot;fmt&quot;
  4. &quot;io&quot;
  5. &quot;net/http&quot;
  6. &quot;net/url&quot;
  7. &quot;os&quot;
  8. &quot;strings&quot;
  9. &quot;github.com/cheggaaa/pb&quot;
  10. &quot;time&quot;
  11. )
  12. /*
  13. usage = usage text
  14. version = current number
  15. help use Sprintf
  16. *cliUrl from cmd
  17. *cliVersion from cmd
  18. *cliHelp * from cmd
  19. */
  20. var (
  21. usage = &quot;Usage: ./gofret -url=http://some/do.zip&quot;
  22. version = &quot;Version: 0.1&quot;
  23. help = fmt.Sprintf(&quot;\n\n %s\n\n\n %s&quot;, usage, version)
  24. cliUrl *string
  25. cliVersion *bool
  26. cliHelp *bool
  27. )
  28. func init() {
  29. /*
  30. if *cliUrl != &quot;&quot; {
  31. fmt.Println(*cliUrl)
  32. }
  33. ./gofret -url=http://somesite.com/somefile.zip
  34. ./gofret -url=https://github.com/aligoren/syspy/archive/master.zip
  35. */
  36. cliUrl = flag.String(&quot;url&quot;, &quot;&quot;, usage)
  37. /*
  38. else if *cliVersion{
  39. fmt.Println(flag.Lookup(&quot;version&quot;).Usage)
  40. }
  41. ./gofret -version
  42. */
  43. cliVersion = flag.Bool(&quot;version&quot;, false, version)
  44. /*
  45. if *cliHelp {
  46. fmt.Println(flag.Lookup(&quot;help&quot;).Usage)
  47. }
  48. ./gofret -help
  49. */
  50. cliHelp = flag.Bool(&quot;help&quot;, false, help)
  51. }
  52. func main() {
  53. /*
  54. Parse all flags
  55. */
  56. flag.Parse()
  57. if *cliUrl != &quot;&quot; {
  58. fmt.Println(&quot;Downloading file&quot;)
  59. /* parse url from *cliUrl */
  60. fileUrl, err := url.Parse(*cliUrl)
  61. if err != nil {
  62. panic(err)
  63. }
  64. /* get path from *cliUrl */
  65. filePath := fileUrl.Path
  66. /*
  67. seperate file.
  68. http://+site.com/+(file.zip)
  69. */
  70. segments := strings.Split(filePath, &quot;/&quot;)
  71. /*
  72. file.zip filename lenth -1
  73. */
  74. fileName := segments[len(segments)-1]
  75. /*
  76. Create new file.
  77. Filename from fileName variable
  78. */
  79. file, err := os.Create(fileName)
  80. if err != nil {
  81. fmt.Println(err)
  82. panic(err)
  83. }
  84. defer file.Close()
  85. /*
  86. check status and CheckRedirect
  87. */
  88. checkStatus := http.Client{
  89. CheckRedirect: func(r *http.Request, via []*http.Request) error {
  90. r.URL.Opaque = r.URL.Path
  91. return nil
  92. },
  93. }
  94. /*
  95. Get Response: 200 OK?
  96. */
  97. response, err := checkStatus.Get(*cliUrl)
  98. if err != nil {
  99. fmt.Println(err)
  100. panic(err)
  101. }
  102. defer response.Body.Close()
  103. fmt.Println(response.Status) // Example: 200 OK
  104. /*
  105. fileSize example: 12572 bytes
  106. */
  107. fileSize, err := io.Copy(file, response.Body)
  108. /*
  109. progressbar worked after download :(
  110. */
  111. var countSize int = int(fileSize/1000)
  112. count := countSize
  113. bar := pb.StartNew(count)
  114. for i := 0; i &lt; count; i++ {
  115. bar.Increment()
  116. time.Sleep(time.Millisecond)
  117. }
  118. bar.FinishPrint(&quot;The End!&quot;)
  119. if err != nil {
  120. panic(err)
  121. }
  122. fmt.Printf(&quot;%s with %v bytes downloaded&quot;, fileName, count)
  123. } else if *cliVersion {
  124. /*
  125. lookup version flag&#39;s usage text
  126. */
  127. fmt.Println(flag.Lookup(&quot;version&quot;).Usage)
  128. } else if *cliHelp {
  129. /*
  130. lookup help flag&#39;s usage text
  131. */
  132. fmt.Println(flag.Lookup(&quot;help&quot;).Usage)
  133. } else {
  134. /*
  135. using help&#39;s usage text for handling other status
  136. */
  137. fmt.Println(flag.Lookup(&quot;help&quot;).Usage)
  138. }
  139. }

While the my program is running:

  1. Downloading file
  2. 200 OK

After download working progressbar:

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

My progressbar code below:

  1. /*
  2. progressbar worked after download :(
  3. */
  4. var countSize int = int(fileSize/1000)
  5. count := countSize
  6. bar := pb.StartNew(count)
  7. for i := 0; i &lt; count; i++ {
  8. bar.Increment()
  9. time.Sleep(time.Millisecond)
  10. }
  11. bar.FinishPrint(&quot;The End!&quot;)

How can I solve progressbar problem?

答案1

得分: 8

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

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

More goroutines is not needed. Just read from bar

<!-- language: golang -->

  1. // start new bar
  2. bar := pb.New(fileSize).SetUnits(pb.U_BYTES)
  3. bar.Start()
  4. // create proxy reader
  5. rd := bar.NewProxyReader(response.Body)
  6. // and copy from reader
  7. io.Copy(file, rd)

答案2

得分: 5

我写了下面的内容,它在一般情况下是正确的,与进度条无关,但这个库恰好设计用来处理这个问题,它有专门的支持,并提供了一个下载文件的示例


你需要在更新进度条的同时并行运行下载,目前你是先下载整个文件,然后再更新进度条。

下面是一个简单的示例,可以帮助你朝着正确的方向前进:

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

  1. filesize := response.ContentLength

然后在一个 goroutine 中开始下载:

  1. go func() {
  2. n, err := io.Copy(file, response.Body)
  3. if n != filesize {
  4. log.Fatal("Truncated")
  5. }
  6. if err != nil {
  7. log.Fatalf("Error: %v", err)
  8. }
  9. }()

然后在下载过程中更新进度条:

  1. countSize := int(filesize / 1000)
  2. bar := pb.StartNew(countSize)
  3. var fi os.FileInfo
  4. for fi == nil || fi.Size() < filesize {
  5. fi, _ = file.Stat()
  6. bar.Set(int(fi.Size() / 1000))
  7. time.Sleep(time.Millisecond)
  8. }
  9. 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:

  1. filesize := response.ContentLength

Then start your download in a goroutine:

  1. go func() {
  2. n, err := io.Copy(file, response.Body)
  3. if n != filesize {
  4. log.Fatal(&quot;Truncated&quot;)
  5. }
  6. if err != nil {
  7. log.Fatalf(&quot;Error: %v&quot;, err)
  8. }
  9. }()

Then update your progressbar as it goes along:

  1. countSize := int(filesize / 1000)
  2. bar := pb.StartNew(countSize)
  3. var fi os.FileInfo
  4. for fi == nil || fi.Size() &lt; filesize {
  5. fi, _ = file.Stat()
  6. bar.Set(int(fi.Size() / 1000))
  7. time.Sleep(time.Millisecond)
  8. }
  9. 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

实际上,你可以使用下面的代码自己实现进度条。

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

这里的关键是使用转义字符\r,它会将当前的进度替换为更新后的进度,从而创建出动态效果。

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

英文:

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

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

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:

确定