mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 23:57:54 +00:00
refactor: fetcher接口调整,返回channel来通知任务成功和失败
This commit is contained in:
1 parent
74d0e8e0bc
commit
79a93d5b50
5 files changed
+78
-66
No files matched your search
@@ -25,6 +25,5 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
PauseErr = errors.New("pause")
|
||||
DeleteErr = errors.New("delete")
|
||||
)
|
||||
@@ -2,7 +2,7 @@ package base
|
||||
|
||||
// 对应协议的下载支持
|
||||
type Fetcher interface {
|
||||
Init(ctl Controller)
|
||||
Setup(ctl Controller) (err <-chan error)
|
||||
// 解析请求
|
||||
Resolve(req *Request) (res *Resource, err error)
|
||||
// 创建任务
|
||||
@@ -13,9 +13,12 @@ type Fetcher interface {
|
||||
}
|
||||
|
||||
type DefaultFetcher struct {
|
||||
Ctl Controller
|
||||
Ctl Controller
|
||||
DoneCh chan error
|
||||
}
|
||||
|
||||
func (f *DefaultFetcher) Init(ctl Controller) {
|
||||
func (f *DefaultFetcher) Setup(ctl Controller) (doneCh <-chan error) {
|
||||
f.Ctl = ctl
|
||||
f.DoneCh = make(chan error, 1)
|
||||
return f.DoneCh
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (d *Downloader) buildFetcher(URL string) (base.Fetcher, error) {
|
||||
}
|
||||
if fetchBuilder, ok := d.fetchBuilders[strings.ToUpper(url.Scheme)]; ok {
|
||||
fetcher := fetchBuilder()
|
||||
fetcher.Init(d.DefaultController)
|
||||
fetcher.Setup(d.DefaultController)
|
||||
return fetcher, nil
|
||||
}
|
||||
return nil, errors.New("unsupported protocol")
|
||||
|
||||
+49
-46
@@ -40,14 +40,12 @@ type Fetcher struct {
|
||||
chunks []*model.Chunk
|
||||
|
||||
pauseCh chan interface{}
|
||||
doneCh chan error
|
||||
}
|
||||
|
||||
func NewFetcher() *Fetcher {
|
||||
return &Fetcher{
|
||||
DefaultFetcher: new(base.DefaultFetcher),
|
||||
pauseCh: make(chan interface{}),
|
||||
doneCh: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +112,11 @@ func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
||||
file.Name = filename
|
||||
}
|
||||
}
|
||||
// Get file name by URL
|
||||
// Get file filename by URL
|
||||
if file.Name == "" {
|
||||
file.Name = path.Base(req.URL)
|
||||
}
|
||||
// unknown file name
|
||||
// unknown file filename
|
||||
if file.Name == "" {
|
||||
file.Name = "unknown"
|
||||
}
|
||||
@@ -138,12 +136,11 @@ func (f *Fetcher) Create(res *base.Resource, opts *base.Options) error {
|
||||
|
||||
func (f *Fetcher) Start() (err error) {
|
||||
// 创建文件
|
||||
name := f.name()
|
||||
name := f.filename()
|
||||
_, err = f.Ctl.Touch(name, f.res.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Ctl.Close(name)
|
||||
f.status = base.DownloadStatusStart
|
||||
if f.res.Range {
|
||||
// 每个连接平均需要下载的分块大小
|
||||
@@ -157,9 +154,9 @@ func (f *Fetcher) Start() (err error) {
|
||||
)
|
||||
if i == f.opts.Connections-1 {
|
||||
// 最后一个分块需要保证把文件下载完
|
||||
end = f.res.Size
|
||||
end = f.res.Size - 1
|
||||
} else {
|
||||
end = begin + chunkSize
|
||||
end = begin + chunkSize - 1
|
||||
}
|
||||
chunk := model.NewChunk(begin, end)
|
||||
f.chunks[i] = chunk
|
||||
@@ -170,8 +167,8 @@ func (f *Fetcher) Start() (err error) {
|
||||
f.clients = make([]*http.Response, 1)
|
||||
f.chunks[0] = model.NewChunk(0, 0)
|
||||
}
|
||||
|
||||
return f.fetch()
|
||||
f.fetch()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Fetcher) Pause() (err error) {
|
||||
@@ -181,7 +178,6 @@ func (f *Fetcher) Pause() (err error) {
|
||||
f.status = base.DownloadStatusPause
|
||||
f.stop()
|
||||
<-f.pauseCh
|
||||
f.Ctl.Close(f.name())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -190,16 +186,16 @@ func (f *Fetcher) Continue() (err error) {
|
||||
return
|
||||
}
|
||||
f.status = base.DownloadStatusStart
|
||||
var name = f.name()
|
||||
var name = f.filename()
|
||||
_, err = f.Ctl.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Ctl.Close(name)
|
||||
return f.fetch()
|
||||
f.fetch()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Fetcher) name() string {
|
||||
func (f *Fetcher) filename() string {
|
||||
// 创建文件
|
||||
var filename = f.opts.Name
|
||||
if filename == "" {
|
||||
@@ -208,44 +204,42 @@ func (f *Fetcher) name() string {
|
||||
return filepath.Join(f.opts.Path, filename)
|
||||
}
|
||||
|
||||
func (f *Fetcher) fetch() (err error) {
|
||||
func (f *Fetcher) fetch() {
|
||||
errCh := make(chan error, f.opts.Connections)
|
||||
defer close(errCh)
|
||||
|
||||
for i := 0; i < f.opts.Connections; i++ {
|
||||
go func(i int) {
|
||||
errCh <- f.fetchChunk(i, f.name(), f.chunks[i])
|
||||
errCh <- f.fetchChunk(i, f.filename(), f.chunks[i])
|
||||
}(i)
|
||||
}
|
||||
|
||||
stopFlag := false
|
||||
for i := 0; i < f.opts.Connections; i++ {
|
||||
fetchErr := <-errCh
|
||||
if fetchErr != nil && !stopFlag {
|
||||
// 确保如果有暂停操作返回暂停
|
||||
if err == nil || err != base.PauseErr {
|
||||
err = fetchErr
|
||||
}
|
||||
if fetchErr != base.PauseErr {
|
||||
go func() {
|
||||
var err error
|
||||
stopFlag := false
|
||||
for i := 0; i < f.opts.Connections; i++ {
|
||||
fetchErr := <-errCh
|
||||
if fetchErr != nil && !stopFlag {
|
||||
// 有一个连接失败就立即终止下载
|
||||
err = fetchErr
|
||||
stopFlag = true
|
||||
f.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == base.PauseErr {
|
||||
// 下载停止,关闭文件句柄
|
||||
f.Ctl.Close(f.filename())
|
||||
|
||||
if f.status == base.DownloadStatusPause {
|
||||
f.pauseCh <- nil
|
||||
err = nil
|
||||
} else {
|
||||
f.status = base.DownloadStatusError
|
||||
if err != nil {
|
||||
f.status = base.DownloadStatusError
|
||||
} else {
|
||||
f.status = base.DownloadStatusDone
|
||||
}
|
||||
f.DoneCh <- err
|
||||
}
|
||||
} else {
|
||||
f.status = base.DownloadStatusDone
|
||||
}
|
||||
f.doneCh <- err
|
||||
return
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Fetcher) stop() {
|
||||
@@ -269,6 +263,14 @@ func (f *Fetcher) fetchChunk(index int, name string, chunk *model.Chunk) (err er
|
||||
)
|
||||
// 重试5次
|
||||
for i := 0; i < 5; i++ {
|
||||
// 如果下载完成直接返回
|
||||
if chunk.Status == base.DownloadStatusDone {
|
||||
return
|
||||
}
|
||||
// 如果已暂停直接跳出
|
||||
if f.status == base.DownloadStatusPause {
|
||||
break
|
||||
}
|
||||
var (
|
||||
resp *http.Response
|
||||
retry bool
|
||||
@@ -280,9 +282,6 @@ func (f *Fetcher) fetchChunk(index int, name string, chunk *model.Chunk) (err er
|
||||
chunk.Downloaded = 0
|
||||
}
|
||||
err = func() error {
|
||||
if f.status == base.DownloadStatusPause {
|
||||
return base.PauseErr
|
||||
}
|
||||
resp, err = client.Do(httpReq)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -295,9 +294,6 @@ func (f *Fetcher) fetchChunk(index int, name string, chunk *model.Chunk) (err er
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
if err == base.PauseErr {
|
||||
return err
|
||||
}
|
||||
// 请求失败重试
|
||||
continue
|
||||
}
|
||||
@@ -326,10 +322,17 @@ func (f *Fetcher) fetchChunk(index int, name string, chunk *model.Chunk) (err er
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
f.chunks[index].Status = base.DownloadStatusError
|
||||
|
||||
if f.status == base.DownloadStatusPause {
|
||||
chunk.Status = base.DownloadStatusPause
|
||||
} else if chunk.Downloaded >= chunk.End-chunk.Begin+1 {
|
||||
chunk.Status = base.DownloadStatusDone
|
||||
} else {
|
||||
f.chunks[index].Status = base.DownloadStatusDone
|
||||
if err != nil {
|
||||
chunk.Status = base.DownloadStatusError
|
||||
} else {
|
||||
chunk.Status = base.DownloadStatusDone
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -76,11 +76,8 @@ func TestFetcher_DownloadContinue(t *testing.T) {
|
||||
defer listener.Close()
|
||||
// 暂停继续
|
||||
downloadContinue(listener, 1, t)
|
||||
fmt.Println("d1")
|
||||
downloadContinue(listener, 5, t)
|
||||
fmt.Println("d5")
|
||||
downloadContinue(listener, 8, t)
|
||||
fmt.Println("d8")
|
||||
downloadContinue(listener, 16, t)
|
||||
}
|
||||
|
||||
@@ -223,9 +220,9 @@ func ifExistAndRemove(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadReady(listener net.Listener, connections int, t *testing.T) base.Fetcher {
|
||||
func downloadReady(listener net.Listener, connections int, t *testing.T) (base.Fetcher, <-chan error) {
|
||||
fetcher := NewFetcher()
|
||||
fetcher.Init(base.NewController())
|
||||
doneCh := fetcher.Setup(base.NewController())
|
||||
res, err := fetcher.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + buildName,
|
||||
})
|
||||
@@ -240,16 +237,20 @@ func downloadReady(listener net.Listener, connections int, t *testing.T) base.Fe
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return fetcher
|
||||
return fetcher, doneCh
|
||||
|
||||
}
|
||||
|
||||
func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
||||
fetcher := downloadReady(listener, connections, t)
|
||||
fetcher, doneCh := downloadReady(listener, connections, t)
|
||||
err := fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = <-doneCh
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := fileMd5(buildFile)
|
||||
got := fileMd5(downloadFile)
|
||||
if want != got {
|
||||
@@ -258,13 +259,11 @@ func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
||||
}
|
||||
|
||||
func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
||||
fetcher := downloadReady(listener, connections, t)
|
||||
go func() {
|
||||
err := fetcher.Start()
|
||||
if err != nil && err != base.PauseErr {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
fetcher, doneCh := downloadReady(listener, connections, t)
|
||||
err := fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
if err := fetcher.Pause(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -272,6 +271,10 @@ func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
||||
if err := fetcher.Continue(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = <-doneCh
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := fileMd5(buildFile)
|
||||
got := fileMd5(downloadFile)
|
||||
if want != got {
|
||||
@@ -280,8 +283,12 @@ func downloadContinue(listener net.Listener, connections int, t *testing.T) {
|
||||
}
|
||||
|
||||
func downloadError(listener net.Listener, connections int, t *testing.T) {
|
||||
fetcher := downloadReady(listener, connections, t)
|
||||
fetcher, doneCh := downloadReady(listener, connections, t)
|
||||
err := fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = <-doneCh
|
||||
if err == nil {
|
||||
t.Errorf("Download error = %v, want %v", err, nil)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user