mirror of https://github.com/Xhofe/alist
* feat: support general users view and cancel own tasks Add a creator attribute to the upload, copy and offline download tasks, so that a GENERAL task creator can view and cancel them. BREAKING CHANGE: 1. A new internal package `task` including the struct `TaskWithCreator` which embeds `tache.Base` is created, and the past dependence on `tache.Task` will all be transferred to dependence on this package. 2. The API `/admin/task` can now also be accessed via `/task`, and the old endpoint is retained to ensure compatibility with legacy automation scripts. Closes #7398 * fix(deps): update github.com/xhofe/tache to v0.1.3pull/7453/head
parent
10c7ebb1c0
commit
64ceb5afb6
2
go.mod
2
go.mod
|
@ -56,7 +56,7 @@ require (
|
||||||
github.com/u2takey/ffmpeg-go v0.5.0
|
github.com/u2takey/ffmpeg-go v0.5.0
|
||||||
github.com/upyun/go-sdk/v3 v3.0.4
|
github.com/upyun/go-sdk/v3 v3.0.4
|
||||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
|
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
|
||||||
github.com/xhofe/tache v0.1.2
|
github.com/xhofe/tache v0.1.3
|
||||||
github.com/xhofe/wopan-sdk-go v0.1.3
|
github.com/xhofe/wopan-sdk-go v0.1.3
|
||||||
github.com/zzzhr1990/go-common-entity v0.0.0-20221216044934-fd1c571e3a22
|
github.com/zzzhr1990/go-common-entity v0.0.0-20221216044934-fd1c571e3a22
|
||||||
golang.org/x/crypto v0.27.0
|
golang.org/x/crypto v0.27.0
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -514,6 +514,8 @@ github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 h1:eDfebW/yfq9DtG9RO3K
|
||||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25/go.mod h1:fH4oNm5F9NfI5dLi0oIMtsLNKQOirUDbEMCIBb/7SU0=
|
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25/go.mod h1:fH4oNm5F9NfI5dLi0oIMtsLNKQOirUDbEMCIBb/7SU0=
|
||||||
github.com/xhofe/tache v0.1.2 h1:pHrXlrWcbTb4G7hVUDW7Rc+YTUnLJvnLBrdktVE1Fqg=
|
github.com/xhofe/tache v0.1.2 h1:pHrXlrWcbTb4G7hVUDW7Rc+YTUnLJvnLBrdktVE1Fqg=
|
||||||
github.com/xhofe/tache v0.1.2/go.mod h1:iKumPFvywf30FRpAHHCt64G0JHLMzT0K+wyGedHsmTQ=
|
github.com/xhofe/tache v0.1.2/go.mod h1:iKumPFvywf30FRpAHHCt64G0JHLMzT0K+wyGedHsmTQ=
|
||||||
|
github.com/xhofe/tache v0.1.3 h1:MipxzlljYX29E1YI/SLC7hVomVF+51iP1OUzlsuq1wE=
|
||||||
|
github.com/xhofe/tache v0.1.3/go.mod h1:iKumPFvywf30FRpAHHCt64G0JHLMzT0K+wyGedHsmTQ=
|
||||||
github.com/xhofe/wopan-sdk-go v0.1.3 h1:J58X6v+n25ewBZjb05pKOr7AWGohb+Rdll4CThGh6+A=
|
github.com/xhofe/wopan-sdk-go v0.1.3 h1:J58X6v+n25ewBZjb05pKOr7AWGohb+Rdll4CThGh6+A=
|
||||||
github.com/xhofe/wopan-sdk-go v0.1.3/go.mod h1:dcY9yA28fnaoZPnXZiVTFSkcd7GnIPTpTIIlfSI5z5Q=
|
github.com/xhofe/wopan-sdk-go v0.1.3/go.mod h1:dcY9yA28fnaoZPnXZiVTFSkcd7GnIPTpTIIlfSI5z5Q=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
|
@ -11,13 +11,14 @@ import (
|
||||||
"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"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/xhofe/tache"
|
"github.com/xhofe/tache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CopyTask struct {
|
type CopyTask struct {
|
||||||
tache.Base
|
task.TaskWithCreator
|
||||||
Status string `json:"-"` //don't save status to save space
|
Status string `json:"-"` //don't save status to save space
|
||||||
SrcObjPath string `json:"src_path"`
|
SrcObjPath string `json:"src_path"`
|
||||||
DstDirPath string `json:"dst_path"`
|
DstDirPath string `json:"dst_path"`
|
||||||
|
@ -53,7 +54,7 @@ var CopyTaskManager *tache.Manager[*CopyTask]
|
||||||
|
|
||||||
// Copy if in the same storage, call move method
|
// Copy if in the same storage, call move method
|
||||||
// if not, add copy task
|
// if not, add copy task
|
||||||
func _copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (tache.TaskWithInfo, error) {
|
func _copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (task.TaskInfoWithCreator, error) {
|
||||||
srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(srcObjPath)
|
srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(srcObjPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed get src storage")
|
return nil, errors.WithMessage(err, "failed get src storage")
|
||||||
|
@ -92,7 +93,11 @@ func _copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not in the same storage
|
// not in the same storage
|
||||||
|
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||||
t := &CopyTask{
|
t := &CopyTask{
|
||||||
|
TaskWithCreator: task.TaskWithCreator{
|
||||||
|
Creator: taskCreator,
|
||||||
|
},
|
||||||
srcStorage: srcStorage,
|
srcStorage: srcStorage,
|
||||||
dstStorage: dstStorage,
|
dstStorage: dstStorage,
|
||||||
SrcObjPath: srcObjActualPath,
|
SrcObjPath: srcObjActualPath,
|
||||||
|
@ -123,6 +128,9 @@ func copyBetween2Storages(t *CopyTask, srcStorage, dstStorage driver.Driver, src
|
||||||
srcObjPath := stdpath.Join(srcObjPath, obj.GetName())
|
srcObjPath := stdpath.Join(srcObjPath, obj.GetName())
|
||||||
dstObjPath := stdpath.Join(dstDirPath, srcObj.GetName())
|
dstObjPath := stdpath.Join(dstDirPath, srcObj.GetName())
|
||||||
CopyTaskManager.Add(&CopyTask{
|
CopyTaskManager.Add(&CopyTask{
|
||||||
|
TaskWithCreator: task.TaskWithCreator{
|
||||||
|
Creator: t.Creator,
|
||||||
|
},
|
||||||
srcStorage: srcStorage,
|
srcStorage: srcStorage,
|
||||||
dstStorage: dstStorage,
|
dstStorage: dstStorage,
|
||||||
SrcObjPath: srcObjPath,
|
SrcObjPath: srcObjPath,
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"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/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/xhofe/tache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// the param named path of functions in this package is a mount path
|
// the param named path of functions in this package is a mount path
|
||||||
|
@ -69,7 +69,7 @@ func Move(ctx context.Context, srcPath, dstDirPath string, lazyCache ...bool) er
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (tache.TaskWithInfo, error) {
|
func Copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (task.TaskInfoWithCreator, error) {
|
||||||
res, err := _copy(ctx, srcObjPath, dstDirPath, lazyCache...)
|
res, err := _copy(ctx, srcObjPath, dstDirPath, lazyCache...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed copy %s to %s: %+v", srcObjPath, dstDirPath, err)
|
log.Errorf("failed copy %s to %s: %+v", srcObjPath, dstDirPath, err)
|
||||||
|
@ -101,8 +101,8 @@ func PutDirectly(ctx context.Context, dstDirPath string, file model.FileStreamer
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func PutAsTask(dstDirPath string, file model.FileStreamer) (tache.TaskWithInfo, error) {
|
func PutAsTask(ctx context.Context, dstDirPath string, file model.FileStreamer) (task.TaskInfoWithCreator, error) {
|
||||||
t, err := putAsTask(dstDirPath, file)
|
t, err := putAsTask(ctx, dstDirPath, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed put %s: %+v", dstDirPath, err)
|
log.Errorf("failed put %s: %+v", dstDirPath, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,13 @@ import (
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"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/task"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/xhofe/tache"
|
"github.com/xhofe/tache"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UploadTask struct {
|
type UploadTask struct {
|
||||||
tache.Base
|
task.TaskWithCreator
|
||||||
storage driver.Driver
|
storage driver.Driver
|
||||||
dstDirActualPath string
|
dstDirActualPath string
|
||||||
file model.FileStreamer
|
file model.FileStreamer
|
||||||
|
@ -33,7 +34,7 @@ func (t *UploadTask) Run() error {
|
||||||
var UploadTaskManager *tache.Manager[*UploadTask]
|
var UploadTaskManager *tache.Manager[*UploadTask]
|
||||||
|
|
||||||
// putAsTask add as a put task and return immediately
|
// putAsTask add as a put task and return immediately
|
||||||
func putAsTask(dstDirPath string, file model.FileStreamer) (tache.TaskWithInfo, error) {
|
func putAsTask(ctx context.Context, dstDirPath string, file model.FileStreamer) (task.TaskInfoWithCreator, error) {
|
||||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithMessage(err, "failed get storage")
|
return nil, errors.WithMessage(err, "failed get storage")
|
||||||
|
@ -49,7 +50,11 @@ func putAsTask(dstDirPath string, file model.FileStreamer) (tache.TaskWithInfo,
|
||||||
//file.SetReader(tempFile)
|
//file.SetReader(tempFile)
|
||||||
//file.SetTmpFile(tempFile)
|
//file.SetTmpFile(tempFile)
|
||||||
}
|
}
|
||||||
|
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||||
t := &UploadTask{
|
t := &UploadTask{
|
||||||
|
TaskWithCreator: task.TaskWithCreator{
|
||||||
|
Creator: taskCreator,
|
||||||
|
},
|
||||||
storage: storage,
|
storage: storage,
|
||||||
dstDirActualPath: dstDirActualPath,
|
dstDirActualPath: dstDirActualPath,
|
||||||
file: file,
|
file: file,
|
||||||
|
|
|
@ -2,6 +2,8 @@ package tool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
@ -9,7 +11,6 @@ import (
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/xhofe/tache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeletePolicy string
|
type DeletePolicy string
|
||||||
|
@ -28,7 +29,7 @@ type AddURLArgs struct {
|
||||||
DeletePolicy DeletePolicy
|
DeletePolicy DeletePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddURL(ctx context.Context, args *AddURLArgs) (tache.TaskWithInfo, error) {
|
func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskInfoWithCreator, error) {
|
||||||
// get tool
|
// get tool
|
||||||
tool, err := Tools.Get(args.Tool)
|
tool, err := Tools.Get(args.Tool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -77,8 +78,12 @@ func AddURL(ctx context.Context, args *AddURLArgs) (tache.TaskWithInfo, error) {
|
||||||
// 防止将下载好的文件删除
|
// 防止将下载好的文件删除
|
||||||
deletePolicy = DeleteNever
|
deletePolicy = DeleteNever
|
||||||
}
|
}
|
||||||
|
|
||||||
|
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||||
t := &DownloadTask{
|
t := &DownloadTask{
|
||||||
|
TaskWithCreator: task.TaskWithCreator{
|
||||||
|
Creator: taskCreator,
|
||||||
|
},
|
||||||
Url: args.URL,
|
Url: args.URL,
|
||||||
DstDirPath: args.DstDirPath,
|
DstDirPath: args.DstDirPath,
|
||||||
TempDir: tempDir,
|
TempDir: tempDir,
|
||||||
|
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DownloadTask struct {
|
type DownloadTask struct {
|
||||||
tache.Base
|
task.TaskWithCreator
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
DstDirPath string `json:"dst_dir_path"`
|
DstDirPath string `json:"dst_dir_path"`
|
||||||
TempDir string `json:"temp_dir"`
|
TempDir string `json:"temp_dir"`
|
||||||
|
@ -171,6 +172,9 @@ func (t *DownloadTask) Complete() error {
|
||||||
for i := range files {
|
for i := range files {
|
||||||
file := files[i]
|
file := files[i]
|
||||||
TransferTaskManager.Add(&TransferTask{
|
TransferTaskManager.Add(&TransferTask{
|
||||||
|
TaskWithCreator: task.TaskWithCreator{
|
||||||
|
Creator: t.Creator,
|
||||||
|
},
|
||||||
file: file,
|
file: file,
|
||||||
DstDirPath: t.DstDirPath,
|
DstDirPath: t.DstDirPath,
|
||||||
TempDir: t.TempDir,
|
TempDir: t.TempDir,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"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"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransferTask struct {
|
type TransferTask struct {
|
||||||
tache.Base
|
task.TaskWithCreator
|
||||||
FileDir string `json:"file_dir"`
|
FileDir string `json:"file_dir"`
|
||||||
DstDirPath string `json:"dst_dir_path"`
|
DstDirPath string `json:"dst_dir_path"`
|
||||||
TempDir string `json:"temp_dir"`
|
TempDir string `json:"temp_dir"`
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/xhofe/tache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskWithCreator struct {
|
||||||
|
tache.Base
|
||||||
|
Creator *model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TaskWithCreator) SetCreator(creator *model.User) {
|
||||||
|
t.Creator = creator
|
||||||
|
t.Persist()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TaskWithCreator) GetCreator() *model.User {
|
||||||
|
return t.Creator
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskInfoWithCreator interface {
|
||||||
|
tache.TaskWithInfo
|
||||||
|
SetCreator(creator *model.User)
|
||||||
|
GetCreator() *model.User
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package handles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/xhofe/tache"
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"io"
|
"io"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ func FsCopy(c *gin.Context) {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var addedTasks []tache.TaskWithInfo
|
var addedTasks []task.TaskInfoWithCreator
|
||||||
for i, name := range req.Names {
|
for i, name := range req.Names {
|
||||||
t, err := fs.Copy(c, stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1)
|
t, err := fs.Copy(c, stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1)
|
||||||
if t != nil {
|
if t != nil {
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package handles
|
package handles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xhofe/tache"
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/stream"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/stream"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
@ -58,9 +57,9 @@ func FsStream(c *gin.Context) {
|
||||||
Mimetype: c.GetHeader("Content-Type"),
|
Mimetype: c.GetHeader("Content-Type"),
|
||||||
WebPutAsTask: asTask,
|
WebPutAsTask: asTask,
|
||||||
}
|
}
|
||||||
var t tache.TaskWithInfo
|
var t task.TaskInfoWithCreator
|
||||||
if asTask {
|
if asTask {
|
||||||
t, err = fs.PutAsTask(dir, s)
|
t, err = fs.PutAsTask(c, dir, s)
|
||||||
} else {
|
} else {
|
||||||
err = fs.PutDirectly(c, dir, s, true)
|
err = fs.PutDirectly(c, dir, s, true)
|
||||||
}
|
}
|
||||||
|
@ -123,12 +122,12 @@ func FsForm(c *gin.Context) {
|
||||||
Mimetype: file.Header.Get("Content-Type"),
|
Mimetype: file.Header.Get("Content-Type"),
|
||||||
WebPutAsTask: asTask,
|
WebPutAsTask: asTask,
|
||||||
}
|
}
|
||||||
var t tache.TaskWithInfo
|
var t task.TaskInfoWithCreator
|
||||||
if asTask {
|
if asTask {
|
||||||
s.Reader = struct {
|
s.Reader = struct {
|
||||||
io.Reader
|
io.Reader
|
||||||
}{f}
|
}{f}
|
||||||
t, err = fs.PutAsTask(dir, &s)
|
t, err = fs.PutAsTask(c, dir, &s)
|
||||||
} else {
|
} else {
|
||||||
ss, err := stream.NewSeekableStream(s, nil)
|
ss, err := stream.NewSeekableStream(s, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"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"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"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"
|
||||||
"github.com/xhofe/tache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SetAria2Req struct {
|
type SetAria2Req struct {
|
||||||
|
@ -133,7 +133,7 @@ func AddOfflineDownload(c *gin.Context) {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var tasks []tache.TaskWithInfo
|
var tasks []task.TaskInfoWithCreator
|
||||||
for _, url := range req.Urls {
|
for _, url := range req.Urls {
|
||||||
t, err := tool.AddURL(c, &tool.AddURLArgs{
|
t, err := tool.AddURL(c, &tool.AddURLArgs{
|
||||||
URL: url,
|
URL: url,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package handles
|
package handles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/task"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
|
@ -12,15 +14,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TaskInfo struct {
|
type TaskInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
State tache.State `json:"state"`
|
Creator string `json:"creator"`
|
||||||
Status string `json:"status"`
|
CreatorRole int `json:"creator_role"`
|
||||||
Progress float64 `json:"progress"`
|
State tache.State `json:"state"`
|
||||||
Error string `json:"error"`
|
Status string `json:"status"`
|
||||||
|
Progress float64 `json:"progress"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfo[T tache.TaskWithInfo](task T) TaskInfo {
|
func getTaskInfo[T task.TaskInfoWithCreator](task T) TaskInfo {
|
||||||
errMsg := ""
|
errMsg := ""
|
||||||
if task.GetErr() != nil {
|
if task.GetErr() != nil {
|
||||||
errMsg = task.GetErr().Error()
|
errMsg = task.GetErr().Error()
|
||||||
|
@ -30,62 +34,142 @@ func getTaskInfo[T tache.TaskWithInfo](task T) TaskInfo {
|
||||||
if math.IsNaN(progress) {
|
if math.IsNaN(progress) {
|
||||||
progress = 100
|
progress = 100
|
||||||
}
|
}
|
||||||
|
creatorName := ""
|
||||||
|
creatorRole := -1
|
||||||
|
if task.GetCreator() != nil {
|
||||||
|
creatorName = task.GetCreator().Username
|
||||||
|
creatorRole = task.GetCreator().Role
|
||||||
|
}
|
||||||
return TaskInfo{
|
return TaskInfo{
|
||||||
ID: task.GetID(),
|
ID: task.GetID(),
|
||||||
Name: task.GetName(),
|
Name: task.GetName(),
|
||||||
State: task.GetState(),
|
Creator: creatorName,
|
||||||
Status: task.GetStatus(),
|
CreatorRole: creatorRole,
|
||||||
Progress: progress,
|
State: task.GetState(),
|
||||||
Error: errMsg,
|
Status: task.GetStatus(),
|
||||||
|
Progress: progress,
|
||||||
|
Error: errMsg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfos[T tache.TaskWithInfo](tasks []T) []TaskInfo {
|
func getTaskInfos[T task.TaskInfoWithCreator](tasks []T) []TaskInfo {
|
||||||
return utils.MustSliceConvert(tasks, getTaskInfo[T])
|
return utils.MustSliceConvert(tasks, getTaskInfo[T])
|
||||||
}
|
}
|
||||||
|
|
||||||
func taskRoute[T tache.TaskWithInfo](g *gin.RouterGroup, manager *tache.Manager[T]) {
|
func argsContains[T comparable](v T, slice ...T) bool {
|
||||||
g.GET("/undone", func(c *gin.Context) {
|
return utils.SliceContains(slice, v)
|
||||||
common.SuccessResp(c, getTaskInfos(manager.GetByState(tache.StatePending, tache.StateRunning,
|
}
|
||||||
tache.StateCanceling, tache.StateErrored, tache.StateFailing, tache.StateWaitingRetry, tache.StateBeforeRetry)))
|
|
||||||
})
|
func getUserInfo(c *gin.Context) (bool, uint, bool) {
|
||||||
g.GET("/done", func(c *gin.Context) {
|
if user, ok := c.Value("user").(*model.User); ok {
|
||||||
common.SuccessResp(c, getTaskInfos(manager.GetByState(tache.StateCanceled, tache.StateFailed, tache.StateSucceeded)))
|
return user.IsAdmin(), user.ID, true
|
||||||
})
|
} else {
|
||||||
g.POST("/info", func(c *gin.Context) {
|
return false, 0, false
|
||||||
tid := c.Query("tid")
|
}
|
||||||
task, ok := manager.GetByID(tid)
|
}
|
||||||
|
|
||||||
|
func getTargetedHandler[T task.TaskInfoWithCreator](manager *tache.Manager[T], callback func(c *gin.Context, task T)) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
isAdmin, uid, ok := getUserInfo(c)
|
||||||
|
if !ok {
|
||||||
|
// if there is no bug, here is unreachable
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, ok := manager.GetByID(c.Query("tid"))
|
||||||
if !ok {
|
if !ok {
|
||||||
common.ErrorStrResp(c, "task not found", 404)
|
common.ErrorStrResp(c, "task not found", 404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !isAdmin && uid != t.GetCreator().ID {
|
||||||
|
// to avoid an attacker using error messages to guess valid TID, return a 404 rather than a 403
|
||||||
|
common.ErrorStrResp(c, "task not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback(c, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func taskRoute[T task.TaskInfoWithCreator](g *gin.RouterGroup, manager *tache.Manager[T]) {
|
||||||
|
g.GET("/undone", func(c *gin.Context) {
|
||||||
|
isAdmin, uid, ok := getUserInfo(c)
|
||||||
|
if !ok {
|
||||||
|
// if there is no bug, here is unreachable
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, getTaskInfos(manager.GetByCondition(func(task T) bool {
|
||||||
|
// avoid directly passing the user object into the function to reduce closure size
|
||||||
|
return (isAdmin || uid == task.GetCreator().ID) &&
|
||||||
|
argsContains(task.GetState(), tache.StatePending, tache.StateRunning, tache.StateCanceling,
|
||||||
|
tache.StateErrored, tache.StateFailing, tache.StateWaitingRetry, tache.StateBeforeRetry)
|
||||||
|
})))
|
||||||
|
})
|
||||||
|
g.GET("/done", func(c *gin.Context) {
|
||||||
|
isAdmin, uid, ok := getUserInfo(c)
|
||||||
|
if !ok {
|
||||||
|
// if there is no bug, here is unreachable
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, getTaskInfos(manager.GetByCondition(func(task T) bool {
|
||||||
|
return (isAdmin || uid == task.GetCreator().ID) &&
|
||||||
|
argsContains(task.GetState(), tache.StateCanceled, tache.StateFailed, tache.StateSucceeded)
|
||||||
|
})))
|
||||||
|
})
|
||||||
|
g.POST("/info", getTargetedHandler(manager, func(c *gin.Context, task T) {
|
||||||
common.SuccessResp(c, getTaskInfo(task))
|
common.SuccessResp(c, getTaskInfo(task))
|
||||||
})
|
}))
|
||||||
g.POST("/cancel", func(c *gin.Context) {
|
g.POST("/cancel", getTargetedHandler(manager, func(c *gin.Context, task T) {
|
||||||
tid := c.Query("tid")
|
manager.Cancel(task.GetID())
|
||||||
manager.Cancel(tid)
|
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
}))
|
||||||
g.POST("/delete", func(c *gin.Context) {
|
g.POST("/delete", getTargetedHandler(manager, func(c *gin.Context, task T) {
|
||||||
tid := c.Query("tid")
|
manager.Remove(task.GetID())
|
||||||
manager.Remove(tid)
|
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
}))
|
||||||
g.POST("/retry", func(c *gin.Context) {
|
g.POST("/retry", getTargetedHandler(manager, func(c *gin.Context, task T) {
|
||||||
tid := c.Query("tid")
|
manager.Retry(task.GetID())
|
||||||
manager.Retry(tid)
|
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
}))
|
||||||
g.POST("/clear_done", func(c *gin.Context) {
|
g.POST("/clear_done", func(c *gin.Context) {
|
||||||
manager.RemoveByState(tache.StateCanceled, tache.StateFailed, tache.StateSucceeded)
|
isAdmin, uid, ok := getUserInfo(c)
|
||||||
|
if !ok {
|
||||||
|
// if there is no bug, here is unreachable
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.RemoveByCondition(func(task T) bool {
|
||||||
|
return (isAdmin || uid == task.GetCreator().ID) &&
|
||||||
|
argsContains(task.GetState(), tache.StateCanceled, tache.StateFailed, tache.StateSucceeded)
|
||||||
|
})
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
})
|
||||||
g.POST("/clear_succeeded", func(c *gin.Context) {
|
g.POST("/clear_succeeded", func(c *gin.Context) {
|
||||||
manager.RemoveByState(tache.StateSucceeded)
|
isAdmin, uid, ok := getUserInfo(c)
|
||||||
|
if !ok {
|
||||||
|
// if there is no bug, here is unreachable
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.RemoveByCondition(func(task T) bool {
|
||||||
|
return (isAdmin || uid == task.GetCreator().ID) && task.GetState() == tache.StateSucceeded
|
||||||
|
})
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
})
|
||||||
g.POST("/retry_failed", func(c *gin.Context) {
|
g.POST("/retry_failed", func(c *gin.Context) {
|
||||||
manager.RetryAllFailed()
|
isAdmin, uid, ok := getUserInfo(c)
|
||||||
|
if !ok {
|
||||||
|
// if there is no bug, here is unreachable
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tasks := manager.GetByCondition(func(task T) bool {
|
||||||
|
return (isAdmin || uid == task.GetCreator().ID) && task.GetState() == tache.StateFailed
|
||||||
|
})
|
||||||
|
for _, t := range tasks {
|
||||||
|
manager.Retry(t.GetID())
|
||||||
|
}
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,16 @@ func Authn(c *gin.Context) {
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AuthNotGuest(c *gin.Context) {
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
if user.IsGuest() {
|
||||||
|
common.ErrorStrResp(c, "You are a guest", 403)
|
||||||
|
c.Abort()
|
||||||
|
} else {
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AuthAdmin(c *gin.Context) {
|
func AuthAdmin(c *gin.Context) {
|
||||||
user := c.MustGet("user").(*model.User)
|
user := c.MustGet("user").(*model.User)
|
||||||
if !user.IsAdmin() {
|
if !user.IsAdmin() {
|
||||||
|
|
|
@ -76,6 +76,7 @@ func Init(e *gin.Engine) {
|
||||||
public.Any("/offline_download_tools", handles.OfflineDownloadTools)
|
public.Any("/offline_download_tools", handles.OfflineDownloadTools)
|
||||||
|
|
||||||
_fs(auth.Group("/fs"))
|
_fs(auth.Group("/fs"))
|
||||||
|
_task(auth.Group("/task", middlewares.AuthNotGuest))
|
||||||
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
||||||
if flags.Debug || flags.Dev {
|
if flags.Debug || flags.Dev {
|
||||||
debug(g.Group("/debug"))
|
debug(g.Group("/debug"))
|
||||||
|
@ -127,8 +128,8 @@ func admin(g *gin.RouterGroup) {
|
||||||
setting.POST("/set_qbit", handles.SetQbittorrent)
|
setting.POST("/set_qbit", handles.SetQbittorrent)
|
||||||
setting.POST("/set_transmission", handles.SetTransmission)
|
setting.POST("/set_transmission", handles.SetTransmission)
|
||||||
|
|
||||||
task := g.Group("/task")
|
// retain /admin/task API to ensure compatibility with legacy automation scripts
|
||||||
handles.SetupTaskRoute(task)
|
_task(g.Group("/task"))
|
||||||
|
|
||||||
ms := g.Group("/message")
|
ms := g.Group("/message")
|
||||||
ms.POST("/get", message.HttpInstance.GetHandle)
|
ms.POST("/get", message.HttpInstance.GetHandle)
|
||||||
|
@ -166,6 +167,10 @@ func _fs(g *gin.RouterGroup) {
|
||||||
g.POST("/add_offline_download", handles.AddOfflineDownload)
|
g.POST("/add_offline_download", handles.AddOfflineDownload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _task(g *gin.RouterGroup) {
|
||||||
|
handles.SetupTaskRoute(g)
|
||||||
|
}
|
||||||
|
|
||||||
func Cors(r *gin.Engine) {
|
func Cors(r *gin.Engine) {
|
||||||
config := cors.DefaultConfig()
|
config := cors.DefaultConfig()
|
||||||
// config.AllowAllOrigins = true
|
// config.AllowAllOrigins = true
|
||||||
|
|
Loading…
Reference in New Issue