From 258b8f520f467b7f7be7cc18d70f1e86de95f182 Mon Sep 17 00:00:00 2001 From: Jealous Date: Mon, 27 Jan 2025 20:25:39 +0800 Subject: [PATCH] feat(recursive-move): add `overwrite` option to preventing unintentional overwriting (#7868 closes #7382,#7719) * feat(recursive-move): add `overwrite` option to preventing unintentional overwriting * chore: rearrange code order --- server/handles/fsbatch.go | 204 ++++++++++++++++++++++---------------- 1 file changed, 116 insertions(+), 88 deletions(-) diff --git a/server/handles/fsbatch.go b/server/handles/fsbatch.go index fa7971df..dd7b7e47 100644 --- a/server/handles/fsbatch.go +++ b/server/handles/fsbatch.go @@ -3,6 +3,7 @@ package handles import ( "fmt" "regexp" + "slices" "github.com/alist-org/alist/v3/internal/errs" "github.com/alist-org/alist/v3/internal/fs" @@ -14,6 +15,121 @@ import ( "github.com/pkg/errors" ) +type RecursiveMoveReq struct { + SrcDir string `json:"src_dir"` + DstDir string `json:"dst_dir"` + Overwrite bool `json:"overwrite"` +} + +func FsRecursiveMove(c *gin.Context) { + var req RecursiveMoveReq + if err := c.ShouldBind(&req); err != nil { + common.ErrorResp(c, err, 400) + return + } + + user := c.MustGet("user").(*model.User) + if !user.CanMove() { + common.ErrorResp(c, errs.PermissionDenied, 403) + return + } + srcDir, err := user.JoinPath(req.SrcDir) + if err != nil { + common.ErrorResp(c, err, 403) + return + } + dstDir, err := user.JoinPath(req.DstDir) + if err != nil { + common.ErrorResp(c, err, 403) + return + } + + meta, err := op.GetNearestMeta(srcDir) + if err != nil { + if !errors.Is(errors.Cause(err), errs.MetaNotFound) { + common.ErrorResp(c, err, 500, true) + return + } + } + c.Set("meta", meta) + + rootFiles, err := fs.List(c, srcDir, &fs.ListArgs{}) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + + var existingFileNames []string + if !req.Overwrite { + dstFiles, err := fs.List(c, dstDir, &fs.ListArgs{}) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + existingFileNames = make([]string, 0, len(dstFiles)) + for _, dstFile := range dstFiles { + existingFileNames = append(existingFileNames, dstFile.GetName()) + } + } + + // record the file path + filePathMap := make(map[model.Obj]string) + movingFiles := generic.NewQueue[model.Obj]() + movingFileNames := make([]string, 0, len(rootFiles)) + for _, file := range rootFiles { + movingFiles.Push(file) + filePathMap[file] = srcDir + } + + for !movingFiles.IsEmpty() { + + movingFile := movingFiles.Pop() + movingFilePath := filePathMap[movingFile] + movingFileName := fmt.Sprintf("%s/%s", movingFilePath, movingFile.GetName()) + if movingFile.IsDir() { + // directory, recursive move + subFilePath := movingFileName + subFiles, err := fs.List(c, movingFileName, &fs.ListArgs{Refresh: true}) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + for _, subFile := range subFiles { + movingFiles.Push(subFile) + filePathMap[subFile] = subFilePath + } + } else { + + if movingFilePath == dstDir { + // same directory, don't move + continue + } + + if !req.Overwrite { + if slices.Contains(existingFileNames, movingFile.GetName()) { + common.ErrorStrResp(c, fmt.Sprintf("file [%s] exists", movingFile.GetName()), 403) + return + } + existingFileNames = append(existingFileNames, movingFile.GetName()) + } + + movingFileNames = append(movingFileNames, movingFileName) + } + + } + + for i, fileName := range movingFileNames { + // move + err := fs.Move(c, fileName, dstDir, len(movingFileNames) > i+1) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + } + + common.SuccessResp(c) +} + type BatchRenameReq struct { SrcDir string `json:"src_dir"` RenameObjects []struct { @@ -61,94 +177,6 @@ func FsBatchRename(c *gin.Context) { common.SuccessResp(c) } -type RecursiveMoveReq struct { - SrcDir string `json:"src_dir"` - DstDir string `json:"dst_dir"` -} - -func FsRecursiveMove(c *gin.Context) { - var req RecursiveMoveReq - if err := c.ShouldBind(&req); err != nil { - common.ErrorResp(c, err, 400) - return - } - - user := c.MustGet("user").(*model.User) - if !user.CanMove() { - common.ErrorResp(c, errs.PermissionDenied, 403) - return - } - srcDir, err := user.JoinPath(req.SrcDir) - if err != nil { - common.ErrorResp(c, err, 403) - return - } - dstDir, err := user.JoinPath(req.DstDir) - if err != nil { - common.ErrorResp(c, err, 403) - return - } - - meta, err := op.GetNearestMeta(srcDir) - if err != nil { - if !errors.Is(errors.Cause(err), errs.MetaNotFound) { - common.ErrorResp(c, err, 500, true) - return - } - } - c.Set("meta", meta) - - rootFiles, err := fs.List(c, srcDir, &fs.ListArgs{}) - if err != nil { - common.ErrorResp(c, err, 500) - return - } - - // record the file path - filePathMap := make(map[model.Obj]string) - movingFiles := generic.NewQueue[model.Obj]() - for _, file := range rootFiles { - movingFiles.Push(file) - filePathMap[file] = srcDir - } - - for !movingFiles.IsEmpty() { - - movingFile := movingFiles.Pop() - movingFilePath := filePathMap[movingFile] - movingFileName := fmt.Sprintf("%s/%s", movingFilePath, movingFile.GetName()) - if movingFile.IsDir() { - // directory, recursive move - subFilePath := movingFileName - subFiles, err := fs.List(c, movingFileName, &fs.ListArgs{Refresh: true}) - if err != nil { - common.ErrorResp(c, err, 500) - return - } - for _, subFile := range subFiles { - movingFiles.Push(subFile) - filePathMap[subFile] = subFilePath - } - } else { - - if movingFilePath == dstDir { - // same directory, don't move - continue - } - - // move - err := fs.Move(c, movingFileName, dstDir, movingFiles.IsEmpty()) - if err != nil { - common.ErrorResp(c, err, 500) - return - } - } - - } - - common.SuccessResp(c) -} - type RegexRenameReq struct { SrcDir string `json:"src_dir"` SrcNameRegex string `json:"src_name_regex"`