feat: integration torrent (#30)

This commit is contained in:
monkeyWie authored and GitHub committed 2022-08-11 14:38:16 +08:00
1 parent 43378c7dc8
commit 034137322f
20 files changed
+925 -139

No files matched your search

+2 -1
View File
@@ -5,7 +5,8 @@ import "errors"
type Status int
const (
DownloadStatusReady Status = iota
DownloadStatusPrepare Status = iota
DownloadStatusReady
DownloadStatusStart
DownloadStatusPause
DownloadStatusError
+5 -1
View File
@@ -2,6 +2,8 @@ package base
// 下载请求
type Request struct {
// 下载链接
Schema string
// 下载链接
URL string
// 附加信息
@@ -12,7 +14,7 @@ type Request struct {
type Resource struct {
Req *Request
// 资源总大小
TotalSize int64
Length int64
// 是否支持断点下载
Range bool
// 资源所包含的文件列表
@@ -33,4 +35,6 @@ type Options struct {
Path string
// 并发连接数
Connections int
// 下载的文件下标列表
SelectFiles []int
}
+47 -59
View File
@@ -2,34 +2,19 @@ package download
import (
"errors"
"github.com/google/uuid"
"github.com/monkeyWie/gopeed-core/internal/controller"
"github.com/monkeyWie/gopeed-core/internal/fetcher"
"github.com/monkeyWie/gopeed-core/internal/protocol/bt"
"github.com/monkeyWie/gopeed-core/internal/protocol/http"
"github.com/monkeyWie/gopeed-core/pkg/base"
"github.com/monkeyWie/gopeed-core/pkg/util"
"net/url"
"strings"
"sync"
"time"
)
type FetcherBuilder func() (protocols []string, builder func() fetcher.Fetcher)
type Listener func(event *Event)
type TaskInfo struct {
ID string
Res *base.Resource
Opts *base.Options
Status base.Status
Files map[string]*base.FileInfo
Progress *Progress
fetcher fetcher.Fetcher
timer *util.Timer
locker *sync.Mutex
}
type Progress struct {
// 下载耗时(纳秒)
Used int64
@@ -41,28 +26,28 @@ type Progress struct {
type Downloader struct {
*controller.DefaultController
fetchBuilders map[string]func() fetcher.Fetcher
tasks map[string]*TaskInfo
fetchBuilders map[string]fetcher.FetcherBuilder
tasks map[string]*Task
listener Listener
}
func NewDownloader(fbs ...FetcherBuilder) *Downloader {
func NewDownloader(fbs ...fetcher.FetcherBuilder) *Downloader {
d := &Downloader{DefaultController: controller.NewController()}
d.fetchBuilders = make(map[string]func() fetcher.Fetcher)
d.fetchBuilders = make(map[string]fetcher.FetcherBuilder)
for _, f := range fbs {
protocols, builder := f()
for _, p := range protocols {
d.fetchBuilders[strings.ToUpper(p)] = builder
for _, p := range f.Schemes() {
d.fetchBuilders[strings.ToUpper(p)] = f
}
}
d.tasks = make(map[string]*TaskInfo)
d.tasks = make(map[string]*Task)
// 每秒统计一次下载速度
go func() {
for {
if len(d.tasks) > 0 {
for _, task := range d.tasks {
if task.Status == base.DownloadStatusDone ||
if task.Status == base.DownloadStatusPrepare ||
task.Status == base.DownloadStatusDone ||
task.Status == base.DownloadStatusError ||
task.Status == base.DownloadStatusPause {
continue
@@ -80,31 +65,41 @@ func NewDownloader(fbs ...FetcherBuilder) *Downloader {
return d
}
func (d *Downloader) buildFetcher(URL string) (fetcher.Fetcher, error) {
url, err := url.Parse(URL)
if err != nil {
return nil, err
func (d *Downloader) buildFetcher(url string) (fetcher.Fetcher, error) {
schema := util.ParseSchema(url)
fetchBuilder, ok := d.fetchBuilders[schema]
if !ok {
fetchBuilder, ok = d.fetchBuilders[util.FileSchema]
}
if fetchBuilder, ok := d.fetchBuilders[strings.ToUpper(url.Scheme)]; ok {
fetcher := fetchBuilder()
if ok {
fetcher := fetchBuilder.Build()
fetcher.Setup(d.DefaultController)
return fetcher, nil
}
return nil, errors.New("unsupported protocol")
}
func (d *Downloader) Resolve(req *base.Request) (*base.Resource, error) {
func (d *Downloader) Resolve(req *base.Request) (string, *base.Resource, error) {
fetcher, err := d.buildFetcher(req.URL)
if err != nil {
return nil, err
return "", nil, err
}
return fetcher.Resolve(req)
res, err := fetcher.Resolve(req)
if err != nil {
return "", nil, err
}
task := NewTask()
task.fetcher = fetcher
task.Res = res
d.tasks[task.ID] = task
return task.ID, res, nil
}
func (d *Downloader) Create(res *base.Resource, opts *base.Options) (err error) {
fetcher, err := d.buildFetcher(res.Req.URL)
if err != nil {
return
func (d *Downloader) Create(taskID string, res *base.Resource, opts *base.Options) (err error) {
task := d.tasks[taskID]
fetcher := d.tasks[taskID].fetcher
if opts == nil {
opts = &base.Options{}
}
if !res.Range || opts.Connections < 1 {
opts.Connections = 1
@@ -113,18 +108,11 @@ func (d *Downloader) Create(res *base.Resource, opts *base.Options) (err error)
if err != nil {
return
}
id := uuid.New().String()
task := &TaskInfo{
ID: id,
Res: res,
Opts: opts,
Status: base.DownloadStatusReady,
Progress: &Progress{},
fetcher: fetcher,
timer: &util.Timer{},
locker: new(sync.Mutex),
}
d.tasks[id] = task
task.Opts = opts
task.Status = base.DownloadStatusReady
task.Progress = &Progress{}
task.timer = &util.Timer{}
task.locker = new(sync.Mutex)
task.timer.Start()
d.emit(EventKeyStart, task)
err = fetcher.Start()
@@ -137,15 +125,15 @@ func (d *Downloader) Create(res *base.Resource, opts *base.Options) (err error)
d.emit(EventKeyError, task, err)
} else {
task.Progress.Used = task.timer.Used()
if task.Res.TotalSize == 0 {
task.Res.TotalSize = task.fetcher.Progress().TotalDownloaded()
if task.Res.Length == 0 {
task.Res.Length = 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
task.Progress.Speed = task.Res.Length / used
task.Progress.Downloaded = task.Res.Length
d.emit(EventKeyDone, task)
}
d.emit(EventKeyFinally, task, err)
@@ -175,7 +163,7 @@ func (d *Downloader) Listener(fn Listener) {
d.listener = fn
}
func (d *Downloader) emit(eventKey EventKey, task *TaskInfo, errs ...error) {
func (d *Downloader) emit(eventKey EventKey, task *Task, errs ...error) {
if d.listener != nil {
var err error
if len(errs) > 0 {
@@ -189,7 +177,7 @@ func (d *Downloader) emit(eventKey EventKey, task *TaskInfo, errs ...error) {
}
}
var defaultDownloader = NewDownloader(http.FetcherBuilder)
var defaultDownloader = NewDownloader(new(http.FetcherBuilder), new(bt.FetcherBuilder))
type boot struct {
url string
@@ -207,7 +195,7 @@ func (b *boot) Extra(extra interface{}) *boot {
return b
}
func (b *boot) Resolve() (*base.Resource, error) {
func (b *boot) Resolve() (string, *base.Resource, error) {
return defaultDownloader.Resolve(&base.Request{
URL: b.url,
Extra: b.extra,
@@ -220,12 +208,12 @@ func (b *boot) Listener(listener Listener) *boot {
}
func (b *boot) Create(opts *base.Options) error {
res, err := b.Resolve()
taskID, res, err := b.Resolve()
if err != nil {
return err
}
defaultDownloader.Listener(b.listener)
return defaultDownloader.Create(res, opts)
return defaultDownloader.Create(taskID, res, opts)
}
func Boot() *boot {
+8 -8
View File
@@ -13,18 +13,18 @@ func TestDownloader_Resolve(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
downloader := NewDownloader(http.FetcherBuilder)
downloader := NewDownloader(new(http.FetcherBuilder))
req := &base.Request{
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
}
res, err := downloader.Resolve(req)
_, res, err := downloader.Resolve(req)
if err != nil {
t.Fatal(err)
}
want := &base.Resource{
Req: req,
TotalSize: test.BuildSize,
Range: true,
Req: req,
Length: test.BuildSize,
Range: true,
Files: []*base.FileInfo{
{
Name: test.BuildName,
@@ -42,11 +42,11 @@ func TestDownloader_Create(t *testing.T) {
listener := test.StartTestFileServer()
defer listener.Close()
downloader := NewDownloader(http.FetcherBuilder)
downloader := NewDownloader(new(http.FetcherBuilder))
req := &base.Request{
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
}
res, err := downloader.Resolve(req)
taskID, res, err := downloader.Resolve(req)
if err != nil {
t.Fatal(err)
}
@@ -58,7 +58,7 @@ func TestDownloader_Create(t *testing.T) {
wg.Done()
}
})
err = downloader.Create(res, &base.Options{
err = downloader.Create(taskID, res, &base.Options{
Path: test.Dir,
Name: test.DownloadName,
Connections: 4,
+1 -1
View File
@@ -14,6 +14,6 @@ const (
type Event struct {
Key EventKey
Task *TaskInfo
Task *Task
Err error
}
+29
View File
@@ -0,0 +1,29 @@
package download
import (
"github.com/google/uuid"
"github.com/monkeyWie/gopeed-core/internal/fetcher"
"github.com/monkeyWie/gopeed-core/pkg/base"
"github.com/monkeyWie/gopeed-core/pkg/util"
"sync"
)
type Task struct {
ID string
Res *base.Resource
Opts *base.Options
Status base.Status
Files map[string]*base.FileInfo
Progress *Progress
fetcher fetcher.Fetcher
timer *util.Timer
locker *sync.Mutex
}
func NewTask() *Task {
return &Task{
ID: uuid.New().String(),
Status: base.DownloadStatusPrepare,
}
}
+13
View File
@@ -0,0 +1,13 @@
package util
import "strings"
const FileSchema = "FILE"
func ParseSchema(url string) string {
index := strings.Index(url, ":")
if index == -1 {
return FileSchema
}
return strings.ToUpper(url[:index])
}