mirror of https://github.com/Xhofe/alist
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 orderpull/7859/merge
parent
99f39410f2
commit
258b8f520f
|
@ -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"`
|
||||
|
|
Loading…
Reference in New Issue