wip: use tool manager

refactor/offline-download
Andy Hsu 2023-10-05 22:13:02 +08:00
parent 0380d7fff9
commit 12dfb60a66
14 changed files with 117 additions and 168 deletions

View File

@ -34,8 +34,7 @@ the address is defined in config file`,
utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart) utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart)
time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second) time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second)
} }
bootstrap.InitAria2() bootstrap.InitOfflineDownloadTools()
bootstrap.InitQbittorrent()
bootstrap.LoadStorages() bootstrap.LoadStorages()
if !flags.Debug && !flags.Dev { if !flags.Debug && !flags.Dev {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)

View File

@ -1,16 +0,0 @@
package bootstrap
import (
"github.com/alist-org/alist/v3/internal/aria2"
"github.com/alist-org/alist/v3/pkg/utils"
)
func InitAria2() {
go func() {
_, err := aria2.InitClient(2)
if err != nil {
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
utils.Log.Infof("Aria2 not ready.")
}
}()
}

View File

@ -0,0 +1,17 @@
package bootstrap
import (
"github.com/alist-org/alist/v3/internal/offline_download/tool"
"github.com/alist-org/alist/v3/pkg/utils"
)
func InitOfflineDownloadTools() {
for k, v := range tool.Tools {
res, err := v.Init()
if err != nil {
utils.Log.Warnf("init tool %s failed: %s", k, err)
} else {
utils.Log.Infof("init tool %s success: %s", k, res)
}
}
}

View File

@ -1,15 +0,0 @@
package bootstrap
import (
"github.com/alist-org/alist/v3/internal/qbittorrent"
"github.com/alist-org/alist/v3/pkg/utils"
)
func InitQbittorrent() {
go func() {
err := qbittorrent.InitClient()
if err != nil {
utils.Log.Infof("qbittorrent not ready.")
}
}()
}

View File

@ -32,7 +32,7 @@ type User struct {
// Determine permissions by bit // Determine permissions by bit
// 0: can see hidden files // 0: can see hidden files
// 1: can access without password // 1: can access without password
// 2: can add aria2 tasks // 2: can add offline download tasks
// 3: can mkdir and upload // 3: can mkdir and upload
// 4: can rename // 4: can rename
// 5: can move // 5: can move
@ -40,7 +40,6 @@ type User struct {
// 7: can remove // 7: can remove
// 8: webdav read // 8: webdav read
// 9: webdav write // 9: webdav write
// 10: can add qbittorrent tasks
Permission int32 `json:"permission"` Permission int32 `json:"permission"`
OtpSecret string `json:"-"` OtpSecret string `json:"-"`
SsoID string `json:"sso_id"` // unique by sso platform SsoID string `json:"sso_id"` // unique by sso platform
@ -83,7 +82,7 @@ func (u *User) CanAccessWithoutPassword() bool {
return u.IsAdmin() || (u.Permission>>1)&1 == 1 return u.IsAdmin() || (u.Permission>>1)&1 == 1
} }
func (u *User) CanAddAria2Tasks() bool { func (u *User) CanAddOfflineDownloadTasks() bool {
return u.IsAdmin() || (u.Permission>>2)&1 == 1 return u.IsAdmin() || (u.Permission>>2)&1 == 1
} }
@ -115,10 +114,6 @@ func (u *User) CanWebdavManage() bool {
return u.IsAdmin() || (u.Permission>>9)&1 == 1 return u.IsAdmin() || (u.Permission>>9)&1 == 1
} }
func (u *User) CanAddQbittorrentTasks() bool {
return u.IsAdmin() || (u.Permission>>10)&1 == 1
}
func (u *User) JoinPath(reqPath string) (string, error) { func (u *User) JoinPath(reqPath string) (string, error) {
return utils.JoinBasePath(u.BasePath, reqPath) return utils.JoinBasePath(u.BasePath, reqPath)
} }

View File

@ -50,11 +50,11 @@ func (a *Aria2) IsReady() bool {
return a.client != nil return a.client != nil
} }
func (a *Aria2) AddURI(args *tool.AddUriArgs) (string, error) { func (a *Aria2) AddURL(args *tool.AddUrlArgs) (string, error) {
options := map[string]interface{}{ options := map[string]interface{}{
"dir": args.TempDir, "dir": args.TempDir,
} }
gid, err := a.client.AddURI([]string{args.Uri}, options) gid, err := a.client.AddURI([]string{args.Url}, options)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -109,7 +109,20 @@ func (a *Aria2) Status(tid string) (*tool.Status, error) {
return s, nil return s, nil
} }
func (a *Aria2) GetFile(tid string) *tool.File { func (a *Aria2) GetFiles(tid string) []tool.File {
//files, err := a.client.GetFiles(tid)
//if err != nil {
// return nil
//}
//return utils.MustSliceConvert(files, func(f rpc.FileInfo) tool.File {
// return tool.File{
// //ReadCloser: nil,
// Name: path.Base(f.Path),
// Size: f.Length,
// Path: "",
// Modified: time.Time{},
// }
//})
return nil return nil
} }

View File

@ -13,13 +13,13 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type AddURIArgs struct { type AddURLArgs struct {
URI string URL string
DstDirPath string DstDirPath string
Tool string Tool string
} }
func AddURI(ctx context.Context, args *AddURIArgs) error { func AddURL(ctx context.Context, args *AddURLArgs) error {
// get tool // get tool
tool, err := Tools.Get(args.Tool) tool, err := Tools.Get(args.Tool)
if err != nil { if err != nil {
@ -27,7 +27,10 @@ func AddURI(ctx context.Context, args *AddURIArgs) error {
} }
// check tool is ready // check tool is ready
if !tool.IsReady() { if !tool.IsReady() {
return errors.Wrapf(err, "tool %s is not ready", args.Tool) // try to init tool
if _, err := tool.Init(); err != nil {
return errors.Wrapf(err, "failed init tool %s", args.Tool)
}
} }
// check storage // check storage
storage, dstDirActualPath, err := op.GetStorageAndActualPath(args.DstDirPath) storage, dstDirActualPath, err := op.GetStorageAndActualPath(args.DstDirPath)
@ -54,20 +57,21 @@ func AddURI(ctx context.Context, args *AddURIArgs) error {
uid := uuid.NewString() uid := uuid.NewString()
tempDir := filepath.Join(conf.Conf.TempDir, args.Tool, uid) tempDir := filepath.Join(conf.Conf.TempDir, args.Tool, uid)
signal := make(chan int) signal := make(chan int)
gid, err := tool.AddURI(&AddUriArgs{ gid, err := tool.AddURL(&AddUrlArgs{
Uri: args.URI, Url: args.URL,
UID: uid, UID: uid,
TempDir: tempDir, TempDir: tempDir,
Signal: signal, Signal: signal,
}) })
if err != nil { if err != nil {
return errors.Wrapf(err, "[%s] failed to add uri %s", args.Tool, args.URI) return errors.Wrapf(err, "[%s] failed to add uri %s", args.Tool, args.URL)
} }
DownTaskManager.Submit(task.WithCancelCtx(&task.Task[string]{ DownTaskManager.Submit(task.WithCancelCtx(&task.Task[string]{
ID: gid, ID: gid,
Name: fmt.Sprintf("download %s to [%s](%s)", args.URI, storage.GetStorage().MountPath, dstDirActualPath), Name: fmt.Sprintf("download %s to [%s](%s)", args.URL, storage.GetStorage().MountPath, dstDirActualPath),
Func: func(tsk *task.Task[string]) error { Func: func(tsk *task.Task[string]) error {
m := &Monitor{ m := &Monitor{
tool: tool,
tsk: tsk, tsk: tsk,
tempDir: tempDir, tempDir: tempDir,
dstDirPath: args.DstDirPath, dstDirPath: args.DstDirPath,

View File

@ -8,8 +8,8 @@ import (
"github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/model"
) )
type AddUriArgs struct { type AddUrlArgs struct {
Uri string Url string
UID string UID string
TempDir string TempDir string
Signal chan int Signal chan int
@ -28,17 +28,18 @@ type Tool interface {
Items() []model.SettingItem Items() []model.SettingItem
Init() (string, error) Init() (string, error)
IsReady() bool IsReady() bool
// AddURI add an uri to download, return the task id // AddURL add an uri to download, return the task id
AddURI(args *AddUriArgs) (string, error) AddURL(args *AddUrlArgs) (string, error)
// Remove the download if task been canceled // Remove the download if task been canceled
Remove(tid string) error Remove(tid string) error
// Status return the status of the download task, if an error occurred, return the error in Status.Err // Status return the status of the download task, if an error occurred, return the error in Status.Err
Status(tid string) (*Status, error) Status(tid string) (*Status, error)
// GetFile return an io.ReadCloser as the download file, if nil, means walk the temp dir to get the files // GetFiles return the files of the download task, if nil, means walk the temp dir to get the files
GetFile(tid string) *File GetFiles(tid string) []File
} }
type File struct { type File struct {
// ReadCloser for http client
io.ReadCloser io.ReadCloser
Name string Name string
Size int64 Size int64

View File

@ -3,7 +3,6 @@ package tool
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -104,9 +103,9 @@ func (m *Monitor) Complete() error {
if err != nil { if err != nil {
return errors.WithMessage(err, "failed get storage") return errors.WithMessage(err, "failed get storage")
} }
var files []*File var files []File
if f := m.tool.GetFile(m.tsk.ID); f != nil { if f := m.tool.GetFiles(m.tsk.ID); f != nil {
files = append(files, f) files = f
} else { } else {
files, err = GetFiles(m.tempDir) files, err = GetFiles(m.tempDir)
if err != nil { if err != nil {
@ -138,7 +137,7 @@ func (m *Monitor) Complete() error {
s := &stream.FileStream{ s := &stream.FileStream{
Ctx: nil, Ctx: nil,
Obj: &model.Object{ Obj: &model.Object{
Name: path.Base(file.Path), Name: filepath.Base(file.Path),
Size: file.Size, Size: file.Size,
Modified: file.Modified, Modified: file.Modified,
IsFolder: false, IsFolder: false,

View File

@ -5,14 +5,14 @@ import (
"path/filepath" "path/filepath"
) )
func GetFiles(dir string) ([]*File, error) { func GetFiles(dir string) ([]File, error) {
var files []*File var files []File
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
if !info.IsDir() { if !info.IsDir() {
files = append(files, &File{ files = append(files, File{
Name: info.Name(), Name: info.Name(),
Size: info.Size(), Size: info.Size(),
Path: path, Path: path,

View File

@ -4,7 +4,9 @@ import (
"github.com/alist-org/alist/v3/internal/aria2" "github.com/alist-org/alist/v3/internal/aria2"
"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/op" "github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/internal/qbittorrent"
"github.com/alist-org/alist/v3/server/common" "github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -36,30 +38,51 @@ func SetAria2(c *gin.Context) {
common.SuccessResp(c, version) common.SuccessResp(c, version)
} }
type AddAria2Req struct { type SetQbittorrentReq struct {
Urls []string `json:"urls"` Url string `json:"url" form:"url"`
Path string `json:"path"` Seedtime string `json:"seedtime" form:"seedtime"`
} }
func AddAria2(c *gin.Context) { func SetQbittorrent(c *gin.Context) {
user := c.MustGet("user").(*model.User) var req SetQbittorrentReq
if !user.CanAddAria2Tasks() { if err := c.ShouldBind(&req); err != nil {
common.ErrorStrResp(c, "permission denied", 403) common.ErrorResp(c, err, 400)
return return
} }
if !aria2.IsAria2Ready() { items := []model.SettingItem{
// try to init client {Key: conf.QbittorrentUrl, Value: req.Url, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
_, err := aria2.InitClient(2) {Key: conf.QbittorrentSeedtime, Value: req.Seedtime, Type: conf.TypeNumber, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
if err != nil { }
if err := op.SaveSettingItems(items); err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
if !aria2.IsAria2Ready() { if err := qbittorrent.InitClient(); err != nil {
common.ErrorStrResp(c, "aria2 still not ready after init", 500) common.ErrorResp(c, err, 500)
return return
} }
common.SuccessResp(c, "ok")
} }
var req AddAria2Req
func OfflineDownloadTools(c *gin.Context) {
tools := tool.Tools.Names()
common.SuccessResp(c, tools)
}
type AddOfflineDownloadReq struct {
Urls []string `json:"urls"`
Path string `json:"path"`
Tool string `json:"tool"`
}
func AddOfflineDownload(c *gin.Context) {
user := c.MustGet("user").(*model.User)
if !user.CanAddOfflineDownloadTasks() {
common.ErrorStrResp(c, "permission denied", 403)
return
}
var req AddOfflineDownloadReq
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
@ -70,7 +93,11 @@ func AddAria2(c *gin.Context) {
return return
} }
for _, url := range req.Urls { for _, url := range req.Urls {
err := aria2.AddURI(c, url, reqPath) err := tool.AddURL(c, &tool.AddURLArgs{
URL: url,
DstDirPath: reqPath,
Tool: req.Tool,
})
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return

View File

@ -1,79 +0,0 @@
package handles
import (
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/internal/qbittorrent"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
)
type SetQbittorrentReq struct {
Url string `json:"url" form:"url"`
Seedtime string `json:"seedtime" form:"seedtime"`
}
func SetQbittorrent(c *gin.Context) {
var req SetQbittorrentReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
items := []model.SettingItem{
{Key: conf.QbittorrentUrl, Value: req.Url, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
{Key: conf.QbittorrentSeedtime, Value: req.Seedtime, Type: conf.TypeNumber, Group: model.SINGLE, Flag: model.PRIVATE},
}
if err := op.SaveSettingItems(items); err != nil {
common.ErrorResp(c, err, 500)
return
}
if err := qbittorrent.InitClient(); err != nil {
common.ErrorResp(c, err, 500)
return
}
common.SuccessResp(c, "ok")
}
type AddQbittorrentReq struct {
Urls []string `json:"urls"`
Path string `json:"path"`
}
func AddQbittorrent(c *gin.Context) {
user := c.MustGet("user").(*model.User)
if !user.CanAddQbittorrentTasks() {
common.ErrorStrResp(c, "permission denied", 403)
return
}
if !qbittorrent.IsQbittorrentReady() {
// try to init client
err := qbittorrent.InitClient()
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if !qbittorrent.IsQbittorrentReady() {
common.ErrorStrResp(c, "qbittorrent still not ready after init", 500)
return
}
}
var req AddQbittorrentReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, url := range req.Urls {
err := qbittorrent.AddURL(c, url, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
}
common.SuccessResp(c)
}

View File

@ -3,8 +3,8 @@ package handles
import ( import (
"strconv" "strconv"
"github.com/alist-org/alist/v3/internal/aria2"
"github.com/alist-org/alist/v3/internal/fs" "github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/offline_download/tool"
"github.com/alist-org/alist/v3/internal/qbittorrent" "github.com/alist-org/alist/v3/internal/qbittorrent"
"github.com/alist-org/alist/v3/pkg/task" "github.com/alist-org/alist/v3/pkg/task"
"github.com/alist-org/alist/v3/server/common" "github.com/alist-org/alist/v3/server/common"
@ -116,10 +116,12 @@ func taskRoute[K comparable](g *gin.RouterGroup, manager *task.Manager[K], k2Str
} }
func SetupTaskRoute(g *gin.RouterGroup) { func SetupTaskRoute(g *gin.RouterGroup) {
taskRoute(g.Group("/aria2_down"), aria2.DownTaskManager, strK2Str, str2StrK)
taskRoute(g.Group("/aria2_transfer"), aria2.TransferTaskManager, uint64K2Str, str2Uint64K)
taskRoute(g.Group("/upload"), fs.UploadTaskManager, uint64K2Str, str2Uint64K) taskRoute(g.Group("/upload"), fs.UploadTaskManager, uint64K2Str, str2Uint64K)
taskRoute(g.Group("/copy"), fs.CopyTaskManager, uint64K2Str, str2Uint64K) taskRoute(g.Group("/copy"), fs.CopyTaskManager, uint64K2Str, str2Uint64K)
taskRoute(g.Group("/qbit_down"), qbittorrent.DownTaskManager, strK2Str, str2StrK) taskRoute(g.Group("/qbit_down"), qbittorrent.DownTaskManager, strK2Str, str2StrK)
taskRoute(g.Group("/qbit_transfer"), qbittorrent.TransferTaskManager, uint64K2Str, str2Uint64K) taskRoute(g.Group("/qbit_transfer"), qbittorrent.TransferTaskManager, uint64K2Str, str2Uint64K)
//taskRoute(g.Group("/aria2_down"), aria2.DownTaskManager, strK2Str, str2StrK)
//taskRoute(g.Group("/aria2_transfer"), aria2.TransferTaskManager, uint64K2Str, str2Uint64K)
taskRoute(g.Group("/offline_download"), tool.DownTaskManager, strK2Str, str2StrK)
taskRoute(g.Group("/offline_download_transfer"), tool.TransferTaskManager, uint64K2Str, str2Uint64K)
} }

View File

@ -70,6 +70,7 @@ func Init(e *gin.Engine) {
// no need auth // no need auth
public := api.Group("/public") public := api.Group("/public")
public.Any("/settings", handles.PublicSettings) public.Any("/settings", handles.PublicSettings)
public.Any("/offline_download_tools", handles.OfflineDownloadTools)
_fs(auth.Group("/fs")) _fs(auth.Group("/fs"))
admin(auth.Group("/admin", middlewares.AuthAdmin)) admin(auth.Group("/admin", middlewares.AuthAdmin))
@ -155,8 +156,9 @@ func _fs(g *gin.RouterGroup) {
g.PUT("/put", middlewares.FsUp, handles.FsStream) g.PUT("/put", middlewares.FsUp, handles.FsStream)
g.PUT("/form", middlewares.FsUp, handles.FsForm) g.PUT("/form", middlewares.FsUp, handles.FsForm)
g.POST("/link", middlewares.AuthAdmin, handles.Link) g.POST("/link", middlewares.AuthAdmin, handles.Link)
g.POST("/add_aria2", handles.AddAria2) //g.POST("/add_aria2", handles.AddOfflineDownload)
g.POST("/add_qbit", handles.AddQbittorrent) //g.POST("/add_qbit", handles.AddQbittorrent)
g.POST("/add_offline_download", handles.AddOfflineDownload)
} }
func Cors(r *gin.Engine) { func Cors(r *gin.Engine) {