From 1dab099906fd9b3ab4a3ad17d7cf65667430655d Mon Sep 17 00:00:00 2001 From: kobe koto <91669733+kobe-koto@users.noreply.github.com> Date: Tue, 16 Jun 2026 04:19:34 +0800 Subject: [PATCH] fix: handle unknown-size offline download tasks --- internal/offline_download/aria2/aria2.go | 6 ++- internal/offline_download/http/client.go | 27 ++++++++++--- internal/offline_download/qbit/qbit.go | 5 ++- internal/offline_download/tool/download.go | 1 + internal/offline_download/tool/transfer.go | 46 ++++++++++++++++++---- server/handles/task.go | 3 ++ 6 files changed, 73 insertions(+), 15 deletions(-) diff --git a/internal/offline_download/aria2/aria2.go b/internal/offline_download/aria2/aria2.go index 5c037ff4c..dce1981e7 100644 --- a/internal/offline_download/aria2/aria2.go +++ b/internal/offline_download/aria2/aria2.go @@ -100,7 +100,9 @@ func (a *Aria2) Status(task *tool.DownloadTask) (*tool.Status, error) { Err: err, TotalBytes: total, } - s.Progress = float64(downloaded) / float64(total) * 100 + if total > 0 { + s.Progress = float64(downloaded) / float64(total) * 100 + } if len(info.FollowedBy) != 0 { s.NewGID = info.FollowedBy[0] notify.Signals.Delete(task.GID) @@ -109,12 +111,14 @@ func (a *Aria2) Status(task *tool.DownloadTask) (*tool.Status, error) { switch info.Status { case "complete": s.Completed = true + s.Progress = 100 case "error": s.Err = errors.Errorf("failed to download %s, error: %s", task.GID, info.ErrorMessage) case "active": s.Status = "aria2: " + info.Status if info.Seeder == "true" { s.Completed = true + s.Progress = 100 } case "waiting", "paused": s.Status = "aria2: " + info.Status diff --git a/internal/offline_download/http/client.go b/internal/offline_download/http/client.go index 86314031d..059e0321b 100644 --- a/internal/offline_download/http/client.go +++ b/internal/offline_download/http/client.go @@ -80,15 +80,21 @@ func (s SimpleHttp) Run(task *tool.DownloadTask) error { } fileSize := resp.ContentLength if streamPut { - if fileSize == 0 { - start, end, _ := http_range.ParseContentRange(resp.Header.Get("Content-Range")) - fileSize = start + end + if fileSize <= 0 { + start, end, err := http_range.ParseContentRange(resp.Header.Get("Content-Range")) + if err == nil && end >= start { + fileSize = end - start + 1 + } + } + if fileSize > 0 { + task.SetTotalBytes(fileSize) } - task.SetTotalBytes(fileSize) task.TempDir = filename return nil } - task.SetTotalBytes(fileSize) + if fileSize > 0 { + task.SetTotalBytes(fileSize) + } // save to temp dir if err := os.MkdirAll(task.TempDir, os.ModePerm); err != nil { return err @@ -104,7 +110,16 @@ func (s SimpleHttp) Run(task *tool.DownloadTask) error { } defer file.Close() err = utils.CopyWithCtx(task.Ctx(), file, resp.Body, fileSize, task.SetProgress) - return err + if err != nil { + return err + } + info, err := file.Stat() + if err != nil { + return err + } + task.SetTotalBytes(info.Size()) + task.SetProgress(100) + return nil } func init() { diff --git a/internal/offline_download/qbit/qbit.go b/internal/offline_download/qbit/qbit.go index aac3a2c72..d363160ce 100644 --- a/internal/offline_download/qbit/qbit.go +++ b/internal/offline_download/qbit/qbit.go @@ -65,10 +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 + if info.Size > 0 { + s.Progress = float64(info.Completed) / float64(info.Size) * 100 + } switch info.State { case qbittorrent.UPLOADING, qbittorrent.PAUSEDUP, qbittorrent.QUEUEDUP, qbittorrent.STALLEDUP, qbittorrent.FORCEDUP, qbittorrent.CHECKINGUP: s.Completed = true + s.Progress = 100 case qbittorrent.ALLOCATING, qbittorrent.DOWNLOADING, qbittorrent.METADL, qbittorrent.PAUSEDDL, qbittorrent.QUEUEDDL, qbittorrent.STALLEDDL, qbittorrent.CHECKINGDL, qbittorrent.FORCEDDL, qbittorrent.CHECKINGRESUMEDATA, qbittorrent.MOVING: s.Status = "[qBittorrent] downloading" case qbittorrent.ERROR, qbittorrent.MISSINGFILES, qbittorrent.UNKNOWN: diff --git a/internal/offline_download/tool/download.go b/internal/offline_download/tool/download.go index 5ee6ef4ff..7b7515206 100644 --- a/internal/offline_download/tool/download.go +++ b/internal/offline_download/tool/download.go @@ -201,6 +201,7 @@ func (t *DownloadTask) Transfer() error { }, DeletePolicy: t.DeletePolicy, Url: t.Url, + ParentTask: t, } tsk.SetTotalBytes(t.GetTotalBytes()) tsk.groupID = path.Join(tsk.DstStorageMp, tsk.DstActualPath) diff --git a/internal/offline_download/tool/transfer.go b/internal/offline_download/tool/transfer.go index 7109669ee..3e0eeac14 100644 --- a/internal/offline_download/tool/transfer.go +++ b/internal/offline_download/tool/transfer.go @@ -28,9 +28,10 @@ import ( type TransferTask struct { fs.TaskData - DeletePolicy DeletePolicy `json:"delete_policy"` - Url string `json:"url"` - groupID string `json:"-"` + DeletePolicy DeletePolicy `json:"delete_policy"` + Url string `json:"url"` + groupID string `json:"-"` + ParentTask *DownloadTask `json:"-"` } func (t *TransferTask) Run() error { @@ -53,11 +54,15 @@ func (t *TransferTask) Run() error { defer func() { t.SetEndTime(time.Now()) }() if t.SrcStorage == nil { if t.DeletePolicy == UploadDownloadStream { - rr, err := stream.GetRangeReaderFromLink(t.GetTotalBytes(), &model.Link{URL: t.Url}) + downloadSize := t.GetTotalBytes() + if downloadSize <= 0 { + downloadSize = -1 + } + rr, err := stream.GetRangeReaderFromLink(downloadSize, &model.Link{URL: t.Url}) if err != nil { return err } - r, err := rr.RangeRead(t.Ctx(), http_range.Range{Length: t.GetTotalBytes()}) + r, err := rr.RangeRead(t.Ctx(), http_range.Range{Length: downloadSize}) if err != nil { return err } @@ -67,7 +72,7 @@ func (t *TransferTask) Run() error { Ctx: t.Ctx(), Obj: &model.Object{ Name: name, - Size: t.GetTotalBytes(), + Size: downloadSize, Modified: time.Now(), IsFolder: false, }, @@ -75,7 +80,17 @@ func (t *TransferTask) Run() error { Mimetype: mimetype, Closers: utils.NewClosers(r), } - return op.Put(context.WithValue(t.Ctx(), conf.SkipHookKey, struct{}{}), t.DstStorage, t.DstActualPath, s, t.SetProgress) + if downloadSize < 0 { + if _, err := s.CacheFullAndWriter(nil, nil); err != nil { + return err + } + t.setKnownSize(s.GetSize()) + } + if err := op.Put(context.WithValue(t.Ctx(), conf.SkipHookKey, struct{}{}), t.DstStorage, t.DstActualPath, s, t.SetProgress); err != nil { + return err + } + t.SetCompletedSize(s.GetSize()) + return nil } return transferStdPath(t) } @@ -89,6 +104,23 @@ func (t *TransferTask) GetName() string { return fmt.Sprintf("transfer [%s](%s) to [%s](%s)", t.SrcStorageMp, t.SrcActualPath, t.DstStorageMp, t.DstActualPath) } +func (t *TransferTask) setKnownSize(size int64) { + if size >= 0 { + t.SetTotalBytes(size) + if t.ParentTask != nil { + t.ParentTask.SetTotalBytes(size) + } + } +} + +func (t *TransferTask) SetCompletedSize(size int64) { + t.setKnownSize(size) + t.SetProgress(100) + if t.ParentTask != nil { + t.ParentTask.SetProgress(100) + } +} + func (t *TransferTask) OnSucceeded() { if t.DeletePolicy == DeleteOnUploadSucceed || t.DeletePolicy == DeleteAlways { if t.SrcStorage == nil { diff --git a/server/handles/task.go b/server/handles/task.go index 032f363ad..edf6da62f 100644 --- a/server/handles/task.go +++ b/server/handles/task.go @@ -40,6 +40,9 @@ func getTaskInfo[T task.TaskExtensionInfo](task T) TaskInfo { if math.IsNaN(progress) { progress = 100 } + if math.IsInf(progress, 0) { + progress = 0 + } creatorName := "" creatorRole := -1 if task.GetCreator() != nil {