mirror of https://github.com/Xhofe/alist
feat(offline-download): allow using offline download tools in any storage (#7716)
* Feat(offline-download): allow using thunder offline download tool in any storage * Feat(offline-download): allow using 115 offline download tool in any storage * Feat(offline-download): allow using pikpak offline download tool in any storage * style(offline-download): unify offline download tool names * feat(offline-download): show available offline download tools only * Fix(offline-download): update unmodified tool names. --------- Co-authored-by: Andy Hsu <i@nn.ci>pull/7812/head^2
parent
e04114d102
commit
b60da9732f
|
@ -58,6 +58,15 @@ const (
|
||||||
TransmissionUri = "transmission_uri"
|
TransmissionUri = "transmission_uri"
|
||||||
TransmissionSeedtime = "transmission_seedtime"
|
TransmissionSeedtime = "transmission_seedtime"
|
||||||
|
|
||||||
|
// 115
|
||||||
|
Pan115TempDir = "115_temp_dir"
|
||||||
|
|
||||||
|
// pikpak
|
||||||
|
PikPakTempDir = "pikpak_temp_dir"
|
||||||
|
|
||||||
|
// thunder
|
||||||
|
ThunderTempDir = "thunder_temp_dir"
|
||||||
|
|
||||||
// single
|
// single
|
||||||
Token = "token"
|
Token = "token"
|
||||||
IndexProgress = "index_progress"
|
IndexProgress = "index_progress"
|
||||||
|
|
|
@ -3,6 +3,8 @@ package _115
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/115"
|
"github.com/alist-org/alist/v3/drivers/115"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
@ -33,13 +35,23 @@ func (p *Cloud115) Init() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Cloud115) IsReady() bool {
|
func (p *Cloud115) IsReady() bool {
|
||||||
|
tempDir := setting.GetStr(conf.Pan115TempDir)
|
||||||
|
if tempDir == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
storage, _, err := op.GetStorageAndActualPath(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, ok := storage.(*_115.Pan115); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Cloud115) AddURL(args *tool.AddUrlArgs) (string, error) {
|
func (p *Cloud115) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
// 添加新任务刷新缓存
|
// 添加新任务刷新缓存
|
||||||
p.refreshTaskCache = true
|
p.refreshTaskCache = true
|
||||||
// args.TempDir 已经被修改为了 DstDirPath
|
|
||||||
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -50,6 +62,11 @@ func (p *Cloud115) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if err := op.MakeDir(ctx, storage, actualPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -64,7 +81,7 @@ func (p *Cloud115) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Cloud115) Remove(task *tool.DownloadTask) error {
|
func (p *Cloud115) Remove(task *tool.DownloadTask) error {
|
||||||
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
|
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -81,7 +98,7 @@ func (p *Cloud115) Remove(task *tool.DownloadTask) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Cloud115) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
func (p *Cloud115) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||||
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
|
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package pikpak
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/pikpak"
|
"github.com/alist-org/alist/v3/drivers/pikpak"
|
||||||
|
@ -17,7 +19,7 @@ type PikPak struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PikPak) Name() string {
|
func (p *PikPak) Name() string {
|
||||||
return "pikpak"
|
return "PikPak"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PikPak) Items() []model.SettingItem {
|
func (p *PikPak) Items() []model.SettingItem {
|
||||||
|
@ -34,13 +36,23 @@ func (p *PikPak) Init() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PikPak) IsReady() bool {
|
func (p *PikPak) IsReady() bool {
|
||||||
|
tempDir := setting.GetStr(conf.PikPakTempDir)
|
||||||
|
if tempDir == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
storage, _, err := op.GetStorageAndActualPath(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, ok := storage.(*pikpak.PikPak); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PikPak) AddURL(args *tool.AddUrlArgs) (string, error) {
|
func (p *PikPak) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
// 添加新任务刷新缓存
|
// 添加新任务刷新缓存
|
||||||
p.refreshTaskCache = true
|
p.refreshTaskCache = true
|
||||||
// args.TempDir 已经被修改为了 DstDirPath
|
|
||||||
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -51,6 +63,11 @@ func (p *PikPak) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if err := op.MakeDir(ctx, storage, actualPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -65,7 +82,7 @@ func (p *PikPak) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PikPak) Remove(task *tool.DownloadTask) error {
|
func (p *PikPak) Remove(task *tool.DownloadTask) error {
|
||||||
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
|
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -82,7 +99,7 @@ func (p *PikPak) Remove(task *tool.DownloadTask) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PikPak) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
func (p *PikPak) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||||
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
|
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/thunder"
|
"github.com/alist-org/alist/v3/drivers/thunder"
|
||||||
|
@ -18,7 +20,7 @@ type Thunder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thunder) Name() string {
|
func (t *Thunder) Name() string {
|
||||||
return "thunder"
|
return "Thunder"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thunder) Items() []model.SettingItem {
|
func (t *Thunder) Items() []model.SettingItem {
|
||||||
|
@ -35,13 +37,23 @@ func (t *Thunder) Init() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thunder) IsReady() bool {
|
func (t *Thunder) IsReady() bool {
|
||||||
|
tempDir := setting.GetStr(conf.ThunderTempDir)
|
||||||
|
if tempDir == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
storage, _, err := op.GetStorageAndActualPath(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, ok := storage.(*thunder.Thunder); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thunder) AddURL(args *tool.AddUrlArgs) (string, error) {
|
func (t *Thunder) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
// 添加新任务刷新缓存
|
// 添加新任务刷新缓存
|
||||||
t.refreshTaskCache = true
|
t.refreshTaskCache = true
|
||||||
// args.TempDir 已经被修改为了 DstDirPath
|
|
||||||
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -52,6 +64,11 @@ func (t *Thunder) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if err := op.MakeDir(ctx, storage, actualPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -66,7 +83,7 @@ func (t *Thunder) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thunder) Remove(task *tool.DownloadTask) error {
|
func (t *Thunder) Remove(task *tool.DownloadTask) error {
|
||||||
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
|
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,7 +100,7 @@ func (t *Thunder) Remove(task *tool.DownloadTask) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Thunder) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
func (t *Thunder) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||||
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
|
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ package tool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_115 "github.com/alist-org/alist/v3/drivers/115"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/pikpak"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/thunder"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/internal/task"
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -76,19 +80,26 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
|
||||||
tempDir := filepath.Join(conf.Conf.TempDir, args.Tool, uid)
|
tempDir := filepath.Join(conf.Conf.TempDir, args.Tool, uid)
|
||||||
deletePolicy := args.DeletePolicy
|
deletePolicy := args.DeletePolicy
|
||||||
|
|
||||||
|
// 如果当前 storage 是对应网盘,则直接下载到目标路径,无需转存
|
||||||
switch args.Tool {
|
switch args.Tool {
|
||||||
case "115 Cloud":
|
case "115 Cloud":
|
||||||
tempDir = args.DstDirPath
|
if _, ok := storage.(*_115.Pan115); ok {
|
||||||
// 防止将下载好的文件删除
|
tempDir = args.DstDirPath
|
||||||
deletePolicy = DeleteNever
|
} else {
|
||||||
case "pikpak":
|
tempDir = filepath.Join(setting.GetStr(conf.Pan115TempDir), uid)
|
||||||
tempDir = args.DstDirPath
|
}
|
||||||
// 防止将下载好的文件删除
|
case "PikPak":
|
||||||
deletePolicy = DeleteNever
|
if _, ok := storage.(*pikpak.PikPak); ok {
|
||||||
case "thunder":
|
tempDir = args.DstDirPath
|
||||||
tempDir = args.DstDirPath
|
} else {
|
||||||
// 防止将下载好的文件删除
|
tempDir = filepath.Join(setting.GetStr(conf.PikPakTempDir), uid)
|
||||||
deletePolicy = DeleteNever
|
}
|
||||||
|
case "Thunder":
|
||||||
|
if _, ok := storage.(*thunder.Thunder); ok {
|
||||||
|
tempDir = args.DstDirPath
|
||||||
|
} else {
|
||||||
|
tempDir = filepath.Join(setting.GetStr(conf.ThunderTempDir), uid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package tool_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetFiles(t *testing.T) {
|
|
||||||
files, err := tool.GetFiles("..")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
t.Log(file.Name, file.Size, file.Path, file.Modified)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,6 @@
|
||||||
package tool
|
package tool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,28 +36,3 @@ type Tool interface {
|
||||||
// Run for simple http download
|
// Run for simple http download
|
||||||
Run(task *DownloadTask) error
|
Run(task *DownloadTask) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetFileser interface {
|
|
||||||
// GetFiles return the files of the download task, if nil, means walk the temp dir to get the files
|
|
||||||
GetFiles(task *DownloadTask) []File
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
// ReadCloser for http client
|
|
||||||
ReadCloser io.ReadCloser
|
|
||||||
Name string
|
|
||||||
Size int64
|
|
||||||
Path string
|
|
||||||
Modified time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) GetReadCloser() (io.ReadCloser, error) {
|
|
||||||
if f.ReadCloser != nil {
|
|
||||||
return f.ReadCloser, nil
|
|
||||||
}
|
|
||||||
file, err := os.Open(f.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (t *DownloadTask) Run() error {
|
||||||
}
|
}
|
||||||
if err := t.tool.Run(t); !errs.IsNotSupportError(err) {
|
if err := t.tool.Run(t); !errs.IsNotSupportError(err) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return t.Complete()
|
return t.Transfer()
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -80,10 +80,10 @@ outer:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if t.tool.Name() == "pikpak" {
|
if t.tool.Name() == "Pikpak" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if t.tool.Name() == "thunder" {
|
if t.tool.Name() == "Thunder" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if t.tool.Name() == "115 Cloud" {
|
if t.tool.Name() == "115 Cloud" {
|
||||||
|
@ -109,7 +109,7 @@ outer:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.tool.Name() == "transmission" {
|
if t.tool.Name() == "Transmission" {
|
||||||
// hack for transmission
|
// hack for transmission
|
||||||
seedTime := setting.GetInt(conf.TransmissionSeedtime, 0)
|
seedTime := setting.GetInt(conf.TransmissionSeedtime, 0)
|
||||||
if seedTime >= 0 {
|
if seedTime >= 0 {
|
||||||
|
@ -146,7 +146,7 @@ func (t *DownloadTask) Update() (bool, error) {
|
||||||
}
|
}
|
||||||
// if download completed
|
// if download completed
|
||||||
if info.Completed {
|
if info.Completed {
|
||||||
err := t.Complete()
|
err := t.Transfer()
|
||||||
return true, errors.WithMessage(err, "failed to transfer file")
|
return true, errors.WithMessage(err, "failed to transfer file")
|
||||||
}
|
}
|
||||||
// if download failed
|
// if download failed
|
||||||
|
@ -156,45 +156,16 @@ func (t *DownloadTask) Update() (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DownloadTask) Complete() error {
|
func (t *DownloadTask) Transfer() error {
|
||||||
var (
|
toolName := t.tool.Name()
|
||||||
files []File
|
if toolName == "115 Cloud" || toolName == "PikPak" || toolName == "Thunder" {
|
||||||
err error
|
// 如果不是直接下载到目标路径,则进行转存
|
||||||
)
|
if t.TempDir != t.DstDirPath {
|
||||||
if t.tool.Name() == "pikpak" {
|
return transferObj(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if t.tool.Name() == "thunder" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if t.tool.Name() == "115 Cloud" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if getFileser, ok := t.tool.(GetFileser); ok {
|
|
||||||
files = getFileser.GetFiles(t)
|
|
||||||
} else {
|
|
||||||
files, err = GetFiles(t.TempDir)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to get files")
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
// upload files
|
return transferStd(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy)
|
||||||
for i := range files {
|
|
||||||
file := files[i]
|
|
||||||
tsk := &TransferTask{
|
|
||||||
TaskExtension: task.TaskExtension{
|
|
||||||
Creator: t.GetCreator(),
|
|
||||||
},
|
|
||||||
file: file,
|
|
||||||
DstDirPath: t.DstDirPath,
|
|
||||||
TempDir: t.TempDir,
|
|
||||||
DeletePolicy: t.DeletePolicy,
|
|
||||||
FileDir: file.Path,
|
|
||||||
}
|
|
||||||
tsk.SetTotalBytes(file.Size)
|
|
||||||
TransferTaskManager.Add(tsk)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DownloadTask) GetName() string {
|
func (t *DownloadTask) GetName() string {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package tool
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,8 +26,11 @@ func (t ToolsManager) Add(tool Tool) {
|
||||||
func (t ToolsManager) Names() []string {
|
func (t ToolsManager) Names() []string {
|
||||||
names := make([]string, 0, len(t))
|
names := make([]string, 0, len(t))
|
||||||
for name := range t {
|
for name := range t {
|
||||||
names = append(names, name)
|
if tool, err := t.Get(name); err == nil && tool.IsReady() {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
sort.Strings(names)
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package tool
|
package tool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/stream"
|
"github.com/alist-org/alist/v3/internal/stream"
|
||||||
|
@ -14,80 +12,60 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/xhofe/tache"
|
"github.com/xhofe/tache"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
stdpath "path"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransferTask struct {
|
type TransferTask struct {
|
||||||
task.TaskExtension
|
task.TaskExtension
|
||||||
FileDir string `json:"file_dir"`
|
Status string `json:"-"` //don't save status to save space
|
||||||
DstDirPath string `json:"dst_dir_path"`
|
SrcObjPath string `json:"src_obj_path"`
|
||||||
TempDir string `json:"temp_dir"`
|
DstDirPath string `json:"dst_dir_path"`
|
||||||
DeletePolicy DeletePolicy `json:"delete_policy"`
|
SrcStorage driver.Driver `json:"-"`
|
||||||
file File
|
DstStorage driver.Driver `json:"-"`
|
||||||
|
SrcStorageMp string `json:"src_storage_mp"`
|
||||||
|
DstStorageMp string `json:"dst_storage_mp"`
|
||||||
|
DeletePolicy DeletePolicy `json:"delete_policy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransferTask) Run() error {
|
func (t *TransferTask) Run() error {
|
||||||
t.ClearEndTime()
|
t.ClearEndTime()
|
||||||
t.SetStartTime(time.Now())
|
t.SetStartTime(time.Now())
|
||||||
defer func() { t.SetEndTime(time.Now()) }()
|
defer func() { t.SetEndTime(time.Now()) }()
|
||||||
// check dstDir again
|
if t.SrcStorage == nil {
|
||||||
var err error
|
return transferStdPath(t)
|
||||||
if (t.file == File{}) {
|
} else {
|
||||||
t.file, err = GetFile(t.FileDir)
|
return transferObjPath(t)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to get file %s", t.FileDir)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(t.DstDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed get storage")
|
|
||||||
}
|
|
||||||
mimetype := utils.GetMimeType(t.file.Path)
|
|
||||||
rc, err := t.file.GetReadCloser()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to open file %s", t.file.Path)
|
|
||||||
}
|
|
||||||
s := &stream.FileStream{
|
|
||||||
Ctx: nil,
|
|
||||||
Obj: &model.Object{
|
|
||||||
Name: filepath.Base(t.file.Path),
|
|
||||||
Size: t.file.Size,
|
|
||||||
Modified: t.file.Modified,
|
|
||||||
IsFolder: false,
|
|
||||||
},
|
|
||||||
Reader: rc,
|
|
||||||
Mimetype: mimetype,
|
|
||||||
Closers: utils.NewClosers(rc),
|
|
||||||
}
|
|
||||||
relDir, err := filepath.Rel(t.TempDir, filepath.Dir(t.file.Path))
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("find relation directory error: %v", err)
|
|
||||||
}
|
|
||||||
newDistDir := filepath.Join(dstDirActualPath, relDir)
|
|
||||||
return op.Put(t.Ctx(), storage, newDistDir, s, t.SetProgress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransferTask) GetName() string {
|
func (t *TransferTask) GetName() string {
|
||||||
return fmt.Sprintf("transfer %s to [%s]", t.file.Path, t.DstDirPath)
|
return fmt.Sprintf("transfer [%s](%s) to [%s](%s)", t.SrcStorageMp, t.SrcObjPath, t.DstStorageMp, t.DstDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransferTask) GetStatus() string {
|
func (t *TransferTask) GetStatus() string {
|
||||||
return "transferring"
|
return t.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransferTask) OnSucceeded() {
|
func (t *TransferTask) OnSucceeded() {
|
||||||
if t.DeletePolicy == DeleteOnUploadSucceed || t.DeletePolicy == DeleteAlways {
|
if t.DeletePolicy == DeleteOnUploadSucceed || t.DeletePolicy == DeleteAlways {
|
||||||
err := os.Remove(t.file.Path)
|
if t.SrcStorage == nil {
|
||||||
if err != nil {
|
removeStdTemp(t)
|
||||||
log.Errorf("failed to delete file %s, error: %s", t.file.Path, err.Error())
|
} else {
|
||||||
|
removeObjTemp(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransferTask) OnFailed() {
|
func (t *TransferTask) OnFailed() {
|
||||||
if t.DeletePolicy == DeleteOnUploadFailed || t.DeletePolicy == DeleteAlways {
|
if t.DeletePolicy == DeleteOnUploadFailed || t.DeletePolicy == DeleteAlways {
|
||||||
err := os.Remove(t.file.Path)
|
if t.SrcStorage == nil {
|
||||||
if err != nil {
|
removeStdTemp(t)
|
||||||
log.Errorf("failed to delete file %s, error: %s", t.file.Path, err.Error())
|
} else {
|
||||||
|
removeObjTemp(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,3 +73,202 @@ func (t *TransferTask) OnFailed() {
|
||||||
var (
|
var (
|
||||||
TransferTaskManager *tache.Manager[*TransferTask]
|
TransferTaskManager *tache.Manager[*TransferTask]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func transferStd(ctx context.Context, tempDir, dstDirPath string, deletePolicy DeletePolicy) error {
|
||||||
|
dstStorage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get dst storage")
|
||||||
|
}
|
||||||
|
entries, err := os.ReadDir(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
taskCreator, _ := ctx.Value("user").(*model.User)
|
||||||
|
for _, entry := range entries {
|
||||||
|
t := &TransferTask{
|
||||||
|
TaskExtension: task.TaskExtension{
|
||||||
|
Creator: taskCreator,
|
||||||
|
},
|
||||||
|
SrcObjPath: stdpath.Join(tempDir, entry.Name()),
|
||||||
|
DstDirPath: dstDirActualPath,
|
||||||
|
DstStorage: dstStorage,
|
||||||
|
DstStorageMp: dstStorage.GetStorage().MountPath,
|
||||||
|
DeletePolicy: deletePolicy,
|
||||||
|
}
|
||||||
|
TransferTaskManager.Add(t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferStdPath(t *TransferTask) error {
|
||||||
|
t.Status = "getting src object"
|
||||||
|
info, err := os.Stat(t.SrcObjPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
t.Status = "src object is dir, listing objs"
|
||||||
|
entries, err := os.ReadDir(t.SrcObjPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
srcRawPath := stdpath.Join(t.SrcObjPath, entry.Name())
|
||||||
|
dstObjPath := stdpath.Join(t.DstDirPath, info.Name())
|
||||||
|
t := &TransferTask{
|
||||||
|
TaskExtension: task.TaskExtension{
|
||||||
|
Creator: t.Creator,
|
||||||
|
},
|
||||||
|
SrcObjPath: srcRawPath,
|
||||||
|
DstDirPath: dstObjPath,
|
||||||
|
DstStorage: t.DstStorage,
|
||||||
|
SrcStorageMp: t.SrcStorageMp,
|
||||||
|
DstStorageMp: t.DstStorageMp,
|
||||||
|
DeletePolicy: t.DeletePolicy,
|
||||||
|
}
|
||||||
|
TransferTaskManager.Add(t)
|
||||||
|
}
|
||||||
|
t.Status = "src object is dir, added all transfer tasks of files"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return transferStdFile(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferStdFile(t *TransferTask) error {
|
||||||
|
rc, err := os.Open(t.SrcObjPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open file %s", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
info, err := rc.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to get file %s", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
mimetype := utils.GetMimeType(t.SrcObjPath)
|
||||||
|
s := &stream.FileStream{
|
||||||
|
Ctx: nil,
|
||||||
|
Obj: &model.Object{
|
||||||
|
Name: filepath.Base(t.SrcObjPath),
|
||||||
|
Size: info.Size(),
|
||||||
|
Modified: info.ModTime(),
|
||||||
|
IsFolder: false,
|
||||||
|
},
|
||||||
|
Reader: rc,
|
||||||
|
Mimetype: mimetype,
|
||||||
|
Closers: utils.NewClosers(rc),
|
||||||
|
}
|
||||||
|
t.SetTotalBytes(info.Size())
|
||||||
|
return op.Put(t.Ctx(), t.DstStorage, t.DstDirPath, s, t.SetProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeStdTemp(t *TransferTask) {
|
||||||
|
info, err := os.Stat(t.SrcObjPath)
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := os.Remove(t.SrcObjPath); err != nil {
|
||||||
|
log.Errorf("failed to delete temp file %s, error: %s", t.SrcObjPath, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferObj(ctx context.Context, tempDir, dstDirPath string, deletePolicy DeletePolicy) error {
|
||||||
|
srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get src storage")
|
||||||
|
}
|
||||||
|
dstStorage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get dst storage")
|
||||||
|
}
|
||||||
|
objs, err := op.List(ctx, srcStorage, srcObjActualPath, model.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessagef(err, "failed list src [%s] objs", tempDir)
|
||||||
|
}
|
||||||
|
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||||
|
for _, obj := range objs {
|
||||||
|
t := &TransferTask{
|
||||||
|
TaskExtension: task.TaskExtension{
|
||||||
|
Creator: taskCreator,
|
||||||
|
},
|
||||||
|
SrcObjPath: stdpath.Join(srcObjActualPath, obj.GetName()),
|
||||||
|
DstDirPath: dstDirActualPath,
|
||||||
|
SrcStorage: srcStorage,
|
||||||
|
DstStorage: dstStorage,
|
||||||
|
SrcStorageMp: srcStorage.GetStorage().MountPath,
|
||||||
|
DstStorageMp: dstStorage.GetStorage().MountPath,
|
||||||
|
DeletePolicy: deletePolicy,
|
||||||
|
}
|
||||||
|
TransferTaskManager.Add(t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferObjPath(t *TransferTask) error {
|
||||||
|
t.Status = "getting src object"
|
||||||
|
srcObj, err := op.Get(t.Ctx(), t.SrcStorage, t.SrcObjPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessagef(err, "failed get src [%s] file", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
if srcObj.IsDir() {
|
||||||
|
t.Status = "src object is dir, listing objs"
|
||||||
|
objs, err := op.List(t.Ctx(), t.SrcStorage, t.SrcObjPath, model.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessagef(err, "failed list src [%s] objs", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
for _, obj := range objs {
|
||||||
|
if utils.IsCanceled(t.Ctx()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
srcObjPath := stdpath.Join(t.SrcObjPath, obj.GetName())
|
||||||
|
dstObjPath := stdpath.Join(t.DstDirPath, srcObj.GetName())
|
||||||
|
TransferTaskManager.Add(&TransferTask{
|
||||||
|
TaskExtension: task.TaskExtension{
|
||||||
|
Creator: t.Creator,
|
||||||
|
},
|
||||||
|
SrcObjPath: srcObjPath,
|
||||||
|
DstDirPath: dstObjPath,
|
||||||
|
SrcStorage: t.SrcStorage,
|
||||||
|
DstStorage: t.DstStorage,
|
||||||
|
SrcStorageMp: t.SrcStorageMp,
|
||||||
|
DstStorageMp: t.DstStorageMp,
|
||||||
|
DeletePolicy: t.DeletePolicy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Status = "src object is dir, added all transfer tasks of objs"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return transferObjFile(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferObjFile(t *TransferTask) error {
|
||||||
|
srcFile, err := op.Get(t.Ctx(), t.SrcStorage, t.SrcObjPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessagef(err, "failed get src [%s] file", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
link, _, err := op.Link(t.Ctx(), t.SrcStorage, t.SrcObjPath, model.LinkArgs{
|
||||||
|
Header: http.Header{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessagef(err, "failed get [%s] link", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
fs := stream.FileStream{
|
||||||
|
Obj: srcFile,
|
||||||
|
Ctx: t.Ctx(),
|
||||||
|
}
|
||||||
|
// any link provided is seekable
|
||||||
|
ss, err := stream.NewSeekableStream(fs, link)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessagef(err, "failed get [%s] stream", t.SrcObjPath)
|
||||||
|
}
|
||||||
|
t.SetTotalBytes(srcFile.GetSize())
|
||||||
|
return op.Put(t.Ctx(), t.DstStorage, t.DstDirPath, ss, t.SetProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeObjTemp(t *TransferTask) {
|
||||||
|
srcObj, err := op.Get(t.Ctx(), t.SrcStorage, t.SrcObjPath)
|
||||||
|
if err != nil || srcObj.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := op.Remove(t.Ctx(), t.SrcStorage, t.SrcObjPath); err != nil {
|
||||||
|
log.Errorf("failed to delete temp obj %s, error: %s", t.SrcObjPath, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
package tool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetFiles(dir string) ([]File, error) {
|
|
||||||
var files []File
|
|
||||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
files = append(files, File{
|
|
||||||
Name: info.Name(),
|
|
||||||
Size: info.Size(),
|
|
||||||
Path: path,
|
|
||||||
Modified: info.ModTime(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFile(path string) (File, error) {
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return File{}, err
|
|
||||||
}
|
|
||||||
return File{
|
|
||||||
Name: info.Name(),
|
|
||||||
Size: info.Size(),
|
|
||||||
Path: path,
|
|
||||||
Modified: info.ModTime(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -29,7 +29,7 @@ func (t *Transmission) Run(task *tool.DownloadTask) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transmission) Name() string {
|
func (t *Transmission) Name() string {
|
||||||
return "transmission"
|
return "Transmission"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transmission) Items() []model.SettingItem {
|
func (t *Transmission) Items() []model.SettingItem {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package handles
|
package handles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_115 "github.com/alist-org/alist/v3/drivers/115"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/pikpak"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/thunder"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||||
|
@ -73,11 +76,6 @@ func SetQbittorrent(c *gin.Context) {
|
||||||
common.SuccessResp(c, "ok")
|
common.SuccessResp(c, "ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
func OfflineDownloadTools(c *gin.Context) {
|
|
||||||
tools := tool.Tools.Names()
|
|
||||||
common.SuccessResp(c, tools)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SetTransmissionReq struct {
|
type SetTransmissionReq struct {
|
||||||
Uri string `json:"uri" form:"uri"`
|
Uri string `json:"uri" form:"uri"`
|
||||||
Seedtime string `json:"seedtime" form:"seedtime"`
|
Seedtime string `json:"seedtime" form:"seedtime"`
|
||||||
|
@ -97,7 +95,7 @@ func SetTransmission(c *gin.Context) {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_tool, err := tool.Tools.Get("transmission")
|
_tool, err := tool.Tools.Get("Transmission")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
|
@ -109,6 +107,143 @@ func SetTransmission(c *gin.Context) {
|
||||||
common.SuccessResp(c, "ok")
|
common.SuccessResp(c, "ok")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Set115Req struct {
|
||||||
|
TempDir string `json:"temp_dir" form:"temp_dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set115(c *gin.Context) {
|
||||||
|
var req Set115Req
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.TempDir != "" {
|
||||||
|
storage, _, err := op.GetStorageAndActualPath(req.TempDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorStrResp(c, "storage does not exists", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if storage.Config().CheckStatus && storage.GetStorage().Status != op.WORK {
|
||||||
|
common.ErrorStrResp(c, "storage not init: "+storage.GetStorage().Status, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := storage.(*_115.Pan115); !ok {
|
||||||
|
common.ErrorStrResp(c, "unsupported storage driver for offline download, only 115 Cloud is supported", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items := []model.SettingItem{
|
||||||
|
{Key: conf.Pan115TempDir, Value: req.TempDir, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||||
|
}
|
||||||
|
if err := op.SaveSettingItems(items); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_tool, err := tool.Tools.Get("115 Cloud")
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := _tool.Init(); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetPikPakReq struct {
|
||||||
|
TempDir string `json:"temp_dir" form:"temp_dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetPikPak(c *gin.Context) {
|
||||||
|
var req SetPikPakReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.TempDir != "" {
|
||||||
|
storage, _, err := op.GetStorageAndActualPath(req.TempDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorStrResp(c, "storage does not exists", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if storage.Config().CheckStatus && storage.GetStorage().Status != op.WORK {
|
||||||
|
common.ErrorStrResp(c, "storage not init: "+storage.GetStorage().Status, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := storage.(*pikpak.PikPak); !ok {
|
||||||
|
common.ErrorStrResp(c, "unsupported storage driver for offline download, only PikPak is supported", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items := []model.SettingItem{
|
||||||
|
{Key: conf.PikPakTempDir, Value: req.TempDir, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||||
|
}
|
||||||
|
if err := op.SaveSettingItems(items); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_tool, err := tool.Tools.Get("PikPak")
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := _tool.Init(); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetThunderReq struct {
|
||||||
|
TempDir string `json:"temp_dir" form:"temp_dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetThunder(c *gin.Context) {
|
||||||
|
var req SetThunderReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.TempDir != "" {
|
||||||
|
storage, _, err := op.GetStorageAndActualPath(req.TempDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorStrResp(c, "storage does not exists", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if storage.Config().CheckStatus && storage.GetStorage().Status != op.WORK {
|
||||||
|
common.ErrorStrResp(c, "storage not init: "+storage.GetStorage().Status, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := storage.(*thunder.Thunder); !ok {
|
||||||
|
common.ErrorStrResp(c, "unsupported storage driver for offline download, only Thunder is supported", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items := []model.SettingItem{
|
||||||
|
{Key: conf.ThunderTempDir, Value: req.TempDir, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||||
|
}
|
||||||
|
if err := op.SaveSettingItems(items); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_tool, err := tool.Tools.Get("Thunder")
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := _tool.Init(); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func OfflineDownloadTools(c *gin.Context) {
|
||||||
|
tools := tool.Tools.Names()
|
||||||
|
common.SuccessResp(c, tools)
|
||||||
|
}
|
||||||
|
|
||||||
type AddOfflineDownloadReq struct {
|
type AddOfflineDownloadReq struct {
|
||||||
Urls []string `json:"urls"`
|
Urls []string `json:"urls"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
|
|
|
@ -132,6 +132,9 @@ func admin(g *gin.RouterGroup) {
|
||||||
setting.POST("/set_aria2", handles.SetAria2)
|
setting.POST("/set_aria2", handles.SetAria2)
|
||||||
setting.POST("/set_qbit", handles.SetQbittorrent)
|
setting.POST("/set_qbit", handles.SetQbittorrent)
|
||||||
setting.POST("/set_transmission", handles.SetTransmission)
|
setting.POST("/set_transmission", handles.SetTransmission)
|
||||||
|
setting.POST("/set_115", handles.Set115)
|
||||||
|
setting.POST("/set_pikpak", handles.SetPikPak)
|
||||||
|
setting.POST("/set_thunder", handles.SetThunder)
|
||||||
|
|
||||||
// retain /admin/task API to ensure compatibility with legacy automation scripts
|
// retain /admin/task API to ensure compatibility with legacy automation scripts
|
||||||
_task(g.Group("/task"))
|
_task(g.Group("/task"))
|
||||||
|
|
Loading…
Reference in New Issue