8000 feat(thunder): add offline download tool by Lanfei · Pull Request #7673 · AlistGo/alist · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(thunder): add offline download tool #7673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 25, 2024
Merged
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
61 changes: 61 additions & 0 deletions drivers/thunder/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/alist-org/alist/v3/drivers/base"
Expand Down Expand Up @@ -522,3 +523,63 @@ func (xc *XunLeiCommon) IsLogin() bool {
_, err := xc.Request(XLUSER_API_URL+"/user/me", http.MethodGet, nil, nil)
return err == nil
}

// 离线下载文件
func (xc *XunLeiCommon) OfflineDownload(ctx context.Context, fileUrl string, parentDir model.Obj, fileName string) (*OfflineTask, error) {
var resp OfflineDownloadResp
_, err := xc.Request(FILE_API_URL, http.MethodPost, func(r *resty.Request) {
r.SetContext(ctx)
r.SetBody(&base.Json{
"kind": FILE,
"name": fileName,
"parent_id": parentDir.GetID(),
"upload_type": UPLOAD_TYPE_URL,
"url": base.Json{
"url": fileUrl,
},
})
}, &resp)

if err != nil {
return nil, err
}

return &resp.Task, err
}

/*
获取离线下载任务列表
*/
func (xc *XunLeiCommon) OfflineList(ctx context.Context, nextPageToken string) ([]OfflineTask, error) {
res := make([]OfflineTask, 0)

var resp OfflineListResp
_, err := xc.Request(TASK_API_URL, http.MethodGet, func(req *resty.Request) {
req.SetContext(ctx).
SetQueryParams(map[string]string{
"type": "offline",
"limit": "10000",
"page_token": nextPageToken,
})
}, &resp)

if err != nil {
return nil, fmt.Errorf("failed to get offline list: %w", err)
}
res = append(res, resp.Tasks...)
return res, nil
}

func (xc *XunLeiCommon) DeleteOfflineTasks(ctx context.Context, taskIDs []string, deleteFiles bool) error {
_, err := xc.Request(TASK_API_URL, http.MethodDelete, func(req *resty.Request) {
req.SetContext(ctx).
SetQueryParams(map[string]string{
"task_ids": strings.Join(taskIDs, ","),
"delete_files": strconv.FormatBool(deleteFiles),
})
}, nil)
if err != nil {
return fmt.Errorf("failed to delete tasks %v: %w", taskIDs, err)
}
return nil
}
47 changes: 47 additions & 0 deletions drivers/thunder/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,50 @@ type UploadTaskResponse struct {

File Files `json:"file"`
}

// 添加离线下载响应
type OfflineDownloadResp struct {
File *string `json:"file"`
Task OfflineTask `json:"task"`
UploadType string `json:"upload_type"`
URL struct {
Kind string `json:"kind"`
} `json:"url"`
}

// 离线下载列表
type OfflineListResp struct {
ExpiresIn int64 `json:"expires_in"`
NextPageToken string `json:"next_page_token"`
Tasks []OfflineTask `json:"tasks"`
}

// offlineTask
type OfflineTask struct {
Callback string `json:"callback"`
CreatedTime string `json:"created_time"`
FileID string `json:"file_id"`
FileName string `json:"file_name"`
FileSize string `json:"file_size"`
IconLink string `json:"icon_link"`
ID string `json:"id"`
Kind string `json:"kind"`
Message string `json:"message"`
Name string `json:"name"`
Params Params `json:"params"`
Phase string `json:"phase"` // PHASE_TYPE_RUNNING, PHASE_TYPE_ERROR, PHASE_TYPE_COMPLETE, PHASE_TYPE_PENDING
Progress int64 `json:"progress"`
Space string `json:"space"`
StatusSize int64 `json:"status_size"`
Statuses []string `json:"statuses"`
ThirdTaskID string `json:"third_task_id"`
Type string `json:"type"`
UpdatedTime string `json:"updated_time"`
UserID string `json:"user_id"`
}

type Params struct {
FolderType string `json:"folder_type"`
PredictSpeed string `json:"predict_speed"`
PredictType string `json:"predict_type"`
}
1 change: 1 addition & 0 deletions drivers/thunder/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
const (
API_URL = "https://api-pan.xunlei.com/drive/v1"
FILE_API_URL = API_URL + "/files"
TASK_API_URL = API_URL + "/tasks"
XLUSER_API_URL = "https://xluser-ssl.xunlei.com/v1"
)

Expand Down
1 change: 1 addition & 0 deletions internal/offline_download/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import (
_ "github.com/alist-org/alist/v3/internal/offline_download/http"
_ "github.com/alist-org/alist/v3/internal/offline_download/pikpak"
_ "github.com/alist-org/alist/v3/internal/offline_download/qbit"
_ "github.com/alist-org/alist/v3/internal/offline_download/thunder"
_ "github.com/alist-org/alist/v3/internal/offline_download/transmission"
)
126 changes: 126 additions & 0 deletions internal/offline_download/thunder/thunder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package thunder

import (
"context"
"errors"
"fmt"
"strconv"

"github.com/alist-org/alist/v3/drivers/thunder"
"github.com/alist-org/alist/v3/internal/errs"
"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/op"
)

