diff --git a/cmd/server.go b/cmd/server.go index f78d82b8..0678e3e1 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -34,8 +34,7 @@ the address is defined in config file`, utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart) time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second) } - bootstrap.InitAria2() - bootstrap.InitQbittorrent() + bootstrap.InitOfflineDownloadTools() bootstrap.LoadStorages() if !flags.Debug && !flags.Dev { gin.SetMode(gin.ReleaseMode) diff --git a/internal/bootstrap/aria2.go b/internal/bootstrap/aria2.go deleted file mode 100644 index 60017dda..00000000 --- a/internal/bootstrap/aria2.go +++ /dev/null @@ -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.") - } - }() -} diff --git a/internal/bootstrap/offline_download.go b/internal/bootstrap/offline_download.go new file mode 100644 index 00000000..26e04071 --- /dev/null +++ b/internal/bootstrap/offline_download.go @@ -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) + } + } +} diff --git a/internal/bootstrap/qbittorrent.go b/internal/bootstrap/qbittorrent.go deleted file mode 100644 index 315977eb..00000000 --- a/internal/bootstrap/qbittorrent.go +++ /dev/null @@ -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.") - } - }() -} diff --git a/internal/model/user.go b/internal/model/user.go index d7b2863c..46fe9bd3 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -32,7 +32,7 @@ type User struct { // Determine permissions by bit // 0: can see hidden files // 1: can access without password - // 2: can add aria2 tasks + // 2: can add offline download tasks // 3: can mkdir and upload // 4: can rename // 5: can move @@ -40,7 +40,6 @@ type User struct { // 7: can remove // 8: webdav read // 9: webdav write - // 10: can add qbittorrent tasks Permission int32 `json:"permission"` OtpSecret string `json:"-"` 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 } -func (u *User) CanAddAria2Tasks() bool { +func (u *User) CanAddOfflineDownloadTasks() bool { 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 } -func (u *User) CanAddQbittorrentTasks() bool { - return u.IsAdmin() || (u.Permission>>10)&1 == 1 -} - func (u *User) JoinPath(reqPath string) (string, error) { return utils.JoinBasePath(u.BasePath, reqPath) } diff --git a/internal/offline_download/aria2/aria2.go b/internal/offline_download/aria2/aria2.go index a2a6135d..f2b9628c 100644 --- a/internal/offline_download/aria2/aria2.go +++ b/internal/offline_download/aria2/aria2.go @@ -50,11 +50,11 @@ func (a *Aria2) IsReady() bool { 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{}{ "dir": args.TempDir, } - gid, err := a.client.AddURI([]string{args.Uri}, options) + gid, err := a.client.AddURI([]string{args.Url}, options) if err != nil { return "", err } @@ -109,7 +109,20 @@ func (a *Aria2) Status(tid string) (*tool.Status, error) { 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 } diff --git a/internal/offline_download/tool/add.go b/internal/offline_download/tool/add.go index ce613838..ceaf92d3 100644 --- a/internal/offline_download/tool/add.go +++ b/internal/offline_download/tool/add.go @@ -13,13 +13,13 @@ import ( "github.com/pkg/errors" ) -type AddURIArgs struct { - URI string +type AddURLArgs struct { + URL string DstDirPath string Tool string } -func AddURI(ctx context.Context, args *AddURIArgs) error { +func AddURL(ctx context.Context, args *AddURLArgs) error { // get tool tool, err := Tools.Get(args.Tool) if err != nil { @@ -27,7 +27,10 @@ func AddURI(ctx context.Context, args *AddURIArgs) error { } // check tool is ready 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 storage, dstDirActualPath, err := op.GetStorageAndActualPath(args.DstDirPath) @@ -54,20 +57,21 @@ func AddURI(ctx context.Context, args *AddURIArgs) error { uid := uuid.NewString() tempDir := filepath.Join(conf.Conf.TempDir, args.Tool, uid) signal := make(chan int) - gid, err := tool.AddURI(&AddUriArgs{ - Uri: args.URI, + gid, err := tool.AddURL(&AddUrlArgs{ + Url: args.URL, UID: uid, TempDir: tempDir, Signal: signal, }) 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]{ 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 { m := &Monitor{ + tool: tool, tsk: tsk, tempDir: tempDir, dstDirPath: args.DstDirPath, diff --git a/internal/offline_download/tool/base.go b/internal/offline_download/tool/base.go index 8d5a5282..4689635b 100644 --- a/internal/offline_download/tool/base.go +++ b/internal/offline_download/tool/base.go @@ -8,8 +8,8 @@ import ( "github.com/alist-org/alist/v3/internal/model" ) -type AddUriArgs struct { - Uri string +type AddUrlArgs struct { + Url string UID string TempDir string Signal chan int @@ -28,17 +28,18 @@ type Tool interface { Items() []model.SettingItem Init() (string, error) IsReady() bool - // AddURI add an uri to download, return the task id - AddURI(args *AddUriArgs) (string, error) + // AddURL add an uri to download, return the task id + AddURL(args *AddUrlArgs) (string, error) // Remove the download if task been canceled Remove(tid string) error // Status return the status of the download task, if an error occurred, return the error in Status.Err 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 - GetFile(tid string) *File + // GetFiles return the files of the download task, if nil, means walk the temp dir to get the files + GetFiles(tid string) []File } type File struct { + // ReadCloser for http client io.ReadCloser Name string Size int64 diff --git a/internal/offline_download/tool/monitor.go b/internal/offline_download/tool/monitor.go index 79d82117..984bda17 100644 --- a/internal/offline_download/tool/monitor.go +++ b/internal/offline_download/tool/monitor.go @@ -3,7 +3,6 @@ package tool import ( "fmt" "os" - "path" "path/filepath" "sync" "sync/atomic" @@ -104,9 +103,9 @@ func (m *Monitor) Complete() error { if err != nil { return errors.WithMessage(err, "failed get storage") } - var files []*File - if f := m.tool.GetFile(m.tsk.ID); f != nil { - files = append(files, f) + var files []File + if f := m.tool.GetFiles(m.tsk.ID); f != nil { + files = f } else { files, err = GetFiles(m.tempDir) if err != nil { @@ -138,7 +137,7 @@ func (m *Monitor) Complete() error { s := &stream.FileStream{ Ctx: nil, Obj: &model.Object{ - Name: path.Base(file.Path), + Name: filepath.Base(file.Path), Size: file.Size, Modified: file.Modified, IsFolder: false, diff --git a/internal/offline_download/tool/util.go b/internal/offline_download/tool/util.go index 4344b89f..4258eff6 100644 --- a/internal/offline_download/tool/util.go +++ b/internal/offline_download/tool/util.go @@ -5,14 +5,14 @@ import ( "path/filepath" ) -func GetFiles(dir string) ([]*File, error) { - var files []*File +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{ + files = append(files, File{ Name: info.Name(), Size: info.Size(), Path: path, diff --git a/server/handles/aria2.go b/server/handles/offline_download.go similarity index 53% rename from server/handles/aria2.go rename to server/handles/offline_download.go index a78eb659..72948eba 100644 --- a/server/handles/aria2.go +++ b/server/handles/offline_download.go @@ -4,7 +4,9 @@ import ( "github.com/alist-org/alist/v3/internal/aria2" "github.com/alist-org/alist/v3/internal/conf" "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/qbittorrent" "github.com/alist-org/alist/v3/server/common" "github.com/gin-gonic/gin" ) @@ -36,30 +38,51 @@ func SetAria2(c *gin.Context) { common.SuccessResp(c, version) } -type AddAria2Req struct { - Urls []string `json:"urls"` - Path string `json:"path"` +type SetQbittorrentReq struct { + Url string `json:"url" form:"url"` + Seedtime string `json:"seedtime" form:"seedtime"` } -func AddAria2(c *gin.Context) { +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.OFFLINE_DOWNLOAD, Flag: model.PRIVATE}, + {Key: conf.QbittorrentSeedtime, Value: req.Seedtime, Type: conf.TypeNumber, Group: model.OFFLINE_DOWNLOAD, 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") +} + +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.CanAddAria2Tasks() { + if !user.CanAddOfflineDownloadTasks() { common.ErrorStrResp(c, "permission denied", 403) return } - if !aria2.IsAria2Ready() { - // try to init client - _, err := aria2.InitClient(2) - if err != nil { - common.ErrorResp(c, err, 500) - return - } - if !aria2.IsAria2Ready() { - common.ErrorStrResp(c, "aria2 still not ready after init", 500) - return - } - } - var req AddAria2Req + + var req AddOfflineDownloadReq if err := c.ShouldBind(&req); err != nil { common.ErrorResp(c, err, 400) return @@ -70,7 +93,11 @@ func AddAria2(c *gin.Context) { return } 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 { common.ErrorResp(c, err, 500) return diff --git a/server/handles/qbittorrent.go b/server/handles/qbittorrent.go deleted file mode 100644 index b2280454..00000000 --- a/server/handles/qbittorrent.go +++ /dev/null @@ -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) -} diff --git a/server/handles/task.go b/server/handles/task.go index 8d311b46..15e80672 100644 --- a/server/handles/task.go +++ b/server/handles/task.go @@ -3,8 +3,8 @@ package handles import ( "strconv" - "github.com/alist-org/alist/v3/internal/aria2" "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/pkg/task" "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) { - 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("/copy"), fs.CopyTaskManager, uint64K2Str, str2Uint64K) taskRoute(g.Group("/qbit_down"), qbittorrent.DownTaskManager, strK2Str, str2StrK) 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) } diff --git a/server/router.go b/server/router.go index 92ede88b..7f179231 100644 --- a/server/router.go +++ b/server/router.go @@ -70,6 +70,7 @@ func Init(e *gin.Engine) { // no need auth public := api.Group("/public") public.Any("/settings", handles.PublicSettings) + public.Any("/offline_download_tools", handles.OfflineDownloadTools) _fs(auth.Group("/fs")) admin(auth.Group("/admin", middlewares.AuthAdmin)) @@ -155,8 +156,9 @@ func _fs(g *gin.RouterGroup) { g.PUT("/put", middlewares.FsUp, handles.FsStream) g.PUT("/form", middlewares.FsUp, handles.FsForm) g.POST("/link", middlewares.AuthAdmin, handles.Link) - g.POST("/add_aria2", handles.AddAria2) - g.POST("/add_qbit", handles.AddQbittorrent) + //g.POST("/add_aria2", handles.AddOfflineDownload) + //g.POST("/add_qbit", handles.AddQbittorrent) + g.POST("/add_offline_download", handles.AddOfflineDownload) } func Cors(r *gin.Engine) {