mirror of
https://github.com/AlistGo/alist.git
synced 2025-06-06 02:21:06 +08:00
* feat(qbittorrent): authorization and logging in support * feat(qbittorrent/client): support `AddFromLink` * refactor(qbittorrent/client): check authorization when getting a new client * feat(qbittorrent/client): support `GetInfo` * test(qbittorrent/client): update test cases * feat(qbittorrent): init qbittorrent client on bootstrap * feat(qbittorrent): support setting webui url via gin * feat(qbittorrent/client): support deleting * feat(qbittorrent/client): parse `TorrentStatus` enum when unmarshalling json in `GetInfo()` * feat(qbittorrent/client): support getting files by id * feat(qbittorrent): support adding qbittorrent tasks via gin * refactor(qbittorrent/client): return a `Client` interface in `New()` instead of `*client` * refactor: task handle * chore: fix typo * chore: change path --------- Co-authored-by: Andy Hsu <i@nn.ci>
This commit is contained in:
69
server/handles/qbittorrent.go
Normal file
69
server/handles/qbittorrent.go
Normal file
@ -0,0 +1,69 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
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.QBITTORRENT, 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() {
|
||||
common.ErrorStrResp(c, "qbittorrent not ready", 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)
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/aria2"
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -19,9 +20,19 @@ type TaskInfo struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func getTaskInfoUint(task *task.Task[uint64]) TaskInfo {
|
||||
type K2Str[K comparable] func(k K) string
|
||||
|
||||
func uint64K2Str(k uint64) string {
|
||||
return strconv.FormatUint(k, 10)
|
||||
}
|
||||
|
||||
func strK2Str(str string) string {
|
||||
return str
|
||||
}
|
||||
|
||||
func getTaskInfo[K comparable](task *task.Task[K], k2Str K2Str[K]) TaskInfo {
|
||||
return TaskInfo{
|
||||
ID: strconv.FormatUint(task.ID, 10),
|
||||
ID: k2Str(task.ID),
|
||||
Name: task.Name,
|
||||
State: task.GetState(),
|
||||
Status: task.GetStatus(),
|
||||
@ -30,183 +41,68 @@ func getTaskInfoUint(task *task.Task[uint64]) TaskInfo {
|
||||
}
|
||||
}
|
||||
|
||||
func getTaskInfoStr(task *task.Task[string]) TaskInfo {
|
||||
return TaskInfo{
|
||||
ID: task.ID,
|
||||
Name: task.Name,
|
||||
State: task.GetState(),
|
||||
Status: task.GetStatus(),
|
||||
Progress: task.GetProgress(),
|
||||
Error: task.GetErrMsg(),
|
||||
}
|
||||
}
|
||||
|
||||
func getTaskInfosUint(tasks []*task.Task[uint64]) []TaskInfo {
|
||||
func getTaskInfos[K comparable](tasks []*task.Task[K], k2Str K2Str[K]) []TaskInfo {
|
||||
var infos []TaskInfo
|
||||
for _, t := range tasks {
|
||||
infos = append(infos, getTaskInfoUint(t))
|
||||
infos = append(infos, getTaskInfo(t, k2Str))
|
||||
}
|
||||
return infos
|
||||
}
|
||||
|
||||
func getTaskInfosStr(tasks []*task.Task[string]) []TaskInfo {
|
||||
var infos []TaskInfo
|
||||
for _, t := range tasks {
|
||||
infos = append(infos, getTaskInfoStr(t))
|
||||
}
|
||||
return infos
|
||||
type Str2K[K comparable] func(str string) (K, error)
|
||||
|
||||
func str2Uint64K(str string) (uint64, error) {
|
||||
return strconv.ParseUint(str, 10, 64)
|
||||
}
|
||||
|
||||
func UndoneDownTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosStr(aria2.DownTaskManager.ListUndone()))
|
||||
func str2StrK(str string) (string, error) {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func DoneDownTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosStr(aria2.DownTaskManager.ListDone()))
|
||||
}
|
||||
|
||||
func CancelDownTask(c *gin.Context) {
|
||||
tid := c.Query("tid")
|
||||
if err := aria2.DownTaskManager.Cancel(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
func taskRoute[K comparable](g *gin.RouterGroup, manager *task.Manager[K], k2Str K2Str[K], str2K Str2K[K]) {
|
||||
g.GET("/undone", func(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfos(manager.ListUndone(), k2Str))
|
||||
})
|
||||
g.GET("/done", func(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfos(manager.ListDone(), k2Str))
|
||||
})
|
||||
g.POST("/cancel", func(c *gin.Context) {
|
||||
tid := c.Query("tid")
|
||||
id, err := str2K(tid)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := manager.Cancel(id); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
})
|
||||
g.POST("/delete", func(c *gin.Context) {
|
||||
tid := c.Query("tid")
|
||||
id, err := str2K(tid)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := manager.Remove(id); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
})
|
||||
g.POST("/clear_done", func(c *gin.Context) {
|
||||
manager.ClearDone()
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDownTask(c *gin.Context) {
|
||||
tid := c.Query("tid")
|
||||
if err := aria2.DownTaskManager.Remove(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func ClearDoneDownTasks(c *gin.Context) {
|
||||
aria2.DownTaskManager.ClearDone()
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func UndoneTransferTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosUint(aria2.TransferTaskManager.ListUndone()))
|
||||
}
|
||||
|
||||
func DoneTransferTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosUint(aria2.TransferTaskManager.ListDone()))
|
||||
}
|
||||
|
||||
func CancelTransferTask(c *gin.Context) {
|
||||
id := c.Query("tid")
|
||||
tid, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := aria2.TransferTaskManager.Cancel(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteTransferTask(c *gin.Context) {
|
||||
id := c.Query("tid")
|
||||
tid, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := aria2.TransferTaskManager.Remove(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func ClearDoneTransferTasks(c *gin.Context) {
|
||||
aria2.TransferTaskManager.ClearDone()
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func UndoneUploadTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosUint(fs.UploadTaskManager.ListUndone()))
|
||||
}
|
||||
|
||||
func DoneUploadTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosUint(fs.UploadTaskManager.ListDone()))
|
||||
}
|
||||
|
||||
func CancelUploadTask(c *gin.Context) {
|
||||
id := c.Query("tid")
|
||||
tid, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := fs.UploadTaskManager.Cancel(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteUploadTask(c *gin.Context) {
|
||||
id := c.Query("tid")
|
||||
tid, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := fs.UploadTaskManager.Remove(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func ClearDoneUploadTasks(c *gin.Context) {
|
||||
fs.UploadTaskManager.ClearDone()
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func UndoneCopyTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosUint(fs.CopyTaskManager.ListUndone()))
|
||||
}
|
||||
|
||||
func DoneCopyTask(c *gin.Context) {
|
||||
common.SuccessResp(c, getTaskInfosUint(fs.CopyTaskManager.ListDone()))
|
||||
}
|
||||
|
||||
func CancelCopyTask(c *gin.Context) {
|
||||
id := c.Query("tid")
|
||||
tid, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := fs.CopyTaskManager.Cancel(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteCopyTask(c *gin.Context) {
|
||||
id := c.Query("tid")
|
||||
tid, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := fs.CopyTaskManager.Remove(tid); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func ClearDoneCopyTasks(c *gin.Context) {
|
||||
fs.CopyTaskManager.ClearDone()
|
||||
common.SuccessResp(c)
|
||||
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)
|
||||
}
|
||||
|
@ -89,28 +89,10 @@ func admin(g *gin.RouterGroup) {
|
||||
setting.POST("/delete", handles.DeleteSetting)
|
||||
setting.POST("/reset_token", handles.ResetToken)
|
||||
setting.POST("/set_aria2", handles.SetAria2)
|
||||
setting.POST("/set_qbittorrent", handles.SetQbittorrent)
|
||||
|
||||
task := g.Group("/task")
|
||||
task.GET("/down/undone", handles.UndoneDownTask)
|
||||
task.GET("/down/done", handles.DoneDownTask)
|
||||
task.POST("/down/cancel", handles.CancelDownTask)
|
||||
task.POST("/down/delete", handles.DeleteDownTask)
|
||||
task.POST("/down/clear_done", handles.ClearDoneDownTasks)
|
||||
task.GET("/transfer/undone", handles.UndoneTransferTask)
|
||||
task.GET("/transfer/done", handles.DoneTransferTask)
|
||||
task.POST("/transfer/cancel", handles.CancelTransferTask)
|
||||
task.POST("/transfer/delete", handles.DeleteTransferTask)
|
||||
task.POST("/transfer/clear_done", handles.ClearDoneTransferTasks)
|
||||
task.GET("/upload/undone", handles.UndoneUploadTask)
|
||||
task.GET("/upload/done", handles.DoneUploadTask)
|
||||
task.POST("/upload/cancel", handles.CancelUploadTask)
|
||||
task.POST("/upload/delete", handles.DeleteUploadTask)
|
||||
task.POST("/upload/clear_done", handles.ClearDoneUploadTasks)
|
||||
task.GET("/copy/undone", handles.UndoneCopyTask)
|
||||
task.GET("/copy/done", handles.DoneCopyTask)
|
||||
task.POST("/copy/cancel", handles.CancelCopyTask)
|
||||
task.POST("/copy/delete", handles.DeleteCopyTask)
|
||||
task.POST("/copy/clear_done", handles.ClearDoneCopyTasks)
|
||||
handles.SetupTaskRoute(task)
|
||||
|
||||
ms := g.Group("/message")
|
||||
ms.POST("/get", message.HttpInstance.GetHandle)
|
||||
@ -139,6 +121,7 @@ func _fs(g *gin.RouterGroup) {
|
||||
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)
|
||||
}
|
||||
|
||||
func Cors(r *gin.Engine) {
|
||||
|
Reference in New Issue
Block a user