Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions internal/offline_download/qbit/qbit.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package qbit

import (
"io"
"net/http"
"net/url"
"strings"

"github.com/OpenListTeam/OpenList/v4/drivers/base"
"github.com/OpenListTeam/OpenList/v4/internal/conf"
"github.com/OpenListTeam/OpenList/v4/internal/errs"
"github.com/OpenListTeam/OpenList/v4/internal/model"
"github.com/OpenListTeam/OpenList/v4/internal/net"
"github.com/OpenListTeam/OpenList/v4/internal/offline_download/tool"
"github.com/OpenListTeam/OpenList/v4/internal/setting"
"github.com/OpenListTeam/OpenList/v4/pkg/qbittorrent"
"github.com/OpenListTeam/OpenList/v4/pkg/torrent"
"github.com/pkg/errors"
)

const maxQbittorrentTorrentSize = 10 * 1024 * 1024

type QBittorrent struct {
client qbittorrent.Client
}
Expand Down Expand Up @@ -46,13 +56,50 @@ func (a *QBittorrent) IsReady() bool {
}

func (a *QBittorrent) AddURL(args *tool.AddUrlArgs) (string, error) {
err := a.client.AddFromLink(args.Url, args.TempDir, args.UID)
var err error
if len(args.TorrentData) > 0 {
err = a.client.AddFromTorrent(args.TorrentData, args.TempDir, args.UID)
} else if torrentData, ok := fetchTorrentDataFromURL(args); ok {
err = a.client.AddFromTorrent(torrentData, args.TempDir, args.UID)
} else {
err = a.client.AddFromLink(args.Url, args.TempDir, args.UID)
}
if err != nil {
return "", err
}
return args.UID, nil
}

func fetchTorrentDataFromURL(args *tool.AddUrlArgs) ([]byte, bool) {
u, err := url.Parse(strings.TrimSpace(args.Url))
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
return nil, false
}

resp, err := net.RequestHttp(
args.Ctx,
http.MethodGet,
http.Header{"User-Agent": []string{base.UserAgent}},
args.Url,
)
if err != nil {
return nil, false
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, false
}

data, err := io.ReadAll(io.LimitReader(resp.Body, maxQbittorrentTorrentSize+1))
if err != nil || len(data) > maxQbittorrentTorrentSize {
return nil, false
}
if _, err := torrent.Decode(data); err != nil {
return nil, false
}
return data, true
}

func (a *QBittorrent) Remove(task *tool.DownloadTask) error {
err := a.client.Delete(task.GID, false)
return err
Expand All @@ -65,7 +112,11 @@ 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
} 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
Expand Down
10 changes: 8 additions & 2 deletions internal/offline_download/tool/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ const (
DeleteOnUploadFailed DeletePolicy = "delete_on_upload_failed"
DeleteNever DeletePolicy = "delete_never"
DeleteAlways DeletePolicy = "delete_always"
DeleteAfterSeeding DeletePolicy = "delete_after_seeding"
UploadDownloadStream DeletePolicy = "upload_download_stream"
)

type AddURLArgs struct {
URL string
TorrentData []byte
DstDirPath string
Tool string
DeletePolicy DeletePolicy
Expand All @@ -55,6 +57,9 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
if storage.Config().NoUpload {
return nil, errors.WithStack(errs.UploadNotSupported)
}
if len(args.TorrentData) > 0 && args.Tool != "qBittorrent" {
return nil, fmt.Errorf("%s does not support uploaded torrent files, please submit a magnet link or use qBittorrent", args.Tool)
}
// check path is valid
obj, err := op.Get(ctx, storage, dstDirActualPath)
if err != nil {
Expand All @@ -68,7 +73,7 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
}
}
// try putting url
if args.Tool == "SimpleHttp" {
if len(args.TorrentData) == 0 && args.Tool == "SimpleHttp" {
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")
}
Expand All @@ -80,7 +85,7 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
}

