diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index c23774cd..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}", - "env": {}, - "args": [] - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0c9921c3..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "go.useLanguageServer": true, - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, -} \ No newline at end of file diff --git a/cmd/cli/flags.go b/cmd/cli/flags.go new file mode 100644 index 00000000..40f0da22 --- /dev/null +++ b/cmd/cli/flags.go @@ -0,0 +1,42 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" +) + +type args struct { + url string + connections *int + dir *string +} + +func parse() *args { + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + panic(err) + } + var args args + args.connections = flag.Int("C", 16, "Concurrent connections.") + args.dir = flag.String("D", dir, "Save directory.") + flag.Parse() + t := flag.Args() + if len(t) > 0 { + args.url = t[0] + } else { + gPrintln("missing url parameter") + gPrintln("try 'gopeed -h' for more information") + os.Exit(1) + } + return &args +} + +func gPrint(msg string) { + fmt.Print("gopeed: " + msg) +} + +func gPrintln(msg string) { + gPrint(msg + "\n") +} diff --git a/cmd/cli/gopeed.go b/cmd/cli/gopeed.go new file mode 100644 index 00000000..04f3d464 --- /dev/null +++ b/cmd/cli/gopeed.go @@ -0,0 +1,64 @@ +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/monkeyWie/gopeed-core/util" + "path/filepath" + "sync" +) + +const progressWidth = 20 + +func main() { + args := parse() + + downloader := download.NewDownloader(http.FetcherBuilder) + res, err := downloader.Resolve(&base.Request{ + URL: args.url, + }) + if err != nil { + panic(err) + } + + err = downloader.Create(res, &base.Options{ + Path: *args.dir, + Connections: *args.connections, + }) + 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 { + printProgress(taskInfo, "downloading...") + } + if eventKey == base.EventKeyDone { + printProgress(taskInfo, "complete") + wg.Done() + } + }) + wg.Wait() + fmt.Println() + gPrint("saving file " + filepath.Join(*args.dir, res.Files[0].Name)) +} + +func printProgress(taskInfo *download.TaskInfo, title string) { + rate := float64(taskInfo.Progress.Downloaded) / float64(taskInfo.Res.TotalSize) + completeWidth := int(progressWidth * rate) + speed := util.ByteFmt(taskInfo.Progress.Speed) + totalSize := util.ByteFmt(taskInfo.Res.TotalSize) + fmt.Printf("\r%s [", title) + for i := 0; i < progressWidth; i++ { + if i < completeWidth { + fmt.Print("■") + } else { + fmt.Print("□") + } + } + fmt.Printf("] %.1f%% %s/s %s", rate*100, speed, totalSize) +} diff --git a/cmd/terminal/gopeed.go b/cmd/terminal/gopeed.go deleted file mode 100644 index 909fc326..00000000 --- a/cmd/terminal/gopeed.go +++ /dev/null @@ -1,64 +0,0 @@ -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: "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, - }) - 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 bf1e6204..4de61146 100644 --- a/download/base/constants.go +++ b/download/base/constants.go @@ -17,6 +17,7 @@ type EventKey string const ( EventKeyStart = "start" EventKeyPause = "pause" + EventKeyContinue = "continue" EventKeyProgress = "progress" EventKeyError = "error" EventKeyDone = "done" diff --git a/download/downloader.go b/download/downloader.go index bf639f8c..ae864556 100644 --- a/download/downloader.go +++ b/download/downloader.go @@ -121,7 +121,14 @@ func (d *Downloader) Create(res *base.Resource, opts *base.Options) (err error) d.emit(task, base.EventKeyError) } else { task.Progress.Used = task.timer.Used() - task.Progress.Speed = task.Res.TotalSize / (task.Progress.Used / int64(time.Second)) + if task.Res.TotalSize == 0 { + task.Res.TotalSize = task.fetcher.Progress().TotalDownloaded() + } + used := task.Progress.Used / int64(time.Second) + if used == 0 { + used = 1 + } + task.Progress.Speed = task.Res.TotalSize / used task.Progress.Downloaded = task.Res.TotalSize d.emit(d.tasks[id], base.EventKeyDone) } @@ -144,7 +151,7 @@ func (d *Downloader) Continue(id string) { defer task.locker.Unlock() task.timer.Continue() task.fetcher.Continue() - d.emit(task, base.EventKeyStart) + d.emit(task, base.EventKeyContinue) } func (d *Downloader) Listener(fn func(taskInfo *TaskInfo, eventKey base.EventKey)) { diff --git a/download/http/fetcher.go b/download/http/fetcher.go index 983ee875..4d4835f1 100644 --- a/download/http/fetcher.go +++ b/download/http/fetcher.go @@ -16,6 +16,7 @@ import ( "path" "path/filepath" "strconv" + "strings" "time" ) @@ -117,8 +118,8 @@ func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) { } } // Get file filename by URL - if file.Name == "" { - file.Name = path.Base(req.URL) + if file.Name == "" && strings.Count(req.URL, "/") > 2 { + file.Name = filepath.Base(req.URL) } // unknown file filename if file.Name == "" { diff --git a/go.mod b/go.mod index 6eb1af3b..b9de5cff 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.13 require ( github.com/RoaringBitmap/roaring v0.4.21 github.com/cenkalti/mse v0.0.0-20140930130441-6ef65f170972 + github.com/gizak/termui/v3 v3.1.0 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 9dc4009e..8555b33e 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc= +github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= @@ -26,11 +28,17 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc 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.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k= 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/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 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/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840= +github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -48,11 +56,10 @@ github.com/superhawk610/terminal v0.1.0 h1:OpWp0+861M3l5j4ZWprm8q0fgG9Y2xXZp5tku 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= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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= diff --git a/util/bytefmt.go b/util/bytefmt.go new file mode 100644 index 00000000..9026fc3e --- /dev/null +++ b/util/bytefmt.go @@ -0,0 +1,23 @@ +package util + +import ( + "fmt" + "math" +) + +var unitArr = []string{"B", "KB", "MB", "GB", "TB", "EB"} + +func ByteFmt(size int64) string { + if size == 0 { + return "unknown" + } + fs := float64(size) + p := int(math.Log(fs) / math.Log(1024)) + val := fs / math.Pow(1024, float64(p)) + _, frac := math.Modf(val) + if frac > 0 { + return fmt.Sprintf("%.1f%s", math.Floor(val*10)/10, unitArr[p]) + } else { + return fmt.Sprintf("%d%s", int(val), unitArr[p]) + } +} diff --git a/util/bytefmt_test.go b/util/bytefmt_test.go new file mode 100644 index 00000000..88f90a02 --- /dev/null +++ b/util/bytefmt_test.go @@ -0,0 +1,57 @@ +package util + +import "testing" + +func TestByteFmt(t *testing.T) { + type args struct { + size int64 + } + tests := []struct { + name string + args args + want string + }{ + { + name: "100B", + args: args{size: int64(100)}, + want: "100B", + }, + { + name: "1KB", + args: args{size: int64(1024)}, + want: "1KB", + }, + { + name: "1.9KB", + args: args{size: int64(1024*2 - 1)}, + want: "1.9KB", + }, + { + name: "2KB", + args: args{size: int64(1024 * 2)}, + want: "2KB", + }, + { + name: "1MB", + args: args{size: int64(1024 * 1024)}, + want: "1MB", + }, + { + name: "1.9MB", + args: args{size: int64(1024*1024*2 - 1)}, + want: "1.9MB", + }, + { + name: "2MB", + args: args{size: int64(1024 * 1024 * 2)}, + want: "2MB", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ByteFmt(tt.args.size); got != tt.want { + t.Errorf("ByteFmt() = %v, want %v", got, tt.want) + } + }) + } +}