From 9f22022e50c2ba2e212fc9dbe165fd23da6075e9 Mon Sep 17 00:00:00 2001 From: gengjiawen Date: Fri, 5 Jun 2026 02:04:05 +0000 Subject: [PATCH] feat(task): expose offline download file info Co-authored-by: Codex --- internal/offline_download/115/client.go | 4 ++- internal/offline_download/115_open/client.go | 4 ++- internal/offline_download/123/client.go | 9 ++++++ internal/offline_download/aria2/aria2.go | 20 ++++++++++++- internal/offline_download/http/client.go | 2 ++ internal/offline_download/pikpak/pikpak.go | 4 ++- internal/offline_download/qbit/qbit.go | 8 +++++- internal/offline_download/thunder/thunder.go | 2 ++ .../thunder_browser/thunder_browser.go | 2 ++ .../offline_download/thunderx/thunderx.go | 2 ++ internal/offline_download/tool/add.go | 23 +++++++++++---- internal/offline_download/tool/base.go | 2 ++ internal/offline_download/tool/download.go | 28 ++++++++++++++++++- .../offline_download/transmission/client.go | 8 +++++- server/handles/task.go | 15 ++++++++++ 15 files changed, 121 insertions(+), 12 deletions(-) diff --git a/internal/offline_download/115/client.go b/internal/offline_download/115/client.go index 9e9f702d5..563793521 100644 --- a/internal/offline_download/115/client.go +++ b/internal/offline_download/115/client.go @@ -126,8 +126,10 @@ func (p *Cloud115) Status(task *tool.DownloadTask) (*tool.Status, error) { s.Status = t.GetStatus() s.Completed = t.IsDone() s.TotalBytes = t.Size + s.FileName = t.Name + s.FileSize = t.Size if t.IsFailed() { - s.Err = fmt.Errorf(t.GetStatus()) + s.Err = fmt.Errorf("%s", t.GetStatus()) } return s, nil } diff --git a/internal/offline_download/115_open/client.go b/internal/offline_download/115_open/client.go index d12e02ec5..b7d810c4f 100644 --- a/internal/offline_download/115_open/client.go +++ b/internal/offline_download/115_open/client.go @@ -123,8 +123,10 @@ func (o *Open115) Status(task *tool.DownloadTask) (*tool.Status, error) { s.Status = t.GetStatus() s.Completed = t.IsDone() s.TotalBytes = t.Size + s.FileName = t.Name + s.FileSize = t.Size if t.IsFailed() { - s.Err = fmt.Errorf(t.GetStatus()) + s.Err = fmt.Errorf("%s", t.GetStatus()) } return s, nil } diff --git a/internal/offline_download/123/client.go b/internal/offline_download/123/client.go index 2c4f47048..5020f516b 100644 --- a/internal/offline_download/123/client.go +++ b/internal/offline_download/123/client.go @@ -128,6 +128,8 @@ func (*Pan123) Status(task *tool.DownloadTask) (*tool.Status, error) { return &tool.Status{ TotalBytes: t.Size, + FileName: offlineTaskFileName(t.Name, t.UploadName), + FileSize: t.Size, Progress: t.Progress, Completed: completed, Status: statusStr, @@ -135,6 +137,13 @@ func (*Pan123) Status(task *tool.DownloadTask) (*tool.Status, error) { }, nil } +func offlineTaskFileName(name, uploadName string) string { + if uploadName != "" { + return uploadName + } + return name +} + var _ tool.Tool = (*Pan123)(nil) func init() { diff --git a/internal/offline_download/aria2/aria2.go b/internal/offline_download/aria2/aria2.go index 5c037ff4c..a5fa80e7c 100644 --- a/internal/offline_download/aria2/aria2.go +++ b/internal/offline_download/aria2/aria2.go @@ -3,6 +3,7 @@ package aria2 import ( "context" "fmt" + "path" "strconv" "strings" "time" @@ -99,8 +100,12 @@ func (a *Aria2) Status(task *tool.DownloadTask) (*tool.Status, error) { Completed: info.Status == "complete", Err: err, TotalBytes: total, + FileName: fileNameFromStatus(info), + FileSize: total, + } + if total > 0 { + s.Progress = float64(downloaded) / float64(total) * 100 } - s.Progress = float64(downloaded) / float64(total) * 100 if len(info.FollowedBy) != 0 { s.NewGID = info.FollowedBy[0] notify.Signals.Delete(task.GID) @@ -126,6 +131,19 @@ func (a *Aria2) Status(task *tool.DownloadTask) (*tool.Status, error) { return s, nil } +func fileNameFromStatus(info rpc.StatusInfo) string { + if info.BitTorrent.Info.Name != "" { + return info.BitTorrent.Info.Name + } + for _, file := range info.Files { + if file.Selected == "false" || file.Path == "" { + continue + } + return path.Base(file.Path) + } + return "" +} + var _ tool.Tool = (*Aria2)(nil) func init() { diff --git a/internal/offline_download/http/client.go b/internal/offline_download/http/client.go index 86314031d..06f566681 100644 --- a/internal/offline_download/http/client.go +++ b/internal/offline_download/http/client.go @@ -79,11 +79,13 @@ func (s SimpleHttp) Run(task *tool.DownloadTask) error { filename = fmt.Sprintf("%s-%d-%x", filename, time.Now().UnixMilli(), rand.Uint32()) } fileSize := resp.ContentLength + task.SetFileInfo(filename, fileSize) if streamPut { if fileSize == 0 { start, end, _ := http_range.ParseContentRange(resp.Header.Get("Content-Range")) fileSize = start + end } + task.SetFileInfo(filename, fileSize) task.SetTotalBytes(fileSize) task.TempDir = filename return nil diff --git a/internal/offline_download/pikpak/pikpak.go b/internal/offline_download/pikpak/pikpak.go index f48ed9951..53d598db1 100644 --- a/internal/offline_download/pikpak/pikpak.go +++ b/internal/offline_download/pikpak/pikpak.go @@ -123,13 +123,15 @@ func (p *PikPak) Status(task *tool.DownloadTask) (*tool.Status, error) { if t.ID == task.GID { s.Progress = float64(t.Progress) s.Status = t.Message + s.FileName = t.FileName s.Completed = (t.Phase == "PHASE_TYPE_COMPLETE") s.TotalBytes, err = strconv.ParseInt(t.FileSize, 10, 64) if err != nil { s.TotalBytes = 0 } + s.FileSize = s.TotalBytes if t.Phase == "PHASE_TYPE_ERROR" { - s.Err = fmt.Errorf(t.Message) + s.Err = fmt.Errorf("%s", t.Message) } return s, nil } diff --git a/internal/offline_download/qbit/qbit.go b/internal/offline_download/qbit/qbit.go index aac3a2c72..e33fe3e4a 100644 --- a/internal/offline_download/qbit/qbit.go +++ b/internal/offline_download/qbit/qbit.go @@ -65,7 +65,13 @@ func (a *QBittorrent) Status(task *tool.DownloadTask) (*tool.Status, error) { } s := &tool.Status{} s.TotalBytes = info.Size - s.Progress = float64(info.Completed) / float64(info.Size) * 100 + s.FileName = info.Name + s.FileSize = info.Size + if info.Size > 0 { + s.Progress = float64(info.Completed) / float64(info.Size) * 100 + } else { + s.Progress = info.Progress * 100 + } switch info.State { case qbittorrent.UPLOADING, qbittorrent.PAUSEDUP, qbittorrent.QUEUEDUP, qbittorrent.STALLEDUP, qbittorrent.FORCEDUP, qbittorrent.CHECKINGUP: s.Completed = true diff --git a/internal/offline_download/thunder/thunder.go b/internal/offline_download/thunder/thunder.go index 7cbff89df..d7c0401b3 100644 --- a/internal/offline_download/thunder/thunder.go +++ b/internal/offline_download/thunder/thunder.go @@ -124,11 +124,13 @@ func (t *Thunder) Status(task *tool.DownloadTask) (*tool.Status, error) { if t.ID == task.GID { s.Progress = float64(t.Progress) s.Status = t.Message + s.FileName = t.FileName s.Completed = (t.Phase == "PHASE_TYPE_COMPLETE") s.TotalBytes, err = strconv.ParseInt(t.FileSize, 10, 64) if err != nil { s.TotalBytes = 0 } + s.FileSize = s.TotalBytes if t.Phase == "PHASE_TYPE_ERROR" { s.Err = errors.New(t.Message) } diff --git a/internal/offline_download/thunder_browser/thunder_browser.go b/internal/offline_download/thunder_browser/thunder_browser.go index 9324d7a76..0a0102012 100644 --- a/internal/offline_download/thunder_browser/thunder_browser.go +++ b/internal/offline_download/thunder_browser/thunder_browser.go @@ -151,11 +151,13 @@ func (t *ThunderBrowser) Status(task *tool.DownloadTask) (*tool.Status, error) { if t.ID == task.GID { s.Progress = float64(t.Progress) s.Status = t.Message + s.FileName = t.FileName s.Completed = t.Phase == "PHASE_TYPE_COMPLETE" s.TotalBytes, err = strconv.ParseInt(t.FileSize, 10, 64) if err != nil { s.TotalBytes = 0 } + s.FileSize = s.TotalBytes if t.Phase == "PHASE_TYPE_ERROR" { s.Err = errors.New(t.Message) } diff --git a/internal/offline_download/thunderx/thunderx.go b/internal/offline_download/thunderx/thunderx.go index 3ba4a3680..bb2319984 100644 --- a/internal/offline_download/thunderx/thunderx.go +++ b/internal/offline_download/thunderx/thunderx.go @@ -118,11 +118,13 @@ func (t *ThunderX) Status(task *tool.DownloadTask) (*tool.Status, error) { if t.ID == task.GID { s.Progress = float64(t.Progress) s.Status = t.Message + s.FileName = t.FileName s.Completed = t.Phase == "PHASE_TYPE_COMPLETE" s.TotalBytes, err = strconv.ParseInt(t.FileSize, 10, 64) if err != nil { s.TotalBytes = 0 } + s.FileSize = s.TotalBytes if t.Phase == "PHASE_TYPE_ERROR" { s.Err = errors.New(t.Message) } diff --git a/internal/offline_download/tool/add.go b/internal/offline_download/tool/add.go index 33128ccc2..03984bb2c 100644 --- a/internal/offline_download/tool/add.go +++ b/internal/offline_download/tool/add.go @@ -175,6 +175,7 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro TempDir: tempDir, DeletePolicy: deletePolicy, Toolname: args.Tool, + FileName: fileNameFromURL(args.URL), tool: tool, } DownloadTaskManager.Add(t) @@ -182,16 +183,28 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro } func tryPutUrl(ctx context.Context, path, urlStr string) error { - var dstName string - u, err := url.Parse(urlStr) - if err == nil { - dstName = stdpath.Base(u.Path) - } else { + dstName := fileNameFromURL(urlStr) + if dstName == "" { dstName = "UnnamedURL" } return fs.PutURL(ctx, path, dstName, urlStr) } +func fileNameFromURL(urlStr string) string { + u, err := url.Parse(urlStr) + if err != nil { + return "" + } + name := stdpath.Base(u.Path) + if name == "." || name == "/" { + return "" + } + if decodedName, err := url.PathUnescape(name); err == nil { + name = decodedName + } + return name +} + func isSimpleHttpSchemeUnsupported(urlStr string) bool { u, err := url.Parse(strings.TrimSpace(urlStr)) if err != nil || u.Scheme == "" { diff --git a/internal/offline_download/tool/base.go b/internal/offline_download/tool/base.go index 6de05e5b2..c1688dc19 100644 --- a/internal/offline_download/tool/base.go +++ b/internal/offline_download/tool/base.go @@ -13,6 +13,8 @@ type AddUrlArgs struct { type Status struct { TotalBytes int64 + FileName string + FileSize int64 Progress float64 NewGID string Completed bool diff --git a/internal/offline_download/tool/download.go b/internal/offline_download/tool/download.go index 5ee6ef4ff..6f550a794 100644 --- a/internal/offline_download/tool/download.go +++ b/internal/offline_download/tool/download.go @@ -25,6 +25,8 @@ type DownloadTask struct { TempDir string `json:"temp_dir"` DeletePolicy DeletePolicy `json:"delete_policy"` Toolname string `json:"toolname"` + FileName string `json:"file_name,omitempty"` + FileSize int64 `json:"file_size,omitempty"` Status string `json:"-"` Signal chan int `json:"-"` GID string `json:"-"` @@ -153,8 +155,12 @@ func (t *DownloadTask) Update() (bool, error) { return false, nil } t.callStatusRetried = 0 + totalBytes := max(info.FileSize, info.TotalBytes) + t.SetFileInfo(info.FileName, totalBytes) + if totalBytes > 0 { + t.SetTotalBytes(totalBytes) + } t.SetProgress(info.Progress) - t.SetTotalBytes(info.TotalBytes) t.Status = fmt.Sprintf("[%s]: %s", t.tool.Name(), info.Status) if info.NewGID != "" { log.Debugf("followen by: %+v", info.NewGID) @@ -215,6 +221,26 @@ func (t *DownloadTask) GetName() string { return fmt.Sprintf("download %s to (%s)", t.Url, t.DstDirPath) } +func (t *DownloadTask) SetFileInfo(fileName string, fileSize int64) { + if fileName != "" { + t.FileName = fileName + } + if fileSize > 0 { + t.FileSize = fileSize + } +} + +func (t *DownloadTask) GetFileName() string { + return t.FileName +} + +func (t *DownloadTask) GetFileSize() int64 { + if t.FileSize > 0 { + return t.FileSize + } + return t.GetTotalBytes() +} + func (t *DownloadTask) GetStatus() string { return t.Status } diff --git a/internal/offline_download/transmission/client.go b/internal/offline_download/transmission/client.go index f86390fb8..ab809b5d0 100644 --- a/internal/offline_download/transmission/client.go +++ b/internal/offline_download/transmission/client.go @@ -150,7 +150,13 @@ func (t *Transmission) Status(task *tool.DownloadTask) (*tool.Status, error) { Err: err, } s.Progress = *info.PercentDone * 100 - s.TotalBytes = int64(*info.SizeWhenDone / 8) + if info.Name != nil { + s.FileName = *info.Name + } + if info.SizeWhenDone != nil { + s.TotalBytes = int64(*info.SizeWhenDone / 8) + s.FileSize = s.TotalBytes + } switch *info.Status { case transmissionrpc.TorrentStatusCheckWait, diff --git a/server/handles/task.go b/server/handles/task.go index 032f363ad..1d7610785 100644 --- a/server/handles/task.go +++ b/server/handles/task.go @@ -27,9 +27,16 @@ type TaskInfo struct { StartTime *time.Time `json:"start_time"` EndTime *time.Time `json:"end_time"` TotalBytes int64 `json:"total_bytes"` + FileName string `json:"file_name,omitempty"` + FileSize int64 `json:"file_size,omitempty"` Error string `json:"error"` } +type taskFileInfo interface { + GetFileName() string + GetFileSize() int64 +} + func getTaskInfo[T task.TaskExtensionInfo](task T) TaskInfo { errMsg := "" if task.GetErr() != nil { @@ -46,6 +53,12 @@ func getTaskInfo[T task.TaskExtensionInfo](task T) TaskInfo { creatorName = task.GetCreator().Username creatorRole = task.GetCreator().Role } + fileName := "" + fileSize := int64(0) + if taskWithFileInfo, ok := any(task).(taskFileInfo); ok { + fileName = taskWithFileInfo.GetFileName() + fileSize = taskWithFileInfo.GetFileSize() + } return TaskInfo{ ID: task.GetID(), Name: task.GetName(), @@ -57,6 +70,8 @@ func getTaskInfo[T task.TaskExtensionInfo](task T) TaskInfo { StartTime: task.GetStartTime(), EndTime: task.GetEndTime(), TotalBytes: task.GetTotalBytes(), + FileName: fileName, + FileSize: fileSize, Error: errMsg, } }