// ed2k 链接自动路由:如果当前工具不支持 ed2k,自动尝试使用迅雷系工具
if isEd2kURL(args.URL) {
if len(args.TorrentData) == 0 && isEd2kURL(args.URL) {
if !isEd2kCapableTool(args.Tool) {
// 尝试找到一个可用的支持 ed2k 的工具
fallbackTool, fallbackName := findEd2kCapableTool()
Expand Down Expand Up @@ -171,6 +176,7 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
ApiUrl: common.GetApiUrl(ctx),
},
Url: args.URL,
TorrentData: args.TorrentData,
DstDirPath: args.DstDirPath,
TempDir: tempDir,
DeletePolicy: deletePolicy,
Expand Down
11 changes: 6 additions & 5 deletions internal/offline_download/tool/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
)

type AddUrlArgs struct {
Url string
UID string
TempDir string
Signal chan int
Ctx context.Context
Url string
TorrentData []byte
UID string
TempDir string
Signal chan int
Ctx context.Context
}

type Status struct {
Expand Down
95 changes: 73 additions & 22 deletions internal/offline_download/tool/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
type DownloadTask struct {
task.TaskExtension
Url string `json:"url"`
TorrentData []byte `json:"-"`
DstDirPath string `json:"dst_dir_path"`
TempDir string `json:"temp_dir"`
DeletePolicy DeletePolicy `json:"delete_policy"`
DeleteAfterTime time.Time `json:"delete_after_time,omitempty"`
Toolname string `json:"toolname"`
Status string `json:"-"`
Signal chan int `json:"-"`
Expand Down Expand Up @@ -54,11 +56,12 @@ func (t *DownloadTask) Run() error {
t.Signal = nil
}()
gid, err := t.tool.AddURL(&AddUrlArgs{
Ctx: t.Ctx(),
Url: t.Url,
UID: t.ID,
TempDir: t.TempDir,
Signal: t.Signal,
Ctx: t.Ctx(),
Url: t.Url,
TorrentData: t.TorrentData,
UID: t.ID,
TempDir: t.TempDir,
Signal: t.Signal,
})
if err != nil {
return err
Expand Down Expand Up @@ -116,26 +119,28 @@ outer:
t.Status = "offline download completed, maybe transferring"
// hack for qBittorrent
if t.tool.Name() == "qBittorrent" {
seedTime := setting.GetInt(conf.QbittorrentSeedtime, 0)
if seedTime >= 0 {
if seedDuration, ok := t.seedingDuration(); ok {
t.Status = "offline download completed, waiting for seeding"
Comment on lines 119 to 123
<-time.After(time.Minute * time.Duration(seedTime))
err := t.tool.Remove(t)
if err != nil {
log.Errorln(err.Error())
<-time.After(seedDuration)
if t.shouldRemoveTaskAfterSeeding() {
err := t.tool.Remove(t)
if err != nil {
log.Errorln(err.Error())
}
}
}
}

if t.tool.Name() == "Transmission" {
// hack for transmission
seedTime := setting.GetInt(conf.TransmissionSeedtime, 0)
if seedTime >= 0 {
if seedDuration, ok := t.seedingDuration(); ok {
t.Status = "offline download completed, waiting for seeding"
<-time.After(time.Minute * time.Duration(seedTime))
err := t.tool.Remove(t)
if err != nil {
log.Errorln(err.Error())
<-time.After(seedDuration)
if t.shouldRemoveTaskAfterSeeding() {
err := t.tool.Remove(t)
if err != nil {
log.Errorln(err.Error())
}
}
}
}
Expand Down Expand Up @@ -164,6 +169,7 @@ func (t *DownloadTask) Update() (bool, error) {
}
// if download completed
if info.Completed {
t.setDeleteAfterTime()
err := t.Transfer()
return true, errors.WithMessage(err, "failed to transfer file")
}
Expand All @@ -174,16 +180,60 @@ func (t *DownloadTask) Update() (bool, error) {
return false, nil
}

func (t *DownloadTask) setDeleteAfterTime() {
if t.DeletePolicy != DeleteAfterSeeding || !t.isSeedingTool() || !t.DeleteAfterTime.IsZero() {
return
}
seedDuration, ok := t.seedingDuration()
if !ok {
return
}
t.DeleteAfterTime = time.Now().Add(seedDuration)
}

func (t *DownloadTask) transferDeletePolicy() DeletePolicy {
if t.DeletePolicy == DeleteAfterSeeding && !t.isSeedingTool() {
return DeleteNever
}
return t.DeletePolicy
}

func (t *DownloadTask) isSeedingTool() bool {
toolName := t.tool.Name()
return toolName == "qBittorrent" || toolName == "Transmission"
}

func (t *DownloadTask) shouldRemoveTaskAfterSeeding() bool {
return t.DeletePolicy != DeleteNever
}

func (t *DownloadTask) seedingDuration() (time.Duration, bool) {
var seedTime int
switch t.tool.Name() {
case "qBittorrent":
seedTime = setting.GetInt(conf.QbittorrentSeedtime, 0)
case "Transmission":
seedTime = setting.GetInt(conf.TransmissionSeedtime, 0)
default:
return 0, false
}
if seedTime < 0 {
return 0, false
}
return time.Minute * time.Duration(seedTime), true
}

func (t *DownloadTask) Transfer() error {
toolName := t.tool.Name()
deletePolicy := t.transferDeletePolicy()
if toolName == "115 Cloud" || toolName == "115 Open" || toolName == "123 Open" || toolName == "123Pan" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderX" || toolName == "ThunderBrowser" {
// 如果不是直接下载到目标路径,则进行转存
if t.TempDir != t.DstDirPath {
return transferObj(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy)
return transferObj(t.Ctx(), t.TempDir, t.DstDirPath, deletePolicy, t.DeleteAfterTime)
}
return nil
}
if t.DeletePolicy == UploadDownloadStream {
if deletePolicy == UploadDownloadStream {
dstStorage, dstDirActualPath, err := op.GetStorageAndActualPath(t.DstDirPath)
if err != nil {
return errors.WithMessage(err, "failed get dst storage")
Expand All @@ -200,16 +250,17 @@ func (t *DownloadTask) Transfer() error {
DstStorage: dstStorage,
DstStorageMp: dstStorage.GetStorage().MountPath,
},
DeletePolicy: t.DeletePolicy,
Url: t.Url,
DeletePolicy: deletePolicy,
DeleteAfterTime: t.DeleteAfterTime,
Url: t.Url,
}
tsk.SetTotalBytes(t.GetTotalBytes())
tsk.groupID = path.Join(tsk.DstStorageMp, tsk.DstActualPath)
task_group.TransferCoordinator.AddTask(tsk.groupID, nil)
TransferTaskManager.Add(tsk)
return nil
}
return transferStd(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy)
return transferStd(t.Ctx(), t.TempDir, t.DstDirPath, deletePolicy, t.DeleteAfterTime)
}

func (t *DownloadTask) GetName() string {
Expand Down
Loading