mirror of https://github.com/cloudreve/Cloudreve
				
				
				
			Feat: move files/directories
							parent
							
								
									081c92067f
								
							
						
					
					
						commit
						fd02425547
					
				|  | @ -126,3 +126,11 @@ func DeleteFileByIDs(ids []uint) error { | |||
| 	result := DB.Where("id in (?)", ids).Delete(&File{}) | ||||
| 	return result.Error | ||||
| } | ||||
| 
 | ||||
| // GetRecursiveByPaths 根据给定的文件路径(s)递归查找文件
 | ||||
| func GetRecursiveByPaths(paths []string, uid uint) ([]File, error) { | ||||
| 	files := make([]File, 0, len(paths)) | ||||
| 	search := util.BuildRegexp(paths, "^", "/", "|") | ||||
| 	result := DB.Where("(user_id = ? and dir REGEXP ?) or dir in (?)", uid, search, paths).Find(&files) | ||||
| 	return files, result.Error | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,11 @@ | |||
| package model | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/HFO4/cloudreve/pkg/conf" | ||||
| 	"github.com/HFO4/cloudreve/pkg/util" | ||||
| 	"github.com/jinzhu/gorm" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // Folder 目录
 | ||||
|  | @ -52,3 +55,84 @@ func DeleteFolderByIDs(ids []uint) error { | |||
| 	result := DB.Where("id in (?)", ids).Delete(&Folder{}) | ||||
| 	return result.Error | ||||
| } | ||||
| 
 | ||||
| //func (folder *Folder)GetPositionAbsolute()string{
 | ||||
| //	return path.Join(folder.Position,folder.Name)
 | ||||
| //}
 | ||||
| 
 | ||||
| // MoveFileTo 将此目录下的文件递归移动至dstFolder
 | ||||
| func (folder *Folder) MoveFileTo(files []string, dstFolder *Folder) error { | ||||
| 	// 生成绝对路径
 | ||||
| 	fullFilePath := make([]string, len(files)) | ||||
| 	for i := 0; i < len(files); i++ { | ||||
| 		fullFilePath[i] = path.Join(folder.PositionAbsolute, files[i]) | ||||
| 	} | ||||
| 
 | ||||
| 	// 更改顶级要移动文件的父目录指向
 | ||||
| 	err := DB.Model(File{}).Where("dir in (?) and user_id = ?", fullFilePath, folder.OwnerID). | ||||
| 		Update(map[string]interface{}{ | ||||
| 			"folder_id": dstFolder.ID, | ||||
| 			"dir":       dstFolder.PositionAbsolute, | ||||
| 		}). | ||||
| 		Error | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // MoveFolderTo 将此目录下的目录移动至dstFolder
 | ||||
| func (folder *Folder) MoveFolderTo(dirs []string, dstFolder *Folder) error { | ||||
| 	// 生成全局路径
 | ||||
| 	fullDirs := make([]string, len(dirs)) | ||||
| 	for i := 0; i < len(dirs); i++ { | ||||
| 		fullDirs[i] = path.Join(folder.PositionAbsolute, dirs[i]) | ||||
| 	} | ||||
| 
 | ||||
| 	// 更改顶级要移动目录的父目录指向
 | ||||
| 	err := DB.Model(Folder{}).Where("position_absolute in (?) and owner_id = ?", fullDirs, folder.OwnerID). | ||||
| 		Update(map[string]interface{}{ | ||||
| 			"parent_id":         dstFolder.ID, | ||||
| 			"position":          dstFolder.PositionAbsolute, | ||||
| 			"position_absolute": gorm.Expr(util.BuildConcat("?", "name", conf.DatabaseConfig.Type), util.FillSlash(dstFolder.PositionAbsolute)), | ||||
| 		}). | ||||
| 		Error | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// 更新被移动的目录递归的子目录
 | ||||
| 	for _, parentDir := range fullDirs { | ||||
| 		toBeMoved, err := GetRecursiveChildFolder([]string{parentDir}, folder.OwnerID) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// TODO 找到更好的修改办法
 | ||||
| 		for _, subFolder := range toBeMoved { | ||||
| 			newPosition := path.Join(dstFolder.PositionAbsolute, strings.Replace(subFolder.Position, folder.Position, "", 1)) | ||||
| 			DB.Model(&subFolder).Updates(map[string]interface{}{ | ||||
| 				"position":          newPosition, | ||||
| 				"position_absolute": path.Join(newPosition, subFolder.Name), | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// 更新被移动的目录递归的子文件
 | ||||
| 	for _, parentDir := range fullDirs { | ||||
| 		toBeMoved, err := GetRecursiveByPaths([]string{parentDir}, folder.OwnerID) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		// TODO 找到更好的修改办法
 | ||||
| 		for _, subFile := range toBeMoved { | ||||
| 			newPosition := path.Join(dstFolder.PositionAbsolute, strings.Replace(subFile.Dir, folder.Position, "", 1)) | ||||
| 			DB.Model(&subFile).Updates(map[string]interface{}{ | ||||
| 				"dir": newPosition, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -25,6 +25,33 @@ type Object struct { | |||
| 	Date string `json:"date"` | ||||
| } | ||||
| 
 | ||||
| // Move 移动文件和目录
 | ||||
| func (fs *FileSystem) Move(ctx context.Context, dirs, files []string, src, dst string) error { | ||||
| 	// 获取目的目录
 | ||||
| 	isDstExist, dstFolder := fs.IsPathExist(dst) | ||||
| 	isSrcExist, srcFolder := fs.IsPathExist(src) | ||||
| 	// 不存在时返回空的结果
 | ||||
| 	if !isDstExist || !isSrcExist { | ||||
| 		return ErrPathNotExist | ||||
| 	} | ||||
| 
 | ||||
| 	// 处理目录移动
 | ||||
| 	err := srcFolder.MoveFolderTo(dirs, dstFolder) | ||||
| 	if err != nil { | ||||
| 		return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 处理文件移动
 | ||||
| 	err = srcFolder.MoveFileTo(files, dstFolder) | ||||
| 	if err != nil { | ||||
| 		return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 移动文件
 | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Delete 递归删除对象
 | ||||
| func (fs *FileSystem) Delete(ctx context.Context, dirs, files []string) error { | ||||
| 	// 已删除的总容量,map用于去重
 | ||||
|  | @ -141,7 +168,7 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu | |||
| 	isExist, folder := fs.IsPathExist(dirPath) | ||||
| 	// 不存在时返回空的结果
 | ||||
| 	if !isExist { | ||||
| 		return nil, nil | ||||
| 		return []Object{}, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// 获取子目录
 | ||||
|  | @ -205,38 +232,34 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) erro | |||
| 	fullPath = path.Clean(fullPath) | ||||
| 	base := path.Dir(fullPath) | ||||
| 	dir := path.Base(fullPath) | ||||
| 
 | ||||
| 	// 检查目录名是否合法
 | ||||
| 	if !fs.ValidateLegalName(ctx, dir) { | ||||
| 		return ErrIllegalObjectName | ||||
| 	} | ||||
| 
 | ||||
| 	// 父目录是否存在
 | ||||
| 	isExist, parent := fs.IsPathExist(base) | ||||
| 	if !isExist { | ||||
| 		return ErrPathNotExist | ||||
| 	} | ||||
| 
 | ||||
| 	// 是否有同名目录
 | ||||
| 	// TODO: 有了unique约束后可以不用在此检查
 | ||||
| 	b, _ := fs.IsPathExist(fullPath) | ||||
| 	if b { | ||||
| 		return ErrFolderExisted | ||||
| 	} | ||||
| 
 | ||||
| 	// 是否有同名文件
 | ||||
| 	if ok, _ := fs.IsFileExist(path.Join(base, dir)); ok { | ||||
| 		return ErrFileExisted | ||||
| 	} | ||||
| 
 | ||||
| 	// 创建目录
 | ||||
| 	newFolder := model.Folder{ | ||||
| 		Name:     dir, | ||||
| 		ParentID: parent.ID, | ||||
| 		Position: base, | ||||
| 		OwnerID:  fs.User.ID, | ||||
| 		Name:             dir, | ||||
| 		ParentID:         parent.ID, | ||||
| 		Position:         base, | ||||
| 		PositionAbsolute: fullPath, | ||||
| 		OwnerID:          fs.User.ID, | ||||
| 	} | ||||
| 	_, err := newFolder.Create() | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -56,3 +56,13 @@ func BuildRegexp(search []string, prefix, suffix, condition string) string { | |||
| 	} | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| // BuildConcat 根据数据库类型构建字符串连接表达式
 | ||||
| func BuildConcat(str1, str2 string, DBType string) string { | ||||
| 	switch DBType { | ||||
| 	case "mysql": | ||||
| 		return "CONCAT(" + str1 + "," + str2 + ")" | ||||
| 	default: | ||||
| 		return str1 + "||" + str2 | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -6,3 +6,12 @@ import "strings" | |||
| func DotPathToStandardPath(path string) string { | ||||
| 	return "/" + strings.Replace(path, ",", "/", -1) | ||||
| } | ||||
| 
 | ||||
| // FillSlash 给路径补全`/`
 | ||||
| func FillSlash(path string) string { | ||||
| 	if path == "/" { | ||||
| 		return path | ||||
| 	} | ||||
| 	return path + "/" | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -20,3 +20,18 @@ func Delete(c *gin.Context) { | |||
| 		c.JSON(200, ErrorResponse(err)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Move 移动文件或目录
 | ||||
| func Move(c *gin.Context) { | ||||
| 	// 创建上下文
 | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	defer cancel() | ||||
| 
 | ||||
| 	var service explorer.ItemMoveService | ||||
| 	if err := c.ShouldBindJSON(&service); err == nil { | ||||
| 		res := service.Move(ctx, c) | ||||
| 		c.JSON(200, res) | ||||
| 	} else { | ||||
| 		c.JSON(200, ErrorResponse(err)) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -78,7 +78,9 @@ func InitRouter() *gin.Engine { | |||
| 			// 对象,文件和目录的抽象
 | ||||
| 			object := auth.Group("object") | ||||
| 			{ | ||||
| 				// 删除对象
 | ||||
| 				object.DELETE("", controllers.Delete) | ||||
| 				object.PATCH("", controllers.Move) | ||||
| 			} | ||||
| 
 | ||||
| 		} | ||||
|  |  | |||
|  | @ -43,17 +43,14 @@ func (service *DirectoryService) CreateDirectory(c *gin.Context) serializer.Resp | |||
| 	if err != nil { | ||||
| 		return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 上下文
 | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	defer cancel() | ||||
| 
 | ||||
| 	// 创建目录
 | ||||
| 	err = fs.CreateDirectory(ctx, service.Path) | ||||
| 	if err != nil { | ||||
| 		return serializer.Err(serializer.CodeCreateFolderFailed, err.Error(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	return serializer.Response{ | ||||
| 		Code: 0, | ||||
| 	} | ||||
|  |  | |||
|  | @ -7,6 +7,13 @@ import ( | |||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
| 
 | ||||
| // ItemMoveService 处理多文件/目录移动
 | ||||
| type ItemMoveService struct { | ||||
| 	SrcDir string      `json:"src_dir" binding:"required,min=1,max=65535"` | ||||
| 	Src    ItemService `json:"src" binding:"exists"` | ||||
| 	Dst    string      `json:"dst" binding:"required,min=1,max=65535"` | ||||
| } | ||||
| 
 | ||||
| // ItemService 处理多文件/目录相关服务
 | ||||
| type ItemService struct { | ||||
| 	Items []string `json:"items" binding:"exists"` | ||||
|  | @ -32,3 +39,23 @@ func (service *ItemService) Delete(ctx context.Context, c *gin.Context) serializ | |||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Move 移动对象
 | ||||
| func (service *ItemMoveService) Move(ctx context.Context, c *gin.Context) serializer.Response { | ||||
| 	// 创建文件系统
 | ||||
| 	fs, err := filesystem.NewFileSystemFromContext(c) | ||||
| 	if err != nil { | ||||
| 		return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 删除对象
 | ||||
| 	err = fs.Move(ctx, service.Src.Dirs, service.Src.Items, service.SrcDir, service.Dst) | ||||
| 	if err != nil { | ||||
| 		return serializer.Err(serializer.CodeNotSet, err.Error(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	return serializer.Response{ | ||||
| 		Code: 0, | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 HFO4
						HFO4