diff --git a/.travis.yml b/.travis.yml index 6ef93dd..f3d1a04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,6 @@ go: git: depth: 1 script: - - go build main.go - - go test -v -cover ./... + - go test -v -race -coverprofile=profile.out -covermode=atomic ./... +after_success: + - bash <(curl -s https://codecov.io/bash) -t uuid-repo-token diff --git a/models/folder.go b/models/folder.go index 1e0b352..ffd8e41 100644 --- a/models/folder.go +++ b/models/folder.go @@ -1,6 +1,7 @@ package model import ( + "errors" "github.com/HFO4/cloudreve/pkg/conf" "github.com/HFO4/cloudreve/pkg/util" "github.com/jinzhu/gorm" @@ -127,8 +128,8 @@ func (folder *Folder) MoveFileTo(files []string, dstFolder *Folder) error { } -// MoveFolderTo 将此目录下的目录移动至dstFolder -func (folder *Folder) MoveFolderTo(dirs []string, dstFolder *Folder) error { +// RenameFolderTo 将folder目录下的dirs子目录复制或移动到dstFolder +func (folder *Folder) RenameFolderTo(dirs []string, dstFolder *Folder, isCopy bool) error { // 生成绝对路径 fullDirs := make([]string, len(dirs)) for i := 0; i < len(dirs); i++ { @@ -150,13 +151,46 @@ func (folder *Folder) MoveFolderTo(dirs []string, dstFolder *Folder) error { subFolders[key] = toBeMoved } - // 更改顶级要移动目录的父目录指向 - 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 + // 记录复制要用到的父目录源路径和新的ID + var copyCache = make(map[string]uint) + + var err error + if isCopy { + // 复制 + // TODO:支持多目录 + origin := Folder{} + if DB.Where("position_absolute in (?) and owner_id = ?", fullDirs, folder.OwnerID).Find(&origin).Error != nil { + return errors.New("找不到原始目录") + } + + oldPosition := origin.PositionAbsolute + + // 更新复制后的相关属性 + origin.PositionAbsolute = util.FillSlash(dstFolder.PositionAbsolute) + origin.Name + origin.Position = dstFolder.PositionAbsolute + origin.ParentID = dstFolder.ID + + // 清空主键 + origin.Model = gorm.Model{} + + if err := DB.Create(&origin).Error; err != nil { + return err + } + + // 记录新的主键 + copyCache[oldPosition] = origin.Model.ID + + } else { + // 移动 + // 更改顶级要移动目录的父目录指向 + 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 } @@ -166,14 +200,48 @@ func (folder *Folder) MoveFolderTo(dirs []string, dstFolder *Folder) error { ignorePath := fullDirs[parKey] // TODO 找到更好的修改办法 - for _, subFolder := range toBeMoved { - // 每个分组的第一个目录已经变更指向,直接跳过 - if subFolder.PositionAbsolute != ignorePath { - newPosition := path.Join(dstFolder.PositionAbsolute, strings.Replace(subFolder.Position, folder.PositionAbsolute, "", 1)) - DB.Model(&subFolder).Updates(map[string]interface{}{ - "position": newPosition, - "position_absolute": path.Join(newPosition, subFolder.Name), - }) + if isCopy { + index := 0 + for len(toBeMoved) != 0 { + innerIndex := index % len(toBeMoved) + + // 如果是顶级父目录,直接删除,不需要复制 + if toBeMoved[innerIndex].PositionAbsolute == ignorePath { + toBeMoved = append(toBeMoved[:innerIndex], toBeMoved[innerIndex+1:]...) + continue + } + + // 如果缓存中存在父目录ID,执行复制,并删除 + if newID, ok := copyCache[toBeMoved[innerIndex].Position]; ok { + oldPosition := toBeMoved[innerIndex].PositionAbsolute + newPosition := path.Join( + dstFolder.PositionAbsolute, strings.Replace( + toBeMoved[innerIndex].Position, + folder.PositionAbsolute, "", 1), + ) + toBeMoved[innerIndex].Position = newPosition + toBeMoved[innerIndex].PositionAbsolute = path.Join(newPosition, toBeMoved[innerIndex].Name) + toBeMoved[innerIndex].ParentID = newID + toBeMoved[innerIndex].Model = gorm.Model{} + if err := DB.Create(&toBeMoved[innerIndex]).Error; err != nil { + return err + } + copyCache[oldPosition] = toBeMoved[innerIndex].Model.ID + toBeMoved = append(toBeMoved[:innerIndex], toBeMoved[innerIndex+1:]...) + } + } + + } else { + for _, subFolder := range toBeMoved { + // 每个分组的第一个目录已经变更指向,直接跳过 + if subFolder.PositionAbsolute != ignorePath { + newPosition := path.Join(dstFolder.PositionAbsolute, strings.Replace(subFolder.Position, folder.PositionAbsolute, "", 1)) + // 移动 + DB.Model(&subFolder).Updates(map[string]interface{}{ + "position": newPosition, + "position_absolute": path.Join(newPosition, subFolder.Name), + }) + } } } @@ -188,11 +256,22 @@ func (folder *Folder) MoveFolderTo(dirs []string, dstFolder *Folder) error { if err != nil { return err } + for _, subFile := range toBeMovedFile { newPosition := path.Join(dstFolder.PositionAbsolute, strings.Replace(subFile.Dir, folder.PositionAbsolute, "", 1)) - DB.Model(&subFile).Updates(map[string]interface{}{ - "dir": newPosition, - }) + if isCopy { + // 复制 + subFile.Dir = newPosition + subFile.Model = gorm.Model{} + if err := DB.Create(&subFile).Error; err != nil { + util.Log().Warning("无法复制子文件:%s", err) + } + } else { + DB.Model(&subFile).Updates(map[string]interface{}{ + "dir": newPosition, + }) + } + } } diff --git a/pkg/filesystem/path.go b/pkg/filesystem/path.go index 7daa0bd..fa51631 100644 --- a/pkg/filesystem/path.go +++ b/pkg/filesystem/path.go @@ -25,6 +25,27 @@ type Object struct { Date string `json:"date"` } +// Copy 复制src目录下的文件或目录到dst +func (fs *FileSystem) Copy(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 + } + + // 复制目录 + if len(dirs) > 0 { + err := srcFolder.RenameFolderTo(dirs, dstFolder, true) + if err != nil { + return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err) + } + } + + return nil +} + // Move 移动文件和目录 func (fs *FileSystem) Move(ctx context.Context, dirs, files []string, src, dst string) error { // 获取目的目录 @@ -36,7 +57,7 @@ func (fs *FileSystem) Move(ctx context.Context, dirs, files []string, src, dst s } // 处理目录及子文件移动 - err := srcFolder.MoveFolderTo(dirs, dstFolder) + err := srcFolder.RenameFolderTo(dirs, dstFolder, false) if err != nil { return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err) } diff --git a/routers/controllers/objects.go b/routers/controllers/objects.go index 6eaabb0..3b179cf 100644 --- a/routers/controllers/objects.go +++ b/routers/controllers/objects.go @@ -35,3 +35,18 @@ func Move(c *gin.Context) { c.JSON(200, ErrorResponse(err)) } } + +// Copy 复制文件或目录 +func Copy(c *gin.Context) { + // 创建上下文 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var service explorer.ItemMoveService + if err := c.ShouldBindJSON(&service); err == nil { + res := service.Copy(ctx, c) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index 21c8ce0..a00e9d5 100644 --- a/routers/router.go +++ b/routers/router.go @@ -80,7 +80,10 @@ func InitRouter() *gin.Engine { { // 删除对象 object.DELETE("", controllers.Delete) + // 移动对象 object.PATCH("", controllers.Move) + // 复制对象 + object.POST("copy", controllers.Copy) } } diff --git a/service/explorer/objects.go b/service/explorer/objects.go index 191c3e5..549538a 100644 --- a/service/explorer/objects.go +++ b/service/explorer/objects.go @@ -48,7 +48,7 @@ func (service *ItemMoveService) Move(ctx context.Context, c *gin.Context) serial 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) @@ -59,3 +59,28 @@ func (service *ItemMoveService) Move(ctx context.Context, c *gin.Context) serial } } + +// Copy 复制对象 +func (service *ItemMoveService) Copy(ctx context.Context, c *gin.Context) serializer.Response { + // 复制操作只能对一个目录或文件对象进行操作 + if len(service.Src.Items)+len(service.Src.Dirs) > 1 { + return serializer.ParamErr("只能复制一个对象", nil) + } + + // 创建文件系统 + fs, err := filesystem.NewFileSystemFromContext(c) + if err != nil { + return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) + } + + // 移动对象 + err = fs.Copy(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, + } + +}