diff --git a/internal/offline_download/tool/add.go b/internal/offline_download/tool/add.go index e147d159e..12160dcfe 100644 --- a/internal/offline_download/tool/add.go +++ b/internal/offline_download/tool/add.go @@ -18,6 +18,7 @@ import ( "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser" "github.com/OpenListTeam/OpenList/v4/drivers/thunderx" "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/driver" "github.com/OpenListTeam/OpenList/v4/internal/errs" "github.com/OpenListTeam/OpenList/v4/internal/fs" "github.com/OpenListTeam/OpenList/v4/internal/model" @@ -69,7 +70,7 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro } } // try putting url - if args.Tool == "SimpleHttp" { + if args.Tool == "SimpleHttp" && !isEd2kURL(args.URL) { if isSimpleHttpSchemeUnsupported(args.URL) { return nil, fmt.Errorf("SimpleHttp tool does not support this URL scheme, please use aria2 or other tools for magnet/ed2k links") } @@ -83,13 +84,17 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro // ed2k 链接自动路由:如果当前工具不支持 ed2k,自动尝试使用迅雷系工具 if isEd2kURL(args.URL) { if !isEd2kCapableTool(args.Tool) { - // 尝试找到一个可用的支持 ed2k 的工具 - fallbackTool, fallbackName := findEd2kCapableTool() - if fallbackTool != nil { - // 使用找到的迅雷工具替代 - args.Tool = fallbackName + if storageTool := ed2kToolForStorage(storage); storageTool != "" { + // Prefer the matching native tool when the destination storage supports ed2k. + args.Tool = storageTool } else { - return nil, fmt.Errorf("ed2k protocol is not supported by %s. Please configure and use Thunder/ThunderX/ThunderBrowser for ed2k links", args.Tool) + // Otherwise, try to find an available Thunder-family tool. + fallbackTool, fallbackName := findEd2kCapableTool() + if fallbackTool != nil { + args.Tool = fallbackName + } else { + return nil, fmt.Errorf("ed2k protocol is not supported by %s. Please configure and use Thunder/ThunderX/ThunderBrowser for ed2k links", args.Tool) + } } } } @@ -217,8 +222,45 @@ func isEd2kURL(urlStr string) bool { return strings.HasPrefix(strings.ToLower(urlStr), "ed2k://") } -// ed2kCapableTools 支持 ed2k 协议的工具列表(迅雷系) -var ed2kCapableTools = []string{"Thunder", "ThunderX", "ThunderBrowser"} +func ed2kToolForStorage(storage driver.Driver) string { + switch toolNameForStorage(storage) { + case "115 Cloud", "115 Open": + return toolNameForStorage(storage) + default: + return "" + } +} + +func toolNameForStorage(storage driver.Driver) string { + switch storage.(type) { + case *_115.Pan115: + return "115 Cloud" + case *_115_open.Open115: + return "115 Open" + case *_123.Pan123: + return "123Pan" + case *_123_open.Open123: + return "123 Open" + case *guangyapan.GuangYaPan: + return "GuangYaPan" + case *pikpak.PikPak: + return "PikPak" + case *thunder.Thunder: + return "Thunder" + case *thunderx.ThunderX: + return "ThunderX" + case *thunder_browser.ThunderBrowser, *thunder_browser.ThunderBrowserExpert: + return "ThunderBrowser" + default: + return "" + } +} + +// ed2kCapableTools 支持 ed2k 协议的工具列表 +var ed2kCapableTools = []string{"115 Cloud", "115 Open", "Thunder", "ThunderX", "ThunderBrowser"} + +// ed2kFallbackTools 是当前可用于自动接管 ed2k 任务的工具列表。 +var ed2kFallbackTools = []string{"Thunder", "ThunderX", "ThunderBrowser"} // isEd2kCapableTool 检查工具是否支持 ed2k 协议 func isEd2kCapableTool(toolName string) bool { @@ -232,7 +274,7 @@ func isEd2kCapableTool(toolName string) bool { // findEd2kCapableTool 查找一个可用的支持 ed2k 的工具 func findEd2kCapableTool() (Tool, string) { - for _, name := range ed2kCapableTools { + for _, name := range ed2kFallbackTools { t, err := Tools.Get(name) if err != nil { continue diff --git a/internal/offline_download/tool/add_test.go b/internal/offline_download/tool/add_test.go new file mode 100644 index 000000000..c2782130f --- /dev/null +++ b/internal/offline_download/tool/add_test.go @@ -0,0 +1,86 @@ +package tool + +import ( + "testing" + + _115 "github.com/OpenListTeam/OpenList/v4/drivers/115" + _115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open" + _123 "github.com/OpenListTeam/OpenList/v4/drivers/123" + _123_open "github.com/OpenListTeam/OpenList/v4/drivers/123_open" + "github.com/OpenListTeam/OpenList/v4/drivers/guangyapan" + "github.com/OpenListTeam/OpenList/v4/drivers/pikpak" + "github.com/OpenListTeam/OpenList/v4/drivers/thunder" + "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser" + "github.com/OpenListTeam/OpenList/v4/drivers/thunderx" + "github.com/OpenListTeam/OpenList/v4/internal/driver" +) + +func TestIsEd2kCapableTool(t *testing.T) { + tests := []struct { + name string + want bool + }{ + {name: "115 Cloud", want: true}, + {name: "115 Open", want: true}, + {name: "Thunder", want: true}, + {name: "ThunderX", want: true}, + {name: "ThunderBrowser", want: true}, + {name: "aria2", want: false}, + {name: "SimpleHttp", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isEd2kCapableTool(tt.name); got != tt.want { + t.Fatalf("isEd2kCapableTool(%q) = %v, want %v", tt.name, got, tt.want) + } + }) + } +} + +func TestEd2kToolForStorage(t *testing.T) { + tests := []struct { + name string + storage driver.Driver + want string + }{ + {name: "115 Cloud", storage: &_115.Pan115{}, want: "115 Cloud"}, + {name: "115 Open", storage: &_115_open.Open115{}, want: "115 Open"}, + {name: "other", storage: nil, want: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ed2kToolForStorage(tt.storage); got != tt.want { + t.Fatalf("ed2kToolForStorage(%T) = %q, want %q", tt.storage, got, tt.want) + } + }) + } +} + +func TestToolNameForStorage(t *testing.T) { + tests := []struct { + name string + storage driver.Driver + want string + }{ + {name: "115 Cloud", storage: &_115.Pan115{}, want: "115 Cloud"}, + {name: "115 Open", storage: &_115_open.Open115{}, want: "115 Open"}, + {name: "123Pan", storage: &_123.Pan123{}, want: "123Pan"}, + {name: "123 Open", storage: &_123_open.Open123{}, want: "123 Open"}, + {name: "GuangYaPan", storage: &guangyapan.GuangYaPan{}, want: "GuangYaPan"}, + {name: "PikPak", storage: &pikpak.PikPak{}, want: "PikPak"}, + {name: "Thunder", storage: &thunder.Thunder{}, want: "Thunder"}, + {name: "ThunderX", storage: &thunderx.ThunderX{}, want: "ThunderX"}, + {name: "ThunderBrowser", storage: &thunder_browser.ThunderBrowser{}, want: "ThunderBrowser"}, + {name: "other", storage: nil, want: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := toolNameForStorage(tt.storage); got != tt.want { + t.Fatalf("toolNameForStorage(%T) = %q, want %q", tt.storage, got, tt.want) + } + }) + } +} diff --git a/internal/offline_download/tool/tools.go b/internal/offline_download/tool/tools.go index d5a90f504..fa7fd2608 100644 --- a/internal/offline_download/tool/tools.go +++ b/internal/offline_download/tool/tools.go @@ -5,6 +5,7 @@ import ( "sort" "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" ) var ( @@ -35,6 +36,31 @@ func (t ToolsManager) Names() []string { return names } +// NamesForPath returns ready tools and the native tool for the destination storage. +// Native tools can write directly to their own storage even without a temporary path setting. +func (t ToolsManager) NamesForPath(path string) []string { + names := t.Names() + storage, _, err := op.GetStorageAndActualPath(path) + if err != nil { + return names + } + + name := toolNameForStorage(storage) + if name == "" { + return names + } + for _, existing := range names { + if existing == name { + return names + } + } + if _, ok := t[name]; ok { + names = append(names, name) + sort.Strings(names) + } + return names +} + func (t ToolsManager) Items() []model.SettingItem { var items []model.SettingItem for _, tool := range t { diff --git a/server/handles/offline_download.go b/server/handles/offline_download.go index 5e2befee0..4d4167ba8 100644 --- a/server/handles/offline_download.go +++ b/server/handles/offline_download.go @@ -518,7 +518,7 @@ func SetGuangYaPan(c *gin.Context) { } func OfflineDownloadTools(c *gin.Context) { - tools := tool.Tools.Names() + tools := tool.Tools.NamesForPath(c.Query("path")) common.SuccessResp(c, tools) }