diff --git a/engine/stun.go b/engine/stun.go index c66dd75..d6074db 100644 --- a/engine/stun.go +++ b/engine/stun.go @@ -1,8 +1,10 @@ package engine import ( + "fmt" "net/url" "polaris/ent/downloadclients" + "polaris/log" "polaris/pkg/nat" "polaris/pkg/qbittorrent" @@ -10,31 +12,33 @@ import ( ) func (s *Engine) stunProxyDownloadClient() error { - downloader, e, err := s.GetDownloadClient() - if err != nil { - return err - } - if !e.UseNatTraversal { - return nil - } - if e.Implementation != downloadclients.ImplementationQbittorrent { - return nil - } - d, ok := downloader.(*qbittorrent.Client) - if !ok { - return nil - } - u, err := url.Parse(d.URL) - if err != nil { - return err - } + downloaders := s.db.GetAllDonloadClients() + for _, d := range downloaders { + if !d.Enable { + continue + } + + if d.Implementation != downloadclients.ImplementationQbittorrent { //TODO only support qbittorrent for now + continue + } + + qbt, err := qbittorrent.NewClient(d.URL, d.User, d.Password) + if err != nil { + return fmt.Errorf("connect to download client error: %v", d.URL) + } + u, err := url.Parse(d.URL) + if err != nil { + return err + } + log.Infof("start stun proxy for %s", d.Name) + n, err := nat.NewNatTraversal(func(xa stun.XORMappedAddress) error { + return qbt.SetListenPort(xa.Port) + }, u.Hostname()) + if err != nil { + return err + } + n.StartProxy() - n, err := nat.NewNatTraversal(func(xa stun.XORMappedAddress) error { - return d.SetListenPort(xa.Port) - }, u.Hostname()) - if err != nil { - return err } - n.StartProxy() return nil } diff --git a/ui/lib/providers/settings.dart b/ui/lib/providers/settings.dart index cb13318..0033579 100644 --- a/ui/lib/providers/settings.dart +++ b/ui/lib/providers/settings.dart @@ -251,6 +251,7 @@ class DownloadClient { bool? removeCompletedDownloads; bool? removeFailedDownloads; int? priority; + bool useNatTraversal = false; DownloadClient( {this.id, this.enable, @@ -261,7 +262,7 @@ class DownloadClient { this.password, this.removeCompletedDownloads = true, this.priority = 1, - this.removeFailedDownloads = true}); + this.removeFailedDownloads = true, this.useNatTraversal = false}); DownloadClient.fromJson(Map json) { id = json['id']; @@ -274,6 +275,7 @@ class DownloadClient { priority = json["priority1"]; removeCompletedDownloads = json["remove_completed_downloads"] ?? false; removeFailedDownloads = json["remove_failed_downloads"] ?? false; + useNatTraversal = json["use_nat_traversal"]?? false; } Map toJson() { @@ -288,6 +290,7 @@ class DownloadClient { data["priority"] = priority; data["remove_completed_downloads"] = removeCompletedDownloads; data["remove_failed_downloads"] = removeFailedDownloads; + data["use_nat_traversal"] = useNatTraversal; return data; } diff --git a/ui/lib/settings/downloader.dart b/ui/lib/settings/downloader.dart index 01de8b4..061c3bb 100644 --- a/ui/lib/settings/downloader.dart +++ b/ui/lib/settings/downloader.dart @@ -63,6 +63,7 @@ class _DownloaderState extends ConsumerState { "remove_completed_downloads": client.removeCompletedDownloads, "remove_failed_downloads": client.removeFailedDownloads, "priority": client.priority.toString(), + "use_nat_traversal": client.useNatTraversal, }, child: Column( children: [ @@ -86,6 +87,12 @@ class _DownloaderState extends ConsumerState { labelText: "优先级", helperText: "1-50, 1最高优先级,50最低优先级"), validator: FormBuilderValidators.integer(), autovalidateMode: AutovalidateMode.onUserInteraction), + FormBuilderSwitch( + name: "use_nat_traversal", + enabled: client.implementation == "qbittorrent", + title: const Text("使用内置STUN NAT穿透"), + decoration: InputDecoration(helperText: "内建的NAT穿透功能帮助BT客户端上传(会自动更改下载器的监听地址)"), + ), FormBuilderSwitch( name: "remove_completed_downloads", title: const Text("任务完成后删除")), @@ -150,6 +157,7 @@ class _DownloaderState extends ConsumerState { user: _enableAuth ? values["user"] : null, password: _enableAuth ? values["password"] : null, priority: int.parse(values["priority"]), + useNatTraversal: values["use_nat_traversal"], removeCompletedDownloads: values["remove_completed_downloads"], removeFailedDownloads: values["remove_failed_downloads"])); } else { @@ -165,7 +173,12 @@ class _DownloaderState extends ConsumerState { } return showSettingDialog( - context, title, client.idExists() && client.implementation != "buildin", body, onSubmit, onDelete); + context, + title, + client.idExists() && client.implementation != "buildin", + body, + onSubmit, + onDelete); } Future showSelections() {