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
	
	 Jealous
						Jealous