mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-14 04:13:43 +00:00
feat: integration torrent (#30)
This commit is contained in:
1 parent
43378c7dc8
commit
034137322f
20 files changed
+925
-139
No files matched your search
@@ -5,7 +5,8 @@ import "errors"
|
||||
type Status int
|
||||
|
||||
const (
|
||||
DownloadStatusReady Status = iota
|
||||
DownloadStatusPrepare Status = iota
|
||||
DownloadStatusReady
|
||||
DownloadStatusStart
|
||||
DownloadStatusPause
|
||||
DownloadStatusError
|
||||
|
||||
+5
-1
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,6 +14,6 @@ const (
|
||||
|
||||
type Event struct {
|
||||
Key EventKey
|
||||
Task *TaskInfo
|
||||
Task *Task
|
||||
Err error
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
Reference in new issue
Block a user