vfs: enable rename for nearly all remotes using server side Move or Copy

Before this change remotes without server side Move (eg swift, s3,
gcs) would not be able to rename files.

After it means nearly all remotes will be able to rename files on
rclone mount with the notable exceptions of b2 and yandex.

This changes checks to see if the remote can do Move or Copy then
calls `operations.Move` to do the actual move.  This will do a server
side Move or Copy but won't download and re-upload the file.

It also checks to see if the destination exists first which avoids
conflicts or duplicates.

Fixes #1965
Fixes #2569
This commit is contained in:
Nick Craig-Wood 2018-09-20 10:46:44 +01:00
parent fc640d3a09
commit 59e14c25df
3 changed files with 26 additions and 9 deletions

View File

@ -359,6 +359,11 @@ func Copy(f fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Objec
// Move src object to dst or fdst if nil. If dst is nil then it uses // Move src object to dst or fdst if nil. If dst is nil then it uses
// remote as the name of the new object. // remote as the name of the new object.
// //
// Note that you must check the destination does not exist before
// calling this and pass it as dst. If you pass dst=nil and the
// destination does exist then this may create duplicates or return
// errors.
//
// It returns the destination object if possible. Note that this may // It returns the destination object if possible. Note that this may
// be nil. // be nil.
func Move(fdst fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) { func Move(fdst fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) {

View File

@ -423,6 +423,8 @@ func TestDirRename(t *testing.T) {
r := fstest.NewRun(t) r := fstest.NewRun(t)
defer r.Finalise() defer r.Finalise()
vfs, dir, file1 := dirCreate(t, r) vfs, dir, file1 := dirCreate(t, r)
file3 := r.WriteObject("dir/file3", "file3 contents!", t1)
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file3}, []string{"dir"}, r.Fremote.Precision())
root, err := vfs.Root() root, err := vfs.Root()
require.NoError(t, err) require.NoError(t, err)
@ -434,11 +436,12 @@ func TestDirRename(t *testing.T) {
err = root.Rename("dir", "dir2", root) err = root.Rename("dir", "dir2", root)
assert.NoError(t, err) assert.NoError(t, err)
checkListing(t, root, []string{"dir2,0,true"}) checkListing(t, root, []string{"dir2,0,true"})
checkListing(t, dir, []string{"file1,14,false"}) checkListing(t, dir, []string{"file1,14,false", "file3,15,false"})
// check the underlying r.Fremote // check the underlying r.Fremote
file1.Path = "dir2/file1" file1.Path = "dir2/file1"
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir2"}, r.Fremote.Precision()) file3.Path = "dir2/file3"
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file3}, []string{"dir2"}, r.Fremote.Precision())
// refetch dir // refetch dir
node, err := vfs.Stat("dir2") node, err := vfs.Stat("dir2")
@ -449,10 +452,20 @@ func TestDirRename(t *testing.T) {
err = dir.Rename("file1", "file2", root) err = dir.Rename("file1", "file2", root)
assert.NoError(t, err) assert.NoError(t, err)
checkListing(t, root, []string{"dir2,0,true", "file2,14,false"}) checkListing(t, root, []string{"dir2,0,true", "file2,14,false"})
checkListing(t, dir, []string(nil)) checkListing(t, dir, []string{"file3,15,false"})
// check the underlying r.Fremote // check the underlying r.Fremote
file1.Path = "file2" file1.Path = "file2"
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, file3}, []string{"dir2"}, r.Fremote.Precision())
// Rename a file on top of another file
err = root.Rename("file2", "file3", dir)
assert.NoError(t, err)
checkListing(t, root, []string{"dir2,0,true"})
checkListing(t, dir, []string{"file3,14,false"})
// check the underlying r.Fremote
file1.Path = "dir2/file3"
fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir2"}, r.Fremote.Precision()) fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"dir2"}, r.Fremote.Precision())
// read only check // read only check

View File

@ -9,6 +9,7 @@ import (
"github.com/ncw/rclone/fs" "github.com/ncw/rclone/fs"
"github.com/ncw/rclone/fs/log" "github.com/ncw/rclone/fs/log"
"github.com/ncw/rclone/fs/operations"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -108,18 +109,16 @@ func (f *File) applyPendingRename() {
// Otherwise it will queue the rename operation on the remote until no writers // Otherwise it will queue the rename operation on the remote until no writers
// remain. // remain.
func (f *File) rename(destDir *Dir, newName string) error { func (f *File) rename(destDir *Dir, newName string) error {
// FIXME: could Copy then Delete if Move not available if features := f.d.f.Features(); features.Move == nil && features.Copy == nil {
// - though care needed if case insensitive... err := errors.Errorf("Fs %q can't rename files (no server side Move or Copy)", f.d.f)
doMove := f.d.f.Features().Move
if doMove == nil {
err := errors.Errorf("Fs %q can't rename files (no Move)", f.d.f)
fs.Errorf(f.Path(), "Dir.Rename error: %v", err) fs.Errorf(f.Path(), "Dir.Rename error: %v", err)
return err return err
} }
renameCall := func() error { renameCall := func() error {
newPath := path.Join(destDir.path, newName) newPath := path.Join(destDir.path, newName)
newObject, err := doMove(f.o, newPath) dstOverwritten, _ := f.d.f.NewObject(newPath)
newObject, err := operations.Move(f.d.f, dstOverwritten, newPath, f.o)
if err != nil { if err != nil {
fs.Errorf(f.Path(), "File.Rename error: %v", err) fs.Errorf(f.Path(), "File.Rename error: %v", err)
return err return err