From d92c10da560993628ebecc8195769c86bf3a120c Mon Sep 17 00:00:00 2001
From: kdxcxs <cx@kdxcxs.com>
Date: Wed, 15 Feb 2023 15:58:31 +0800
Subject: [PATCH] fix(qbittorrent): fix multiple bugs for qbittorrent download
 (close #3413 in #3427)

* fix(qbittorrent): wait for qbittorrent to parse torrent and create task

#3413

* fix(qbittorrent): check task state correctly

* fix(qbittorrent): fix path sent to `op.Put()`
---
 internal/qbittorrent/client.go  | 15 +++++++++-
 internal/qbittorrent/monitor.go | 52 ++++++++++++++++++---------------
 2 files changed, 43 insertions(+), 24 deletions(-)

diff --git a/internal/qbittorrent/client.go b/internal/qbittorrent/client.go
index ce7adf59..dec69ad5 100644
--- a/internal/qbittorrent/client.go
+++ b/internal/qbittorrent/client.go
@@ -242,6 +242,19 @@ type TorrentInfo struct {
 	Upspeed           int           `json:"upspeed"`            // 上传速度(字节/秒)
 }
 
+type InfoNotFoundError struct {
+	Id  string
+	Err error
+}
+
+func (i InfoNotFoundError) Error() string {
+	return "there should be exactly one task with tag \"alist-" + i.Id + "\""
+}
+
+func NewInfoNotFoundError(id string) InfoNotFoundError {
+	return InfoNotFoundError{Id: id}
+}
+
 func (c *client) GetInfo(id string) (TorrentInfo, error) {
 	var infos []TorrentInfo
 
@@ -266,7 +279,7 @@ func (c *client) GetInfo(id string) (TorrentInfo, error) {
 		return TorrentInfo{}, err
 	}
 	if len(infos) != 1 {
-		return TorrentInfo{}, errors.New("there should be exactly one task with tag \"alist-" + id + "\"")
+		return TorrentInfo{}, NewInfoNotFoundError(id)
 	}
 	return infos[0], nil
 }
diff --git a/internal/qbittorrent/monitor.go b/internal/qbittorrent/monitor.go
index 7d12e749..36883c40 100644
--- a/internal/qbittorrent/monitor.go
+++ b/internal/qbittorrent/monitor.go
@@ -9,7 +9,6 @@ import (
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 	"os"
-	"path"
 	"path/filepath"
 	"sync"
 	"sync/atomic"
@@ -29,6 +28,30 @@ func (m *Monitor) Loop() error {
 		completed bool
 	)
 	m.finish = make(chan struct{})
+
+	// wait for qbittorrent to parse torrent and create task
+	m.tsk.SetStatus("waiting for qbittorrent to parse torrent and create task")
+	waitCount := 0
+	for {
+		_, err := qbclient.GetInfo(m.tsk.ID)
+		if err == nil {
+			break
+		}
+		switch err.(type) {
+		case InfoNotFoundError:
+			break
+		default:
+			return err
+		}
+
+		waitCount += 1
+		if waitCount >= 60 {
+			return errors.New("torrent parse timeout")
+		}
+		timer := time.NewTimer(time.Second)
+		<-timer.C
+	}
+
 outer:
 	for {
 		select {
@@ -61,29 +84,13 @@ func (m *Monitor) update() (bool, error) {
 	progress := float64(info.Completed) / float64(info.Size) * 100
 	m.tsk.SetProgress(int(progress))
 	switch info.State {
-	case UPLOADING:
-	case PAUSEDUP:
-	case QUEUEDUP:
-	case STALLEDUP:
-	case FORCEDUP:
-	case CHECKINGUP:
+	case UPLOADING, PAUSEDUP, QUEUEDUP, STALLEDUP, FORCEDUP, CHECKINGUP:
 		err = m.complete()
 		return true, errors.WithMessage(err, "failed to transfer file")
-	case ALLOCATING:
-	case DOWNLOADING:
-	case METADL:
-	case PAUSEDDL:
-	case QUEUEDDL:
-	case STALLEDDL:
-	case CHECKINGDL:
-	case FORCEDDL:
-	case CHECKINGRESUMEDATA:
-	case MOVING:
-	case UNKNOWN: // or maybe should return an error for UNKNOWN?
+	case ALLOCATING, DOWNLOADING, METADL, PAUSEDDL, QUEUEDDL, STALLEDDL, CHECKINGDL, FORCEDDL, CHECKINGRESUMEDATA, MOVING:
 		m.tsk.SetStatus("qbittorrent downloading")
 		return false, nil
-	case ERROR:
-	case MISSINGFILES:
+	case ERROR, MISSINGFILES, UNKNOWN:
 		return true, errors.Errorf("failed to download %s, error: %s", m.tsk.ID, info.State)
 	}
 	return true, errors.New("unknown error occurred downloading qbittorrent") // should never happen
@@ -130,7 +137,7 @@ func (m *Monitor) complete() error {
 				}
 				stream := &model.FileStream{
 					Obj: &model.Object{
-						Name:     path.Base(filePath),
+						Name:     file.Name,
 						Size:     size,
 						Modified: time.Now(),
 						IsFolder: false,
@@ -138,8 +145,7 @@ func (m *Monitor) complete() error {
 					ReadCloser: f,
 					Mimetype:   mimetype,
 				}
-				newDistDir := filepath.Join(dstDirActualPath, file.Name)
-				return op.Put(tsk.Ctx, storage, newDistDir, stream, tsk.SetProgress)
+				return op.Put(tsk.Ctx, storage, dstDirActualPath, stream, tsk.SetProgress)
 			},
 		}))
 	}