feat(ui): add nat traversal option

This commit is contained in:
Simon Ding
2025-05-08 16:30:06 +08:00
parent 992fa7ddd0
commit bb0d5d1b58
3 changed files with 46 additions and 26 deletions

View File

@@ -1,8 +1,10 @@
package engine package engine
import ( import (
"fmt"
"net/url" "net/url"
"polaris/ent/downloadclients" "polaris/ent/downloadclients"
"polaris/log"
"polaris/pkg/nat" "polaris/pkg/nat"
"polaris/pkg/qbittorrent" "polaris/pkg/qbittorrent"
@@ -10,31 +12,33 @@ import (
) )
func (s *Engine) stunProxyDownloadClient() error { func (s *Engine) stunProxyDownloadClient() error {
downloader, e, err := s.GetDownloadClient() downloaders := s.db.GetAllDonloadClients()
if err != nil { for _, d := range downloaders {
return err if !d.Enable {
} continue
if !e.UseNatTraversal { }
return nil
} if d.Implementation != downloadclients.ImplementationQbittorrent { //TODO only support qbittorrent for now
if e.Implementation != downloadclients.ImplementationQbittorrent { continue
return nil }
}
d, ok := downloader.(*qbittorrent.Client) qbt, err := qbittorrent.NewClient(d.URL, d.User, d.Password)
if !ok { if err != nil {
return nil return fmt.Errorf("connect to download client error: %v", d.URL)
} }
u, err := url.Parse(d.URL) u, err := url.Parse(d.URL)
if err != nil { if err != nil {
return err 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 return nil
} }

View File

@@ -251,6 +251,7 @@ class DownloadClient {
bool? removeCompletedDownloads; bool? removeCompletedDownloads;
bool? removeFailedDownloads; bool? removeFailedDownloads;
int? priority; int? priority;
bool useNatTraversal = false;
DownloadClient( DownloadClient(
{this.id, {this.id,
this.enable, this.enable,
@@ -261,7 +262,7 @@ class DownloadClient {
this.password, this.password,
this.removeCompletedDownloads = true, this.removeCompletedDownloads = true,
this.priority = 1, this.priority = 1,
this.removeFailedDownloads = true}); this.removeFailedDownloads = true, this.useNatTraversal = false});
DownloadClient.fromJson(Map<String, dynamic> json) { DownloadClient.fromJson(Map<String, dynamic> json) {
id = json['id']; id = json['id'];
@@ -274,6 +275,7 @@ class DownloadClient {
priority = json["priority1"]; priority = json["priority1"];
removeCompletedDownloads = json["remove_completed_downloads"] ?? false; removeCompletedDownloads = json["remove_completed_downloads"] ?? false;
removeFailedDownloads = json["remove_failed_downloads"] ?? false; removeFailedDownloads = json["remove_failed_downloads"] ?? false;
useNatTraversal = json["use_nat_traversal"]?? false;
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -288,6 +290,7 @@ class DownloadClient {
data["priority"] = priority; data["priority"] = priority;
data["remove_completed_downloads"] = removeCompletedDownloads; data["remove_completed_downloads"] = removeCompletedDownloads;
data["remove_failed_downloads"] = removeFailedDownloads; data["remove_failed_downloads"] = removeFailedDownloads;
data["use_nat_traversal"] = useNatTraversal;
return data; return data;
} }

View File

@@ -63,6 +63,7 @@ class _DownloaderState extends ConsumerState<DownloaderSettings> {
"remove_completed_downloads": client.removeCompletedDownloads, "remove_completed_downloads": client.removeCompletedDownloads,
"remove_failed_downloads": client.removeFailedDownloads, "remove_failed_downloads": client.removeFailedDownloads,
"priority": client.priority.toString(), "priority": client.priority.toString(),
"use_nat_traversal": client.useNatTraversal,
}, },
child: Column( child: Column(
children: [ children: [
@@ -86,6 +87,12 @@ class _DownloaderState extends ConsumerState<DownloaderSettings> {
labelText: "优先级", helperText: "1-50, 1最高优先级50最低优先级"), labelText: "优先级", helperText: "1-50, 1最高优先级50最低优先级"),
validator: FormBuilderValidators.integer(), validator: FormBuilderValidators.integer(),
autovalidateMode: AutovalidateMode.onUserInteraction), autovalidateMode: AutovalidateMode.onUserInteraction),
FormBuilderSwitch(
name: "use_nat_traversal",
enabled: client.implementation == "qbittorrent",
title: const Text("使用内置STUN NAT穿透"),
decoration: InputDecoration(helperText: "内建的NAT穿透功能帮助BT客户端上传(会自动更改下载器的监听地址)"),
),
FormBuilderSwitch( FormBuilderSwitch(
name: "remove_completed_downloads", name: "remove_completed_downloads",
title: const Text("任务完成后删除")), title: const Text("任务完成后删除")),
@@ -150,6 +157,7 @@ class _DownloaderState extends ConsumerState<DownloaderSettings> {
user: _enableAuth ? values["user"] : null, user: _enableAuth ? values["user"] : null,
password: _enableAuth ? values["password"] : null, password: _enableAuth ? values["password"] : null,
priority: int.parse(values["priority"]), priority: int.parse(values["priority"]),
useNatTraversal: values["use_nat_traversal"],
removeCompletedDownloads: values["remove_completed_downloads"], removeCompletedDownloads: values["remove_completed_downloads"],
removeFailedDownloads: values["remove_failed_downloads"])); removeFailedDownloads: values["remove_failed_downloads"]));
} else { } else {
@@ -165,7 +173,12 @@ class _DownloaderState extends ConsumerState<DownloaderSettings> {
} }
return showSettingDialog( return showSettingDialog(
context, title, client.idExists() && client.implementation != "buildin", body, onSubmit, onDelete); context,
title,
client.idExists() && client.implementation != "buildin",
body,
onSubmit,
onDelete);
} }
Future<void> showSelections() { Future<void> showSelections() {