diff --git a/internal/controller/controller.go b/internal/controller/controller.go index f99b8456..389543cd 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -6,7 +6,7 @@ import ( ) type Controller struct { - GetConfig func(v any) (bool, error) + GetConfig func(v any) bool FileController //ContextDialer() (proxy.Dialer, error) } @@ -21,8 +21,8 @@ type DefaultFileController struct { func NewController() *Controller { return &Controller{ FileController: &DefaultFileController{}, - GetConfig: func(v any) (bool, error) { - return false, nil + GetConfig: func(v any) bool { + return false }, } } diff --git a/internal/protocol/bt/config.go b/internal/protocol/bt/config.go index 04a107dd..5aab314c 100644 --- a/internal/protocol/bt/config.go +++ b/internal/protocol/bt/config.go @@ -1,5 +1,6 @@ package bt type config struct { - Trackers []string `json:"trackers"` + ListenPort int `json:"listenPort"` + Trackers []string `json:"trackers"` } diff --git a/internal/protocol/bt/fetcher.go b/internal/protocol/bt/fetcher.go index 1d173ef5..8e627247 100644 --- a/internal/protocol/bt/fetcher.go +++ b/internal/protocol/bt/fetcher.go @@ -23,7 +23,8 @@ var ( ) type Fetcher struct { - ctl *controller.Controller + ctl *controller.Controller + config *config torrent *torrent.Torrent meta *fetcher.FetcherMeta @@ -42,6 +43,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) { if f.meta == nil { f.meta = &fetcher.FetcherMeta{} } + exist := f.ctl.GetConfig(&f.config) + if !exist { + f.config = &config{ + ListenPort: 0, + Trackers: []string{}, + } + } return } @@ -54,7 +62,7 @@ func (f *Fetcher) initClient() (err error) { } cfg := torrent.NewDefaultClientConfig() - cfg.ListenPort = 0 + cfg.ListenPort = f.config.ListenPort cfg.DefaultStorage = newFileOpts(newFileClientOpts{ ClientBaseDir: cfg.DataDir, HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) { @@ -231,11 +239,6 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) { if err != nil { return } - var cfg config - exist, err := f.ctl.GetConfig(&cfg) - if err != nil { - return - } // use map to deduplicate trackers := make(map[string]bool) @@ -247,8 +250,8 @@ func (f *Fetcher) addTorrent(req *base.Request) (err error) { } } } - if exist && len(cfg.Trackers) > 0 { - for _, tracker := range cfg.Trackers { + if len(f.config.Trackers) > 0 { + for _, tracker := range f.config.Trackers { trackers[tracker] = true } } diff --git a/internal/protocol/bt/fetcher_test.go b/internal/protocol/bt/fetcher_test.go index 44a83804..d5c21ec4 100644 --- a/internal/protocol/bt/fetcher_test.go +++ b/internal/protocol/bt/fetcher_test.go @@ -98,11 +98,11 @@ func buildConfigFetcher() fetcher.Fetcher { "udp://tracker.birkenwald.de:6969/announce", "udp://tracker.bitsearch.to:1337/announce", }} - newController.GetConfig = func(v any) (bool, error) { + newController.GetConfig = func(v any) bool { if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil { - return false, err + return false } - return true, nil + return true } fetcher.Setup(newController) return fetcher diff --git a/internal/protocol/http/config.go b/internal/protocol/http/config.go index 78772eaf..093cb909 100644 --- a/internal/protocol/http/config.go +++ b/internal/protocol/http/config.go @@ -1,5 +1,6 @@ package http type config struct { - Connections int `json:"connections"` + UserAgent string `json:"userAgent"` + Connections int `json:"connections"` } diff --git a/internal/protocol/http/fetcher.go b/internal/protocol/http/fetcher.go index 55cd255d..8f887428 100644 --- a/internal/protocol/http/fetcher.go +++ b/internal/protocol/http/fetcher.go @@ -51,6 +51,7 @@ func newChunk(begin int64, end int64) *chunk { type Fetcher struct { ctl *controller.Controller + config *config doneCh chan error meta *fetcher.FetcherMeta @@ -71,6 +72,13 @@ func (f *Fetcher) Setup(ctl *controller.Controller) { if f.meta == nil { f.meta = &fetcher.FetcherMeta{} } + exist := f.ctl.GetConfig(&f.config) + if !exist { + f.config = &config{ + UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", + Connections: 1, + } + } return } @@ -78,7 +86,7 @@ func (f *Fetcher) Resolve(req *base.Request) error { if err := base.ParseReqExtra[fhttp.ReqExtra](req); err != nil { return err } - httpReq, err := buildRequest(nil, req) + httpReq, err := f.buildRequest(nil, req) if err != nil { return err } @@ -160,16 +168,7 @@ func (f *Fetcher) Create(opts *base.Options) error { } 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 - } + extra.Connections = f.config.Connections } return nil } @@ -260,7 +259,7 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) { chunk := f.chunks[index] chunk.retryTimes = 0 - httpReq, err := buildRequest(ctx, f.meta.Req) + httpReq, err := f.buildRequest(ctx, f.meta.Req) if err != nil { return err } @@ -357,6 +356,52 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) { return } +func (f *Fetcher) buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request, err error) { + url, err := url.Parse(req.URL) + if err != nil { + return + } + + var ( + method string + body io.Reader + ) + headers := make(map[string][]string) + if req.Extra == nil { + method = http.MethodGet + } else { + extra := req.Extra.(*fhttp.ReqExtra) + if extra.Method != "" { + method = extra.Method + } else { + method = http.MethodGet + } + if len(extra.Header) > 0 { + for k, v := range extra.Header { + headers[k] = []string{v} + } + } + if extra.Body != "" { + body = bytes.NewBufferString(extra.Body) + } + } + if _, ok := headers[base.HttpHeaderUserAgent]; !ok { + // load user agent from config + headers[base.HttpHeaderUserAgent] = []string{f.config.UserAgent} + } + + if ctx != nil { + httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body) + } else { + httpReq, err = http.NewRequest(method, url.String(), body) + } + if err != nil { + return + } + httpReq.Header = headers + return httpReq, nil +} + func (f *Fetcher) splitChunk() (chunks []*chunk) { if f.meta.Res.Range { connections := f.meta.Opts.Extra.(*fhttp.OptsExtra).Connections @@ -393,51 +438,6 @@ func buildClient() *http.Client { } } -func buildRequest(ctx context.Context, req *base.Request) (httpReq *http.Request, err error) { - url, err := url.Parse(req.URL) - if err != nil { - return - } - - var ( - method string - body io.Reader - ) - headers := make(map[string][]string) - if req.Extra == nil { - method = http.MethodGet - } else { - extra := req.Extra.(*fhttp.ReqExtra) - if extra.Method != "" { - method = extra.Method - } else { - method = http.MethodGet - } - if len(extra.Header) > 0 { - for k, v := range extra.Header { - headers[k] = []string{v} - } - } - if extra.Body != "" { - body = bytes.NewBufferString(extra.Body) - } - } - if _, ok := headers[base.HttpHeaderUserAgent]; !ok { - headers[base.HttpHeaderUserAgent] = []string{base.AgentName} - } - - if ctx != nil { - httpReq, err = http.NewRequestWithContext(ctx, method, url.String(), body) - } else { - httpReq, err = http.NewRequest(method, url.String(), body) - } - if err != nil { - return - } - httpReq.Header = headers - return httpReq, nil -} - type fetcherData struct { Chunks []*chunk } diff --git a/internal/protocol/http/fetcher_test.go b/internal/protocol/http/fetcher_test.go index 0b552709..f244ba10 100644 --- a/internal/protocol/http/fetcher_test.go +++ b/internal/protocol/http/fetcher_test.go @@ -314,11 +314,11 @@ func buildConfigFetcher() fetcher.Fetcher { mockCfg := config{ Connections: 16, } - newController.GetConfig = func(v any) (bool, error) { + newController.GetConfig = func(v any) bool { if err := json.Unmarshal([]byte(test.ToJson(mockCfg)), v); err != nil { - return false, err + return false } - return true, nil + return true } fetcher.Setup(newController) return fetcher diff --git a/pkg/base/constants.go b/pkg/base/constants.go index 73481a28..88d13b8a 100644 --- a/pkg/base/constants.go +++ b/pkg/base/constants.go @@ -23,7 +23,3 @@ const ( HttpHeaderRangeFormat = "bytes=%d-%d" ) - -const ( - AgentName = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36" -) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 1ab2c203..d1225ba9 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -212,7 +212,7 @@ func (d *Downloader) parseFb(url string) (fetcher.FetcherBuilder, error) { func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) { ctl := controller.NewController() - ctl.GetConfig = func(v any) (bool, error) { + ctl.GetConfig = func(v any) bool { return d.getProtocolConfig(fetcher.Name(), v) } fetcher.Setup(ctl) @@ -563,18 +563,19 @@ func (d *Downloader) PutConfig(v *DownloaderStoreConfig) error { return d.storage.Put(bucketConfig, "config", v) } -func (d *Downloader) getProtocolConfig(name string, v any) (bool, error) { +func (d *Downloader) getProtocolConfig(name string, v any) bool { cfg, err := d.GetConfig() if err != nil { - return false, err + return false } if cfg.ProtocolConfig == nil || cfg.ProtocolConfig[name] == nil { - return false, nil + return false } if err := util.MapToStruct(cfg.ProtocolConfig[name], v); err != nil { - return false, err + d.Logger.Warn().Err(err).Msgf("get protocol config failed") + return false } - return true, nil + return true } // wait task done diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index 39a2e50d..3d6eee4b 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -205,10 +205,7 @@ func TestDownloader_Protocol_Config(t *testing.T) { defer downloader.Clear() var httpCfg map[string]any - exits, err := downloader.getProtocolConfig("http", &httpCfg) - if err != nil { - t.Fatal(err) - } + exits := downloader.getProtocolConfig("http", &httpCfg) if exits { t.Errorf("getProtocolConfig() got = %v, want %v", exits, false) } @@ -234,7 +231,7 @@ func TestDownloader_Protocol_Config(t *testing.T) { }, } - if err = downloader.PutConfig(storeCfg); err != nil { + if err := downloader.PutConfig(storeCfg); err != nil { t.Fatal(err) } diff --git a/ui/flutter/lib/api/model/downloader_config.dart b/ui/flutter/lib/api/model/downloader_config.dart index 2f45eedb..9abb9c66 100644 --- a/ui/flutter/lib/api/model/downloader_config.dart +++ b/ui/flutter/lib/api/model/downloader_config.dart @@ -30,6 +30,8 @@ class ProtocolConfig { @JsonSerializable() class HttpConfig { + String userAgent = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'; int connections = 0; HttpConfig(); @@ -41,6 +43,7 @@ class HttpConfig { @JsonSerializable() class BtConfig { + int listenPort = 0; List trackers = []; BtConfig(); diff --git a/ui/flutter/lib/api/model/downloader_config.g.dart b/ui/flutter/lib/api/model/downloader_config.g.dart index 015f792c..9addee00 100644 --- a/ui/flutter/lib/api/model/downloader_config.g.dart +++ b/ui/flutter/lib/api/model/downloader_config.g.dart @@ -33,19 +33,23 @@ Map _$ProtocolConfigToJson(ProtocolConfig instance) => 'bt': instance.bt.toJson(), }; -HttpConfig _$HttpConfigFromJson(Map json) => - HttpConfig()..connections = json['connections'] as int; +HttpConfig _$HttpConfigFromJson(Map json) => HttpConfig() + ..userAgent = json['userAgent'] as String + ..connections = json['connections'] as int; Map _$HttpConfigToJson(HttpConfig instance) => { + 'userAgent': instance.userAgent, 'connections': instance.connections, }; BtConfig _$BtConfigFromJson(Map json) => BtConfig() + ..listenPort = json['listenPort'] as int ..trackers = (json['trackers'] as List).map((e) => e as String).toList(); Map _$BtConfigToJson(BtConfig instance) => { + 'listenPort': instance.listenPort, 'trackers': instance.trackers, }; diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart index 8ff7e7b4..fc294fc9 100644 --- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart +++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart @@ -315,6 +315,7 @@ class AppController extends GetxController with WindowListener, TrayListener { result.addAll(trackers); } catch (e) { logger.w("subscribe trackers fail, url: $u", e); + return; } } btExtConfig.subscribeTrackers.clear(); diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index 21d56eb2..cbeb27ad 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -96,6 +96,24 @@ class SettingView extends GetView { // http config items start final httpConfig = downloaderCfg.value.protocolConfig.http; + final buildHttpUa = + _buildConfigItem('User-Agent', () => httpConfig.userAgent, (Key key) { + final uaController = TextEditingController(text: httpConfig.userAgent); + uaController.addListener(() async { + if (uaController.text.isNotEmpty && + uaController.text != httpConfig.userAgent) { + httpConfig.userAgent = uaController.text; + + await debounceSave(); + } + }); + + return TextField( + key: key, + focusNode: FocusNode(), + controller: uaController, + ); + }); final buildHttpConnections = _buildConfigItem( 'connections', () => httpConfig.connections.toString(), (Key key) { final connectionsController = @@ -122,8 +140,32 @@ class SettingView extends GetView { }); // bt config items start + final btConfig = downloaderCfg.value.protocolConfig.bt; final btExtConfig = downloaderCfg.value.extra.bt; + final buildBtListenPort = _buildConfigItem( + 'port', () => btConfig.listenPort.toString(), (Key key) { + final listenPortController = + TextEditingController(text: btConfig.listenPort.toString()); + listenPortController.addListener(() async { + if (listenPortController.text.isNotEmpty && + listenPortController.text != btConfig.listenPort.toString()) { + btConfig.listenPort = int.parse(listenPortController.text); + await debounceSave(); + } + }); + + return TextField( + key: key, + focusNode: FocusNode(), + controller: listenPortController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + NumericalRangeFormatter(min: 0, max: 65535), + ], + ); + }); final buildBtTrackerSubscribeUrls = _buildConfigItem( 'subscribeTracker', () => 'items'.trParams( @@ -479,6 +521,7 @@ class SettingView extends GetView { Card( child: Column( children: _addDivider([ + buildHttpUa(), buildHttpConnections(), ]), )), @@ -486,6 +529,7 @@ class SettingView extends GetView { Card( child: Column( children: _addDivider([ + buildBtListenPort(), buildBtTrackerSubscribeUrls(), buildBtTrackers(), ]),