From 046729bfaa15dc2d8f0b47df2d17574792feb60d Mon Sep 17 00:00:00 2001 From: liwei Date: Tue, 23 Mar 2021 19:03:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=B7=A5?= =?UTF-8?q?=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/terminal/gopeed.go | 42 +++++++++++++++- download/base/constants.go | 10 ++++ download/base/controller.go | 12 +++++ download/base/fetcher.go | 2 + download/base/timer.go | 25 ++++++++++ download/downloader.go | 98 ++++++++++++++++++++++++++----------- download/downloader_test.go | 4 +- download/http/fetcher.go | 12 +++++ go.mod | 4 ++ go.sum | 17 +++++++ 10 files changed, 195 insertions(+), 31 deletions(-) create mode 100644 download/base/timer.go diff --git a/cmd/terminal/gopeed.go b/cmd/terminal/gopeed.go index ef963a57..909fc326 100644 --- a/cmd/terminal/gopeed.go +++ b/cmd/terminal/gopeed.go @@ -1,19 +1,39 @@ package main import ( + "fmt" "github.com/monkeyWie/gopeed-core/download" "github.com/monkeyWie/gopeed-core/download/base" "github.com/monkeyWie/gopeed-core/download/http" + "github.com/superhawk610/bar" + "github.com/ttacon/chalk" + "strconv" + "sync" ) func main() { + b := bar.NewWithOpts( + bar.WithDimensions(10000, 20), + bar.WithDisplay("[", "■", "■", "□", "]"), + bar.WithFormat( + fmt.Sprintf( + " %s:status%s :percent :bar%s :speedKB/s%s ", + chalk.Green, + chalk.Reset, + chalk.Blue, + chalk.Reset, + ), + ), + ) + downloader := download.NewDownloader(http.FetcherBuilder) res, err := downloader.Resolve(&base.Request{ - URL: "http://192.168.200.163:8088/docker-compose", + URL: "https://dldir1.qq.com/weixin/Windows/WeChatSetup.exe", }) if err != nil { panic(err) } + err = downloader.Create(res, &base.Options{ Path: "E:\\test\\gopeed\\http", Connections: 4, @@ -21,4 +41,24 @@ func main() { if err != nil { panic(err) } + + var wg sync.WaitGroup + wg.Add(1) + downloader.Listener(func(taskInfo *download.TaskInfo, eventKey base.EventKey) { + if eventKey == base.EventKeyProgress { + b.Update(int(10000*(float64(taskInfo.Progress.Downloaded)/float64(taskInfo.Res.TotalSize))), bar.Context{ + bar.Ctx("status", "downloading..."), + bar.Ctx("speed", strconv.Itoa(int(taskInfo.Progress.Speed/1024))), + }) + } + if eventKey == base.EventKeyDone { + b.Update(10000, bar.Context{ + bar.Ctx("status", "complete"), + bar.Ctx("speed", strconv.Itoa(int(taskInfo.Progress.Speed/1024))), + }) + wg.Done() + } + }) + wg.Wait() + b.Done() } diff --git a/download/base/constants.go b/download/base/constants.go index 6059a3e7..bf1e6204 100644 --- a/download/base/constants.go +++ b/download/base/constants.go @@ -12,6 +12,16 @@ const ( DownloadStatusDone ) +type EventKey string + +const ( + EventKeyStart = "start" + EventKeyPause = "pause" + EventKeyProgress = "progress" + EventKeyError = "error" + EventKeyDone = "done" +) + const ( HttpCodeOK = 200 HttpCodePartialContent = 206 diff --git a/download/base/controller.go b/download/base/controller.go index 4a6e096d..33a24f7e 100644 --- a/download/base/controller.go +++ b/download/base/controller.go @@ -142,3 +142,15 @@ func (d *DialerWarp) Dial(network, addr string) (c net.Conn, err error) { } return &ConnWarp{conn: conn}, nil } + +// 获取任务中各个文件的已下载字节数 +type Progress []int64 + +// TotalDownloaded 获取任务总下载字节数 +func (p Progress) TotalDownloaded() int64 { + total := int64(0) + for _, d := range p { + total += d + } + return total +} diff --git a/download/base/fetcher.go b/download/base/fetcher.go index 576d255d..2dd879a8 100644 --- a/download/base/fetcher.go +++ b/download/base/fetcher.go @@ -11,6 +11,8 @@ type Fetcher interface { Pause() (err error) Continue() (err error) + // 获取任务下载进度 + Progress() Progress // 该方法会一直阻塞,直到任务下载结束 Wait() (err error) } diff --git a/download/base/timer.go b/download/base/timer.go new file mode 100644 index 00000000..2f3bf617 --- /dev/null +++ b/download/base/timer.go @@ -0,0 +1,25 @@ +package base + +import "time" + +// 计时器 +type Timer struct { + t int64 + used int64 +} + +func (t *Timer) Start() { + t.t = time.Now().UnixNano() +} + +func (t *Timer) Pause() { + t.used += time.Now().UnixNano() - t.t +} + +func (t *Timer) Continue() { + t.t = time.Now().UnixNano() +} + +func (t *Timer) Used() int64 { + return (time.Now().UnixNano() - t.t) + t.used +} diff --git a/download/downloader.go b/download/downloader.go index 396e5334..bf639f8c 100644 --- a/download/downloader.go +++ b/download/downloader.go @@ -7,16 +7,19 @@ import ( "net/url" "strings" "sync" + "time" ) type TaskInfo struct { - ID string - Res *base.Resource - Opts *base.Options - Status base.Status - Files map[string]*base.FileInfo + ID string + Res *base.Resource + Opts *base.Options + Status base.Status + Files map[string]*base.FileInfo + Progress *Progress fetcher base.Fetcher + timer *base.Timer locker *sync.Mutex } @@ -26,7 +29,7 @@ type Downloader struct { *base.DefaultController fetchBuilders map[string]func() base.Fetcher tasks map[string]*TaskInfo - listener func(taskInfo *TaskInfo, status base.Status) + listener func(taskInfo *TaskInfo, eventKey base.EventKey) } func NewDownloader(fbs ...FetcherBuilder) *Downloader { @@ -39,6 +42,27 @@ func NewDownloader(fbs ...FetcherBuilder) *Downloader { } } d.tasks = make(map[string]*TaskInfo) + + // 每秒统计一次下载速度 + go func() { + for { + if len(d.tasks) > 0 { + for _, task := range d.tasks { + if task.Status == base.DownloadStatusDone || + task.Status == base.DownloadStatusError || + task.Status == base.DownloadStatusPause { + continue + } + current := task.fetcher.Progress().TotalDownloaded() + task.Progress.Used = task.timer.Used() + task.Progress.Speed = current - task.Progress.Downloaded + task.Progress.Downloaded = current + d.emit(task, base.EventKeyProgress) + } + } + time.Sleep(time.Second) + } + }() return d } @@ -73,26 +97,33 @@ func (d *Downloader) Create(res *base.Resource, opts *base.Options) (err error) return } id := uuid.New().String() - d.tasks[id] = &TaskInfo{ - ID: id, - Res: res, - Opts: opts, - Status: base.DownloadStatusReady, - fetcher: fetcher, - locker: new(sync.Mutex), + task := &TaskInfo{ + ID: id, + Res: res, + Opts: opts, + Status: base.DownloadStatusReady, + Progress: &Progress{}, + fetcher: fetcher, + timer: &base.Timer{}, + locker: new(sync.Mutex), + } + d.tasks[id] = task + task.timer.Start() + d.emit(task, base.EventKeyStart) + err = fetcher.Start() + if err != nil { + d.emit(task, base.EventKeyError) + return } - d.emit(d.tasks[id], base.DownloadStatusStart) go func() { - err = fetcher.Start() - if err != nil { - d.emit(d.tasks[id], base.DownloadStatusError) - return - } err = fetcher.Wait() if err != nil { - d.emit(d.tasks[id], base.DownloadStatusError) + d.emit(task, base.EventKeyError) } else { - d.emit(d.tasks[id], base.DownloadStatusDone) + task.Progress.Used = task.timer.Used() + task.Progress.Speed = task.Res.TotalSize / (task.Progress.Used / int64(time.Second)) + task.Progress.Downloaded = task.Res.TotalSize + d.emit(d.tasks[id], base.EventKeyDone) } }() return @@ -102,24 +133,35 @@ func (d *Downloader) Pause(id string) { task := d.tasks[id] task.locker.Lock() defer task.locker.Unlock() - d.tasks[id].fetcher.Pause() - d.emit(d.tasks[id], base.DownloadStatusPause) + task.timer.Pause() + task.fetcher.Pause() + d.emit(task, base.EventKeyPause) } func (d *Downloader) Continue(id string) { task := d.tasks[id] task.locker.Lock() defer task.locker.Unlock() - d.tasks[id].fetcher.Continue() - d.emit(d.tasks[id], base.DownloadStatusStart) + task.timer.Continue() + task.fetcher.Continue() + d.emit(task, base.EventKeyStart) } -func (d *Downloader) Listener(fn func(taskInfo *TaskInfo, status base.Status)) { +func (d *Downloader) Listener(fn func(taskInfo *TaskInfo, eventKey base.EventKey)) { d.listener = fn } -func (d *Downloader) emit(taskInfo *TaskInfo, status base.Status) { +func (d *Downloader) emit(taskInfo *TaskInfo, eventKey base.EventKey) { if d.listener != nil { - d.listener(taskInfo, status) + d.listener(taskInfo, eventKey) } } + +type Progress struct { + // 下载耗时(纳秒) + Used int64 + // 每秒下载字节数 + Speed int64 + // 已下载的字节数 + Downloaded int64 +} diff --git a/download/downloader_test.go b/download/downloader_test.go index b661ab29..57ac7071 100644 --- a/download/downloader_test.go +++ b/download/downloader_test.go @@ -53,8 +53,8 @@ func TestDownloader_Create(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - downloader.Listener(func(taskInfo *TaskInfo, status base.Status) { - if status == base.DownloadStatusDone { + downloader.Listener(func(taskInfo *TaskInfo, eventKey base.EventKey) { + if eventKey == base.EventKeyDone { wg.Done() } }) diff --git a/download/http/fetcher.go b/download/http/fetcher.go index 647bd930..97b2e2a2 100644 --- a/download/http/fetcher.go +++ b/download/http/fetcher.go @@ -199,6 +199,18 @@ func (f *Fetcher) Continue() (err error) { return } +func (f *Fetcher) Progress() base.Progress { + p := make(base.Progress, 0) + if len(f.chunks) > 0 { + total := int64(0) + for _, chunk := range f.chunks { + total += chunk.Downloaded + } + p = append(p, total) + } + return p +} + func (f *Fetcher) filename() string { // 创建文件 var filename = f.opts.Name diff --git a/go.mod b/go.mod index 9f2474fb..6eb1af3b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,11 @@ require ( github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972 github.com/google/uuid v1.1.1 github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e + github.com/mattn/go-tty v0.0.3 // indirect github.com/sirupsen/logrus v1.4.2 + github.com/superhawk610/bar v0.0.2 + github.com/superhawk610/terminal v0.1.0 // indirect + github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c ) diff --git a/go.sum b/go.sum index 0ee9d75a..9dc4009e 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,13 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGi github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e h1:KMs6SK8iDSR1+ZzOK10L5wGPpWDByyvOe5nrqk51g2U= github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e/go.mod h1:+AHfJo5+69p+fjvMJTmYajNP9rFBHQcaTDFmuXRRATI= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= +github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= @@ -35,8 +42,14 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/superhawk610/bar v0.0.2 h1:Jf9+rXkpGgzJkpogpx+BGjC4EwkFZc8L7t8QBrmyfqY= +github.com/superhawk610/bar v0.0.2/go.mod h1:xc6t0MG+8Mbj9wnHBEdvX2TRtNIaqhM5fW3R+dF9vH8= +github.com/superhawk610/terminal v0.1.0 h1:OpWp0+861M3l5j4ZWprm8q0fgG9Y2xXZp5tkunvCm/M= +github.com/superhawk610/terminal v0.1.0/go.mod h1:NQ3EEKWSeofexUwENLX3lv4WR2qeGiLlbf/NveZ7ZAQ= github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -44,12 +57,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=