From ece5b91dc2c0c4969fc802945f2077ef75bc61ef Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 14 Aug 2024 21:54:59 +0800 Subject: [PATCH] fix: torrent redundant directory (#649) --- internal/fetcher/fetcher.go | 4 + internal/protocol/bt/fetcher.go | 35 ++++- internal/protocol/bt/fetcher_test.go | 137 +++++++++++++++--- internal/protocol/bt/testdata/test.torrent | Bin 0 -> 16657 bytes internal/protocol/http/fetcher.go | 24 ++- internal/protocol/http/fetcher_test.go | 54 ++++++- pkg/download/downloader.go | 40 ++--- pkg/download/model.go | 42 ++++++ .../lib/api/model/downloader_config.g.dart | 8 +- ui/flutter/lib/api/model/task.dart | 2 + ui/flutter/lib/api/model/task.g.dart | 12 +- .../lib/app/modules/task/views/task_view.dart | 30 +--- .../lib/app/views/buid_task_list_view.dart | 4 +- 13 files changed, 300 insertions(+), 92 deletions(-) create mode 100644 internal/protocol/bt/testdata/test.torrent diff --git a/internal/fetcher/fetcher.go b/internal/fetcher/fetcher.go index b10f79c1..891e79dd 100644 --- a/internal/fetcher/fetcher.go +++ b/internal/fetcher/fetcher.go @@ -110,6 +110,10 @@ type FetcherManager interface { Filters() []*SchemeFilter // Build returns a new fetcher. Build() Fetcher + // ParseName name displayed when the task is not yet resolved, parsed from the request URL + ParseName(u string) string + // AutoRename returns whether the fetcher need renaming the download file when has the same name file. + AutoRename() bool // DefaultConfig returns the default configuration of the protocol. DefaultConfig() any diff --git a/internal/protocol/bt/fetcher.go b/internal/protocol/bt/fetcher.go index 1366ffa3..1b2796c7 100644 --- a/internal/protocol/bt/fetcher.go +++ b/internal/protocol/bt/fetcher.go @@ -11,6 +11,7 @@ import ( "github.com/GopeedLab/gopeed/pkg/util" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" + "net/url" "path/filepath" "strings" "sync" @@ -108,7 +109,7 @@ func (f *Fetcher) Resolve(req *base.Request) error { func (f *Fetcher) Create(opts *base.Options) (err error) { f.meta.Opts = opts if f.meta.Res != nil { - torrentDirMap[f.meta.Res.Hash] = f.meta.FolderPath() + torrentDirMap[f.meta.Res.Hash] = opts.Path } return nil } @@ -120,7 +121,7 @@ func (f *Fetcher) Start() (err error) { } } if ft, ok := ftMap[f.meta.Res.Hash]; ok { - ft.setTorrentDir(f.meta.FolderPath()) + ft.setTorrentDir(f.meta.Opts.Path) } files := f.torrent.Files() // If the user does not specify the file to download, all files will be downloaded by default @@ -245,16 +246,18 @@ func (f *Fetcher) isDone() bool { func (f *Fetcher) updateRes() { res := &base.Resource{ - Name: f.torrent.Name(), Range: true, Files: make([]*base.FileInfo, len(f.torrent.Files())), Hash: f.torrent.InfoHash().String(), } - f.torrent.PeerConns() + // Directory torrent + if f.torrent.Info().Length == 0 { + res.Name = f.torrent.Name() + } for i, file := range f.torrent.Files() { res.Files[i] = &base.FileInfo{ Name: filepath.Base(file.DisplayPath()), - Path: util.Dir(file.Path()), + Path: util.Dir(file.DisplayPath()), Size: file.Length(), } } @@ -458,6 +461,28 @@ func (fm *FetcherManager) Build() fetcher.Fetcher { return &Fetcher{} } +func (fm *FetcherManager) ParseName(u string) string { + var name string + url, err := url.Parse(u) + if err != nil { + return "" + } + + params := url.Query() + if params.Get("dn") != "" { + return params.Get("dn") + } + if params.Get("xt") != "" { + xt := strings.Split(params.Get("xt"), ":") + return xt[len(xt)-1] + } + return name +} + +func (fm *FetcherManager) AutoRename() bool { + return false +} + func (fm *FetcherManager) DefaultConfig() any { return &config{ ListenPort: 0, diff --git a/internal/protocol/bt/fetcher_test.go b/internal/protocol/bt/fetcher_test.go index 1354fc8a..be20321a 100644 --- a/internal/protocol/bt/fetcher_test.go +++ b/internal/protocol/bt/fetcher_test.go @@ -33,7 +33,6 @@ func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) { } want := &base.Resource{ - Name: "ubuntu-22.04-live-server-amd64.iso", Size: 1466714112, Range: true, Files: []*base.FileInfo{ @@ -69,33 +68,123 @@ func TestFetcher_ResolveWithProxy(t *testing.T) { } func doResolve(t *testing.T, fetcher fetcher.Fetcher) { - err := fetcher.Resolve(&base.Request{ - URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent", - Extra: bt.ReqExtra{ - Trackers: []string{ - "udp://tracker.birkenwald.de:6969/announce", - "udp://tracker.bitsearch.to:1337/announce", + t.Run("Resolve Single File", func(t *testing.T) { + err := fetcher.Resolve(&base.Request{ + URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent", + Extra: bt.ReqExtra{ + Trackers: []string{ + "udp://tracker.birkenwald.de:6969/announce", + "udp://tracker.bitsearch.to:1337/announce", + }, }, - }, - }) - if err != nil { - panic(err) - } + }) + if err != nil { + panic(err) + } - want := &base.Resource{ - Name: "ubuntu-22.04-live-server-amd64.iso", - Size: 1466714112, - Range: true, - Files: []*base.FileInfo{ - { - Name: "ubuntu-22.04-live-server-amd64.iso", - Size: 1466714112, + want := &base.Resource{ + Size: 1466714112, + Range: true, + Files: []*base.FileInfo{ + { + Name: "ubuntu-22.04-live-server-amd64.iso", + Size: 1466714112, + }, }, - }, - Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed", + Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed", + } + if !reflect.DeepEqual(want, fetcher.Meta().Res) { + t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want) + } + }) + + t.Run("Resolve Multi Files", func(t *testing.T) { + err := fetcher.Resolve(&base.Request{ + URL: "./testdata/test.torrent", + Extra: bt.ReqExtra{ + Trackers: []string{ + "udp://tracker.birkenwald.de:6969/announce", + "udp://tracker.bitsearch.to:1337/announce", + }, + }, + }) + if err != nil { + panic(err) + } + + want := &base.Resource{ + Name: "test", + Size: 107484864, + Range: true, + Files: []*base.FileInfo{ + { + Name: "c.txt", + Path: "path", + Size: 98501754, + }, + { + Name: "b.txt", + Size: 8904996, + }, + { + Name: "a.txt", + Size: 78114, + }, + }, + Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb", + } + if !reflect.DeepEqual(want, fetcher.Meta().Res) { + t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want) + } + }) + +} + +func TestFetcherManager_ParseName(t *testing.T) { + type args struct { + u string } - if !reflect.DeepEqual(want, fetcher.Meta().Res) { - t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want) + tests := []struct { + name string + args args + want string + }{ + { + name: "broken url", + args: args{ + u: "magnet://!@#%github.com", + }, + want: "", + }, + { + name: "dn", + args: args{ + u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed&dn=ubuntu-22.04-live-server-amd64.iso", + }, + want: "ubuntu-22.04-live-server-amd64.iso", + }, + { + name: "no dn", + args: args{ + u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed", + }, + want: "8a55cfbd5ca5d11507364765936c4f9e55b253ed", + }, + { + name: "non standard magnet", + args: args{ + u: "magnet:?xxt=abcd", + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fm := &FetcherManager{} + if got := fm.ParseName(tt.args.u); got != tt.want { + t.Errorf("ParseName() = %v, want %v", got, tt.want) + } + }) } } diff --git a/internal/protocol/bt/testdata/test.torrent b/internal/protocol/bt/testdata/test.torrent new file mode 100644 index 0000000000000000000000000000000000000000..0c93e1b81068869b3ad632da202ce6ddcb0f1b28 GIT binary patch literal 16657 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 UQle7DML!4oR literal 0 HcmV?d00001 diff --git a/internal/protocol/http/fetcher.go b/internal/protocol/http/fetcher.go index 330a0c2e..3647c2c4 100644 --- a/internal/protocol/http/fetcher.go +++ b/internal/protocol/http/fetcher.go @@ -99,7 +99,7 @@ func (f *Fetcher) Resolve(req *base.Request) error { } if base.HttpCodePartialContent == httpResp.StatusCode || (base.HttpCodeOK == httpResp.StatusCode && httpResp.Header.Get(base.HttpHeaderAcceptRanges) == base.HttpHeaderBytes && strings.HasPrefix(httpResp.Header.Get(base.HttpHeaderContentRange), base.HttpHeaderBytes)) { - // 1.返回206响应码表示支持断点下载 2.不返回206但是Accept-Ranges首部并且等于bytes也表示支持断点下载 + // response 206 status code, support breakpoint continuation res.Range = true // 解析资源大小: bytes 0-1000/1001 => 1001 contentTotal := path.Base(httpResp.Header.Get(base.HttpHeaderContentRange)) @@ -111,7 +111,8 @@ func (f *Fetcher) Resolve(req *base.Request) error { res.Size = parse } } else if base.HttpCodeOK == httpResp.StatusCode { - // 返回200响应码,不支持断点下载,通过Content-Length头获取文件大小,获取不到的话可能是chunked编码 + // response 200 status code, not support breakpoint continuation, get file size by Content-Length header + // if not found, maybe chunked encoding contentLength := httpResp.Header.Get(base.HttpHeaderContentLength) if contentLength != "" { parse, err := strconv.ParseInt(contentLength, 10, 64) @@ -481,6 +482,25 @@ func (fm *FetcherManager) Build() fetcher.Fetcher { return &Fetcher{} } +func (fm *FetcherManager) ParseName(u string) string { + var name string + url, err := url.Parse(u) + if err != nil { + return "" + } + // Get filePath by URL + name = path.Base(url.Path) + // If file name is empty, use host name + if name == "" || name == "/" || name == "." { + name = url.Hostname() + } + return name +} + +func (fm *FetcherManager) AutoRename() bool { + return true +} + func (fm *FetcherManager) DefaultConfig() any { return &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", diff --git a/internal/protocol/http/fetcher_test.go b/internal/protocol/http/fetcher_test.go index 51373e6a..d1507adf 100644 --- a/internal/protocol/http/fetcher_test.go +++ b/internal/protocol/http/fetcher_test.go @@ -192,6 +192,54 @@ func TestFetcher_ConfigUseServerCtime(t *testing.T) { } } +func TestFetcherManager_ParseName(t *testing.T) { + type args struct { + u string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "broken url", + args: args{ + u: "https://!@#%github.com", + }, + want: "", + }, + { + name: "file path", + args: args{ + u: "https://github.com/index.html", + }, + want: "index.html", + }, + { + name: "file path with query and hash", + args: args{ + u: "https://github.com/a/b/index.html/#list?name=1", + }, + want: "index.html", + }, + { + name: "no file path", + args: args{ + u: "https://github.com", + }, + want: "github.com", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fm := &FetcherManager{} + if got := fm.ParseName(tt.args.u); got != tt.want { + t.Errorf("ParseName() = %v, want %v", got, tt.want) + } + }) + } +} + func downloadReady(listener net.Listener, connections int, t *testing.T) fetcher.Fetcher { return doDownloadReady(buildFetcher(), listener, connections, t) } @@ -359,11 +407,11 @@ func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t } func buildFetcher() *Fetcher { - fb := new(FetcherManager) - fetcher := fb.Build() + fm := new(FetcherManager) + fetcher := fm.Build() newController := controller.NewController() newController.GetConfig = func(v any) { - json.Unmarshal([]byte(test.ToJson(fb.DefaultConfig())), v) + json.Unmarshal([]byte(test.ToJson(fm.DefaultConfig())), v) } fetcher.Setup(newController) return fetcher.(*Fetcher) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index e60a1371..8bb2c7f4 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -930,26 +930,28 @@ func (d *Downloader) doStart(task *Task) (err error) { } if isCreate { - d.checkDuplicateLock.Lock() - defer d.checkDuplicateLock.Unlock() - task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name) - // check if the download file is duplicated and rename it automatically. - if task.Meta.Res.Name != "" { - task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name) - fullDirPath := task.Meta.FolderPath() - newName, err := util.CheckDuplicateAndRename(fullDirPath) - if err != nil { - return err + if task.fetcherManager.AutoRename() { + d.checkDuplicateLock.Lock() + defer d.checkDuplicateLock.Unlock() + task.Meta.Opts.Name = util.ReplaceInvalidFilename(task.Meta.Opts.Name) + // check if the download file is duplicated and rename it automatically. + if task.Meta.Res.Name != "" { + task.Meta.Res.Name = util.ReplaceInvalidFilename(task.Meta.Res.Name) + fullDirPath := task.Meta.FolderPath() + newName, err := util.CheckDuplicateAndRename(fullDirPath) + if err != nil { + return err + } + task.Meta.Opts.Name = newName + } else { + task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name) + fullFilePath := task.Meta.SingleFilepath() + newName, err := util.CheckDuplicateAndRename(fullFilePath) + if err != nil { + return err + } + task.Meta.Opts.Name = newName } - task.Meta.Opts.Name = newName - } else { - task.Meta.Res.Files[0].Name = util.ReplaceInvalidFilename(task.Meta.Res.Files[0].Name) - fullFilePath := task.Meta.SingleFilepath() - newName, err := util.CheckDuplicateAndRename(fullFilePath) - if err != nil { - return err - } - task.Meta.Opts.Name = newName } task.Meta.Res.CalcSize(task.Meta.Opts.SelectFiles) diff --git a/pkg/download/model.go b/pkg/download/model.go index 0ec58fda..9edf955c 100644 --- a/pkg/download/model.go +++ b/pkg/download/model.go @@ -1,6 +1,7 @@ package download import ( + "encoding/json" "github.com/GopeedLab/gopeed/internal/controller" "github.com/GopeedLab/gopeed/internal/fetcher" "github.com/GopeedLab/gopeed/internal/protocol/bt" @@ -49,6 +50,47 @@ func NewTask() *Task { } } +// Name returns the display name of the task. +func (t *Task) Name() string { + // Custom name first + if t.Meta.Opts.Name != "" { + return t.Meta.Opts.Name + } + + // Task is not resolved, parse the name from the URL + if t.Meta.Res == nil { + fallbackName := "unknown" + if t.fetcherManager == nil { + return fallbackName + } + parseName := t.fetcherManager.ParseName(t.Meta.Req.URL) + if parseName == "" { + return fallbackName + } + return parseName + } + + // Task is a folder + if t.Meta.Res.Name != "" { + return t.Meta.Res.Name + } + + // Get the name of the first file + return t.Meta.Res.Files[0].Name +} + +func (t *Task) MarshalJSON() ([]byte, error) { + type rawTaskType Task + jsonTask := struct { + rawTaskType + Name string `json:"name"` + }{ + rawTaskType(*t), + t.Name(), + } + return json.Marshal(jsonTask) +} + func (t *Task) updateStatus(status base.Status) { t.UpdatedAt = time.Now() t.Status = status diff --git a/ui/flutter/lib/api/model/downloader_config.g.dart b/ui/flutter/lib/api/model/downloader_config.g.dart index c30a59ef..7d7449e1 100644 --- a/ui/flutter/lib/api/model/downloader_config.g.dart +++ b/ui/flutter/lib/api/model/downloader_config.g.dart @@ -9,7 +9,7 @@ part of 'downloader_config.dart'; DownloaderConfig _$DownloaderConfigFromJson(Map json) => DownloaderConfig( downloadDir: json['downloadDir'] as String? ?? '', - maxRunning: json['maxRunning'] as int? ?? 0, + maxRunning: (json['maxRunning'] as num?)?.toInt() ?? 0, ) ..protocolConfig = ProtocolConfig.fromJson( json['protocolConfig'] as Map?) @@ -38,7 +38,7 @@ Map _$ProtocolConfigToJson(ProtocolConfig instance) => HttpConfig _$HttpConfigFromJson(Map json) => HttpConfig( userAgent: json['userAgent'] as String? ?? '', - connections: json['connections'] as int? ?? 0, + connections: (json['connections'] as num?)?.toInt() ?? 0, useServerCtime: json['useServerCtime'] as bool? ?? false, ); @@ -50,14 +50,14 @@ Map _$HttpConfigToJson(HttpConfig instance) => }; BtConfig _$BtConfigFromJson(Map json) => BtConfig( - listenPort: json['listenPort'] as int? ?? 0, + listenPort: (json['listenPort'] as num?)?.toInt() ?? 0, trackers: (json['trackers'] as List?) ?.map((e) => e as String) .toList() ?? const [], seedKeep: json['seedKeep'] as bool? ?? false, seedRatio: (json['seedRatio'] as num?)?.toDouble() ?? 0, - seedTime: json['seedTime'] as int? ?? 0, + seedTime: (json['seedTime'] as num?)?.toInt() ?? 0, ); Map _$BtConfigToJson(BtConfig instance) => { diff --git a/ui/flutter/lib/api/model/task.dart b/ui/flutter/lib/api/model/task.dart index c8d34cea..53acf9e6 100644 --- a/ui/flutter/lib/api/model/task.dart +++ b/ui/flutter/lib/api/model/task.dart @@ -11,6 +11,7 @@ enum Protocol { http, bt } @JsonSerializable(explicitToJson: true) class Task { String id; + String name; Protocol? protocol; Meta meta; Status status; @@ -21,6 +22,7 @@ class Task { Task({ required this.id, + required this.name, required this.meta, required this.status, required this.uploading, diff --git a/ui/flutter/lib/api/model/task.g.dart b/ui/flutter/lib/api/model/task.g.dart index 47c6efa7..125edefb 100644 --- a/ui/flutter/lib/api/model/task.g.dart +++ b/ui/flutter/lib/api/model/task.g.dart @@ -8,6 +8,7 @@ part of 'task.dart'; Task _$TaskFromJson(Map json) => Task( id: json['id'] as String, + name: json['name'] as String, meta: Meta.fromJson(json['meta'] as Map), status: $enumDecode(_$StatusEnumMap, json['status']), uploading: json['uploading'] as bool, @@ -19,6 +20,7 @@ Task _$TaskFromJson(Map json) => Task( Map _$TaskToJson(Task instance) { final val = { 'id': instance.id, + 'name': instance.name, }; void writeNotNull(String key, dynamic value) { @@ -52,11 +54,11 @@ const _$ProtocolEnumMap = { }; Progress _$ProgressFromJson(Map json) => Progress( - used: json['used'] as int, - speed: json['speed'] as int, - downloaded: json['downloaded'] as int, - uploadSpeed: json['uploadSpeed'] as int, - uploaded: json['uploaded'] as int, + used: (json['used'] as num).toInt(), + speed: (json['speed'] as num).toInt(), + downloaded: (json['downloaded'] as num).toInt(), + uploadSpeed: (json['uploadSpeed'] as num).toInt(), + uploaded: (json['uploaded'] as num).toInt(), ); Map _$ProgressToJson(Progress instance) => { diff --git a/ui/flutter/lib/app/modules/task/views/task_view.dart b/ui/flutter/lib/app/modules/task/views/task_view.dart index 511e8557..a3ccb052 100644 --- a/ui/flutter/lib/app/modules/task/views/task_view.dart +++ b/ui/flutter/lib/app/modules/task/views/task_view.dart @@ -82,8 +82,7 @@ class TaskView extends GetView { ), ListTile( title: Text('taskName'.tr), - subtitle: - buildTooltipSubtitle(selectTask.value?.showName)), + subtitle: buildTooltipSubtitle(selectTask.value?.name)), ListTile( title: Text('taskUrl'.tr), subtitle: @@ -121,37 +120,12 @@ class TaskView extends GetView { } extension TaskEnhance on Task { - String get showName { - if (meta.opts.name.isNotEmpty) { - return meta.opts.name; - } - if (meta.res == null) { - final u = Uri.parse(meta.req.url); - if (u.scheme.startsWith("http")) { - return u.path.isNotEmpty - ? u.path.substring(u.path.lastIndexOf("/") + 1) - : u.host; - } else { - final params = u.queryParameters; - if (params.containsKey("dn")) { - return params["dn"]!; - } else { - return params["xt"]!.split(":").last; - } - } - } - if (meta.res!.name.isNotEmpty) { - return meta.res!.name; - } - return meta.res!.files[0].name; - } - bool get isFolder { return meta.res?.name.isNotEmpty ?? false; } String get explorerUrl { - return path.join(Util.safeDir(meta.opts.path), Util.safeDir(showName)); + return path.join(Util.safeDir(meta.opts.path), Util.safeDir(name)); } Future explorer() async { diff --git a/ui/flutter/lib/app/views/buid_task_list_view.dart b/ui/flutter/lib/app/views/buid_task_list_view.dart index b69b79ed..cb630efa 100644 --- a/ui/flutter/lib/app/views/buid_task_list_view.dart +++ b/ui/flutter/lib/app/views/buid_task_list_view.dart @@ -183,10 +183,10 @@ class BuildTaskListView extends GetView { mainAxisSize: MainAxisSize.min, children: [ ListTile( - title: Text(task.showName), + title: Text(task.name), leading: isFolderTask() ? const Icon(FaIcons.folder) - : Icon(FaIcons.allIcons[findIcon(task.showName)])), + : Icon(FaIcons.allIcons[findIcon(task.name)])), Row( children: [ Expanded(