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
pull/7859/merge
Jealous 2025-01-27 20:25:39 +08:00 committed by GitHub
parent 99f39410f2
commit 258b8f520f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 116 additions and 88 deletions

View File

@ -3,6 +3,7 @@ package handles
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"slices"
"github.com/alist-org/alist/v3/internal/errs" "github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs" "github.com/alist-org/alist/v3/internal/fs"
@ -14,6 +15,121 @@ import (
"github.com/pkg/errors" "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 { type BatchRenameReq struct {
SrcDir string `json:"src_dir"` SrcDir string `json:"src_dir"`
RenameObjects []struct { RenameObjects []struct {
@ -61,94 +177,6 @@ func FsBatchRename(c *gin.Context) {
common.SuccessResp(c) 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 { type RegexRenameReq struct {
SrcDir string `json:"src_dir"` SrcDir string `json:"src_dir"`
SrcNameRegex string `json:"src_name_regex"` SrcNameRegex string `json:"src_name_regex"`