mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-12 03:14:09 +00:00
feat: support custom tracker url (#59)
This commit is contained in:
1 parent
9ef3405e43
commit
691c2f496e
71 files changed
+2209
-891
No files matched your search
@@ -0,0 +1,5 @@
|
||||
package bt
|
||||
|
||||
type config struct {
|
||||
Trackers []string `json:"trackers"`
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
var client *torrent.Client
|
||||
|
||||
type Fetcher struct {
|
||||
Ctl controller.Controller
|
||||
Ctl *controller.Controller
|
||||
|
||||
torrent *torrent.Torrent
|
||||
res *base.Resource
|
||||
@@ -29,7 +29,11 @@ type Fetcher struct {
|
||||
torrentPaths map[string]string
|
||||
}
|
||||
|
||||
func (f *Fetcher) Setup(ctl controller.Controller) (err error) {
|
||||
func (f *Fetcher) Name() string {
|
||||
return "bt"
|
||||
}
|
||||
|
||||
func (f *Fetcher) Setup(ctl *controller.Controller) (err error) {
|
||||
f.Ctl = ctl
|
||||
var once sync.Once
|
||||
once.Do(func() {
|
||||
@@ -105,9 +109,7 @@ func (f *Fetcher) Resolve(req *base.Request) (res *base.Resource, err error) {
|
||||
Name: f.torrent.Name(),
|
||||
Range: true,
|
||||
Files: make([]*base.FileInfo, len(f.torrent.Files())),
|
||||
Extra: map[string]any{
|
||||
"infoHash": f.torrent.InfoHash().String(),
|
||||
},
|
||||
Hash: f.torrent.InfoHash().String(),
|
||||
}
|
||||
for i, file := range f.torrent.Files() {
|
||||
res.Files[i] = &base.FileInfo{
|
||||
@@ -130,10 +132,9 @@ func (f *Fetcher) Create(res *base.Resource, opts *base.Options) (err error) {
|
||||
}
|
||||
}
|
||||
if f.opts.Path != "" {
|
||||
if extra, ok := res.Extra.(map[string]any); ok {
|
||||
f.torrentPaths[extra["infoHash"].(string)] = f.opts.Path
|
||||
}
|
||||
f.torrentPaths[res.Hash] = f.opts.Path
|
||||
}
|
||||
|
||||
f.progress = make(fetcher.Progress, len(f.opts.SelectFiles))
|
||||
f.ready.Store(false)
|
||||
return nil
|
||||
@@ -192,6 +193,18 @@ func (f *Fetcher) addTorrent(url string, resolve bool) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var cfg config
|
||||
exist, err := f.Ctl.GetConfig(&cfg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if exist && len(cfg.Trackers) > 0 {
|
||||
announceList := make([][]string, 0)
|
||||
for _, tracker := range cfg.Trackers {
|
||||
announceList = append(announceList, []string{tracker})
|
||||
}
|
||||
f.torrent.AddTrackers(announceList)
|
||||
}
|
||||
<-f.torrent.GotInfo()
|
||||
if resolve {
|
||||
return
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package bt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/monkeyWie/gopeed/internal/controller"
|
||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||
"github.com/monkeyWie/gopeed/internal/test"
|
||||
"github.com/monkeyWie/gopeed/pkg/base"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFetcher_Resolve_Torrent(t *testing.T) {
|
||||
fetcher := buildFetcher()
|
||||
doResolve(t, buildFetcher())
|
||||
}
|
||||
|
||||
func TestFetcher_Config(t *testing.T) {
|
||||
doResolve(t, buildConfigFetcher())
|
||||
}
|
||||
|
||||
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
||||
res, err := fetcher.Resolve(&base.Request{
|
||||
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
|
||||
})
|
||||
@@ -28,9 +37,7 @@ func TestFetcher_Resolve_Torrent(t *testing.T) {
|
||||
Size: 1466714112,
|
||||
},
|
||||
},
|
||||
Extra: map[string]any{
|
||||
"infoHash": "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||
},
|
||||
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
||||
}
|
||||
if !reflect.DeepEqual(want, res) {
|
||||
t.Errorf("Resolve() got = %v, want %v", res, want)
|
||||
@@ -42,3 +49,21 @@ func buildFetcher() fetcher.Fetcher {
|
||||
fetcher.Setup(controller.NewController())
|
||||
return fetcher
|
||||
}
|
||||
|
||||
func buildConfigFetcher() fetcher.Fetcher {
|
||||
fetcher := new(FetcherBuilder).Build()
|
||||
newController := controller.NewController()
|
||||
mockCfg := config{
|
||||
Trackers: []string{
|
||||
"udp://tracker.birkenwald.de:6969/announce",
|
||||
"udp://tracker.bitsearch.to:1337/announce",
|
||||
}}
|
||||
newController.GetConfig = func(v any) (bool, error) {
|
||||
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
fetcher.Setup(newController)
|
||||
return fetcher
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package http
|
||||
|
||||
type config struct {
|
||||
Connections int `json:"connections"`
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||
"github.com/monkeyWie/gopeed/pkg/base"
|
||||
fhttp "github.com/monkeyWie/gopeed/pkg/protocol/http"
|
||||
"github.com/monkeyWie/gopeed/pkg/util"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"io"
|
||||
@@ -34,6 +35,21 @@ func (re *RequestError) Error() string {
|
||||
return fmt.Sprintf("http request fail,code:%d", re.Code)
|
||||
}
|
||||
|
||||
type chunk struct {
|
||||
Status base.Status
|
||||
Begin int64
|
||||
End int64
|
||||
Downloaded int64
|
||||
}
|
||||
|
||||
func newChunk(begin int64, end int64) *chunk {
|
||||
return &chunk{
|
||||
Status: base.DownloadStatusReady,
|
||||
Begin: begin,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
type Fetcher struct {
|
||||
*fetcher.DefaultFetcher
|
||||
|
||||
@@ -48,7 +64,14 @@ type Fetcher struct {
|
||||
pauseCh chan interface{}
|
||||
}
|
||||
|
||||
func (f *Fetcher) Name() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (f *Fetcher) Resolve(req *base.Request) (*base.Resource, error) {
|
||||
if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpReq, err := buildRequest(nil, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -120,6 +143,29 @@ func (f *Fetcher) Create(res *base.Resource, opts *base.Options) error {
|
||||
f.res = res
|
||||
f.opts = opts
|
||||
f.status = base.DownloadStatusReady
|
||||
|
||||
if err := base.ParseReqExtra[fhttp.ReqExtra](res.Req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.ParseOptsExtra[fhttp.OptsExtra](opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Extra == nil {
|
||||
opts.Extra = &fhttp.ReqExtra{}
|
||||
}
|
||||
extra := opts.Extra.(*fhttp.OptsExtra)
|
||||
if extra.Connections == 0 {
|
||||
var cfg config
|
||||
exist, err := f.Ctl.GetConfig(&cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
extra.Connections = cfg.Connections
|
||||
} else {
|
||||
extra.Connections = 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -131,7 +177,7 @@ func (f *Fetcher) Start() (err error) {
|
||||
return err
|
||||
}
|
||||
f.status = base.DownloadStatusRunning
|
||||
f.chunks = splitChunk(f.res, f.opts)
|
||||
f.chunks = f.splitChunk()
|
||||
f.fetch()
|
||||
return
|
||||
}
|
||||
@@ -188,7 +234,7 @@ func (f *Fetcher) filepath() string {
|
||||
func (f *Fetcher) fetch() {
|
||||
f.ctx, f.cancel = context.WithCancel(context.Background())
|
||||
eg, _ := errgroup.WithContext(f.ctx)
|
||||
for i := 0; i < f.opts.Connections; i++ {
|
||||
for i := 0; i < f.opts.Extra.(*fhttp.OptsExtra).Connections; i++ {
|
||||
i := i
|
||||
eg.Go(func() error {
|
||||
return f.fetchChunk(i)
|
||||
@@ -301,19 +347,20 @@ func (f *Fetcher) fetchChunk(index int) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func splitChunk(res *base.Resource, opts *base.Options) (chunks []*chunk) {
|
||||
if res.Range {
|
||||
func (f *Fetcher) splitChunk() (chunks []*chunk) {
|
||||
if f.res.Range {
|
||||
connections := f.opts.Extra.(*fhttp.OptsExtra).Connections
|
||||
// 每个连接平均需要下载的分块大小
|
||||
chunkSize := res.Size / int64(opts.Connections)
|
||||
chunks = make([]*chunk, opts.Connections)
|
||||
for i := 0; i < opts.Connections; i++ {
|
||||
chunkSize := f.res.Size / int64(connections)
|
||||
chunks = make([]*chunk, connections)
|
||||
for i := 0; i < connections; i++ {
|
||||
var (
|
||||
begin = chunkSize * int64(i)
|
||||
end int64
|
||||
)
|
||||
if i == opts.Connections-1 {
|
||||
if i == connections-1 {
|
||||
// 最后一个分块需要保证把文件下载完
|
||||
end = res.Size - 1
|
||||
end = f.res.Size - 1
|
||||
} else {
|
||||
end = begin + chunkSize - 1
|
||||
}
|
||||
@@ -351,7 +398,7 @@ func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request
|
||||
if req.Extra == nil {
|
||||
method = http.MethodGet
|
||||
} else {
|
||||
extra := req.Extra.(extra)
|
||||
extra := req.Extra.(*fhttp.ReqExtra)
|
||||
if extra.Method != "" {
|
||||
method = extra.Method
|
||||
} else {
|
||||
@@ -414,8 +461,10 @@ func (fb *FetcherBuilder) Restore() (v any, f func(res *base.Resource, opts *bas
|
||||
fetcher.res = res
|
||||
fetcher.opts = opts
|
||||
fetcher.status = base.DownloadStatusPause
|
||||
base.ParseReqExtra[fhttp.ReqExtra](res.Req)
|
||||
base.ParseOptsExtra[fhttp.OptsExtra](opts)
|
||||
if len(fd.Chunks) == 0 {
|
||||
fetcher.chunks = splitChunk(res, opts)
|
||||
fetcher.chunks = fetcher.splitChunk()
|
||||
} else {
|
||||
fetcher.chunks = fd.Chunks
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/monkeyWie/gopeed/internal/controller"
|
||||
"github.com/monkeyWie/gopeed/internal/fetcher"
|
||||
"github.com/monkeyWie/gopeed/internal/test"
|
||||
"github.com/monkeyWie/gopeed/pkg/base"
|
||||
"github.com/monkeyWie/gopeed/pkg/protocol/http"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -111,8 +113,29 @@ func TestFetcher_DownloadResume(t *testing.T) {
|
||||
downloadResume(listener, 16, t)
|
||||
}
|
||||
|
||||
func TestFetcher_Config(t *testing.T) {
|
||||
fetcher, _, _ := doDownloadReady(buildConfigFetcher(), test.StartTestFileServer(), 0, t)
|
||||
err := fetcher.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = fetcher.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := test.FileMd5(test.BuildFile)
|
||||
got := test.FileMd5(test.DownloadFile)
|
||||
if want != got {
|
||||
t.Errorf("Download() got = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadReady(listener net.Listener, connections int, t *testing.T) (fetcher fetcher.Fetcher, res *base.Resource, opts *base.Options) {
|
||||
fetcher = buildFetcher()
|
||||
return doDownloadReady(buildFetcher(), listener, connections, t)
|
||||
}
|
||||
|
||||
func doDownloadReady(f fetcher.Fetcher, listener net.Listener, connections int, t *testing.T) (fetcher fetcher.Fetcher, res *base.Resource, opts *base.Options) {
|
||||
fetcher = f
|
||||
res, err := fetcher.Resolve(&base.Request{
|
||||
URL: "http://" + listener.Addr().String() + "/" + test.BuildName,
|
||||
})
|
||||
@@ -120,9 +143,11 @@ func downloadReady(listener net.Listener, connections int, t *testing.T) (fetche
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts = &base.Options{
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Connections: connections,
|
||||
Name: test.DownloadName,
|
||||
Path: test.Dir,
|
||||
Extra: http.OptsExtra{
|
||||
Connections: connections,
|
||||
},
|
||||
}
|
||||
err = fetcher.Create(res, opts)
|
||||
if err != nil {
|
||||
@@ -150,7 +175,7 @@ func downloadNormal(listener net.Listener, connections int, t *testing.T) {
|
||||
|
||||
func downloadPost(listener net.Listener, connections int, t *testing.T) {
|
||||
fetcher, _, _ := downloadReady(listener, connections, t)
|
||||
fetcher.(*Fetcher).res.Req.Extra = extra{
|
||||
fetcher.(*Fetcher).res.Req.Extra = &http.ReqExtra{
|
||||
Method: "POST",
|
||||
Header: map[string]string{
|
||||
"Authorization": "Bearer 123456",
|
||||
@@ -249,3 +274,19 @@ func buildFetcher() *Fetcher {
|
||||
fetcher.Setup(controller.NewController())
|
||||
return fetcher.(*Fetcher)
|
||||
}
|
||||
|
||||
func buildConfigFetcher() fetcher.Fetcher {
|
||||
fetcher := new(FetcherBuilder).Build()
|
||||
newController := controller.NewController()
|
||||
mockCfg := config{
|
||||
Connections: 16,
|
||||
}
|
||||
newController.GetConfig = func(v any) (bool, error) {
|
||||
if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
fetcher.Setup(newController)
|
||||
return fetcher
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package http
|
||||
|
||||
import "github.com/monkeyWie/gopeed/pkg/base"
|
||||
|
||||
type chunk struct {
|
||||
Status base.Status
|
||||
Begin int64
|
||||
End int64
|
||||
Downloaded int64
|
||||
}
|
||||
|
||||
func newChunk(begin int64, end int64) *chunk {
|
||||
return &chunk{
|
||||
Status: base.DownloadStatusReady,
|
||||
Begin: begin,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
type extra struct {
|
||||
Method string
|
||||
Header map[string]string
|
||||
Body string
|
||||
}
|
||||
Reference in new issue
Block a user