type Thunder struct {
refreshTaskCache bool
}

func (t *Thunder) Name() string {
return "thunder"
}

func (t *Thunder) Items() []model.SettingItem {
return nil
}

func (t *Thunder) Run(task *tool.DownloadTask) error {
return errs.NotSupport
}

func (t *Thunder) Init() (string, error) {
t.refreshTaskCache = false
return "ok", nil
}

func (t *Thunder) IsReady() bool {
return true
}

func (t *Thunder) AddURL(args *tool.AddUrlArgs) (string, error) {
// 添加新任务刷新缓存
t.refreshTaskCache = true
// args.TempDir 已经被修改为了 DstDirPath
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
if err != nil {
return "", err
}
thunderDriver, ok := storage.(*thunder.Thunder)
if !ok {
return "", fmt.Errorf(" 6D47 ;unsupported storage driver for offline download, only Thunder is supported")
}

ctx := context.Background()
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
if err != nil {
return "", err
}

task, err := thunderDriver.OfflineDownload(ctx, args.Url, parentDir, "")
if err != nil {
return "", fmt.Errorf("failed to add offline download task: %w", err)
}

return task.ID, nil
}

func (t *Thunder) Remove(task *tool.DownloadTask) error {
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
if err != nil {
return err
}
thunderDriver, ok := storage.(*thunder.Thunder)
if !ok {
return fmt.Errorf("unsupported storage driver for offline download, only Thunder is supported")
}
ctx := context.Background()
err = thunderDriver.DeleteOfflineTasks(ctx, []string{task.GID}, false)
if err != nil {
return err
}
return nil
}

func (t *Thunder) Status(task *tool.DownloadTask) (*tool.Status, error) {
storage, _, err := op.GetStorageAndActualPath(task.DstDirPath)
if err != nil {
return nil, err
}
thunderDriver, ok := storage.(*thunder.Thunder)
if !ok {
return nil, fmt.Errorf("unsupported storage driver for offline download, only Thunder is supported")
}
tasks, err := t.GetTasks(thunderDriver)
if err != nil {
return nil, err
}
s := &tool.Status{
Progress: 0,
NewGID: "",
Completed: false,
Status: "the task has been deleted&q 9E81 uot;,
Err: nil,
}
for _, t := range tasks {
if t.ID == task.GID {
s.Progress = float64(t.Progress)
s.Status = t.Message
s.Completed = (t.Phase == "PHASE_TYPE_COMPLETE")
s.TotalBytes, err = strconv.ParseInt(t.FileSize, 10, 64)
if err != nil {
s.TotalBytes = 0
}
if t.Phase == "PHASE_TYPE_ERROR" {
s.Err = errors.New(t.Message)
}
return s, nil
}
}
s.Err = fmt.Errorf("the task has been deleted")
return s, nil
}

func init() {
tool.Tools.Add(&Thunder{})
}
42 changes: 42 additions & 0 deletions internal/offline_download/thunder/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package thunder

import (
"context"
"time"

"github.com/Xhofe/go-cache"
"github.com/alist-org/alist/v3/drivers/thunder"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/singleflight"
)

var taskCache = cache.NewMemCache(cache.WithShards[[]thunder.OfflineTask](16))
var taskG singleflight.Group[[]thunder.OfflineTask]

func (t *Thunder) GetTasks(thunderDriver *thunder.Thunder) ([]thunder.OfflineTask, error) {
key := op.Key(thunderDriver, "/drive/v1/task")
if !t.refreshTaskCache {
if tasks, ok := taskCache.Get(key); ok {
return tasks, nil
}
}
t.refreshTaskCache = false
tasks, err, _ := taskG.Do(key, func() ([]thunder.OfflineTask, error) {
ctx := context.Background()
tasks, err := thunderDriver.OfflineList(ctx, "")
if err != nil {
return nil, err
}
// 添加缓存 10s
if len(tasks) > 0 {
taskCache.Set(key, tasks, cache.WithEx[[]thunder.OfflineTask](time.Second*10))
} else {
taskCache.Del(key)
}
return tasks, nil
})
if err != nil {
return nil, err
}
return tasks, nil
}
4 changes: 4 additions & 0 deletions internal/offline_download/tool/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
tempDir = args.DstDirPath
// 防止将下载好的文件删除
deletePolicy = DeleteNever
case "thunder":
tempDir = args.DstDirPath
// 防止将下载好的文件删除
deletePolicy = DeleteNever
}

taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
Expand Down
6 changes: 6 additions & 0 deletions internal/offline_download/tool/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ outer:
if t.tool.Name() == "pikpak" {
return nil
}
if t.tool.Name() == "thunder" {
return nil
}
if t.tool.Name() == "115 Cloud" {
// hack for 115
<-time.After(time.Second * 1)
Expand Down Expand Up @@ -161,6 +164,9 @@ func (t *DownloadTask) Complete() error {
if t.tool.Name() == "pikpak" {
return nil
}
if t.tool.Name() == "thunder" {
return nil
}
if t.tool.Name() == "115 Cloud" {
return nil
}
Expand Down
0