diff --git a/fileutils/file.go b/fileutils/file.go index 0f782cf5..7194f774 100644 --- a/fileutils/file.go +++ b/fileutils/file.go @@ -1,7 +1,9 @@ package fileutils import ( + "fmt" "io" + "math/rand" "os" "path" "path/filepath" @@ -16,8 +18,14 @@ func MoveFile(fs afero.Fs, src, dst string) error { if fs.Rename(src, dst) == nil { return nil } + + info, err := fs.Stat(src) + if err == nil && info.IsDir() { + return CopyFolder(fs, src, dst) + } + // fallback - err := CopyFile(fs, src, dst) + err = CopyFile(fs, src, dst) if err != nil { _ = fs.Remove(dst) return err @@ -71,6 +79,40 @@ func CopyFile(fs afero.Fs, source, dest string) error { return nil } +// CopyFolder copies a folder to destination and returns +// an error if any. If the destination already exists, +// it get overriden. +func CopyFolder(fs afero.Fs, source, dest string) error { + tmpDest := "" + _, err := fs.Stat(dest) + if err == nil { + // destination already exists - rename it so that src + // could be moved to its place + tmpDest = fmt.Sprintf("%s.%d", dest, rand.Intn(9999)) + err = fs.Rename(dest, tmpDest) + if err != nil { + return err + } + } else if !os.IsNotExist(err) { + // unknown error, return + return err + } + + if err = fs.Rename(source, dest); err != nil { + // if moving fails and we renamed an existing destination + // in the previous step, then restore its name + if tmpDest != "" { + fs.Rename(tmpDest, dest) + } + return err + } + + // if moving was successful, delete the renamed dest + _ = fs.Remove(tmpDest) + + return nil +} + // CommonPrefix returns common directory path of provided files func CommonPrefix(sep byte, paths ...string) string { // Handle special cases. diff --git a/http/resource.go b/http/resource.go index 9dcba75e..7076223d 100644 --- a/http/resource.go +++ b/http/resource.go @@ -415,6 +415,32 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach } return fileutils.Copy(d.user.Fs, src, dst) + case "rename": + if !d.user.Perm.Rename { + return errors.ErrPermissionDenied + } + src = path.Clean("/" + src) + dst = path.Clean("/" + dst) + + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: src, + Modify: d.user.Perm.Modify, + Expand: false, + ReadHeader: false, + Checker: d, + }) + if err != nil { + return err + } + + // delete thumbnails + err = delThumbs(ctx, fileCache, file) + if err != nil { + return err + } + + return fileutils.MoveFile(d.user.Fs, src, dst) case "unarchive": if !d.user.Perm.Create { return errors.ErrPermissionDenied @@ -459,32 +485,6 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach return errors.ErrInvalidRequestParams } return nil - case "rename": - if !d.user.Perm.Rename { - return errors.ErrPermissionDenied - } - src = path.Clean("/" + src) - dst = path.Clean("/" + dst) - - file, err := files.NewFileInfo(files.FileOptions{ - Fs: d.user.Fs, - Path: src, - Modify: d.user.Perm.Modify, - Expand: false, - ReadHeader: false, - Checker: d, - }) - if err != nil { - return err - } - - // delete thumbnails - err = delThumbs(ctx, fileCache, file) - if err != nil { - return err - } - - return fileutils.MoveFile(d.user.Fs, src, dst) default: return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams) }