From fd02425547cd83736daa3da32ed0d38fe0e48b1c Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Sun, 1 Dec 2019 18:31:29 +0800 Subject: [PATCH] Feat: move files/directories --- models/file.go | 8 ++++ models/folder.go | 84 ++++++++++++++++++++++++++++++++++ pkg/filesystem/path.go | 45 +++++++++++++----- pkg/util/common.go | 10 ++++ pkg/util/path.go | 9 ++++ routers/controllers/objects.go | 15 ++++++ routers/router.go | 2 + service/explorer/directory.go | 3 -- service/explorer/objects.go | 27 +++++++++++ 9 files changed, 189 insertions(+), 14 deletions(-) diff --git a/models/file.go b/models/file.go index 89984ee..08442a1 100644 --- a/models/file.go +++ b/models/file.go @@ -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 +} diff --git a/models/folder.go b/models/folder.go index c2d4517..b74037a 100644 --- a/models/folder.go +++ b/models/folder.go @@ -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 + +} diff --git a/pkg/filesystem/path.go b/pkg/filesystem/path.go index 04f8c52..dd98a4d 100644 --- a/pkg/filesystem/path.go +++ b/pkg/filesystem/path.go @@ -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 } diff --git a/pkg/util/common.go b/pkg/util/common.go index 1f72510..32b5fd0 100644 --- a/pkg/util/common.go +++ b/pkg/util/common.go @@ -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 + } +} diff --git a/pkg/util/path.go b/pkg/util/path.go index 3467993..eb70909 100644 --- a/pkg/util/path.go +++ b/pkg/util/path.go @@ -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 + "/" + +} diff --git a/routers/controllers/objects.go b/routers/controllers/objects.go index 0db6762..6eaabb0 100644 --- a/routers/controllers/objects.go +++ b/routers/controllers/objects.go @@ -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)) + } +} diff --git a/routers/router.go b/routers/router.go index ae3f506..21c8ce0 100644 --- a/routers/router.go +++ b/routers/router.go @@ -78,7 +78,9 @@ func InitRouter() *gin.Engine { // 对象,文件和目录的抽象 object := auth.Group("object") { + // 删除对象 object.DELETE("", controllers.Delete) + object.PATCH("", controllers.Move) } } diff --git a/service/explorer/directory.go b/service/explorer/directory.go index bb634b5..5b42576 100644 --- a/service/explorer/directory.go +++ b/service/explorer/directory.go @@ -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, } diff --git a/service/explorer/objects.go b/service/explorer/objects.go index d4771e3..33792ab 100644 --- a/service/explorer/objects.go +++ b/service/explorer/objects.go @@ -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, + } + +}