From d2be792d5e98ca88ef82211a27387caf250536f8 Mon Sep 17 00:00:00 2001 From: Gary Kim Date: Mon, 10 Jun 2019 18:01:13 +0800 Subject: [PATCH] moveto: fix case-insensitive same remote move --- fs/operations/operations.go | 41 ++++++++++++++++++++++++++++++-- fs/operations/operations_test.go | 35 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/fs/operations/operations.go b/fs/operations/operations.go index b91e168c2..5cde8f0a0 100644 --- a/fs/operations/operations.go +++ b/fs/operations/operations.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "path" "path/filepath" "sort" @@ -423,8 +424,8 @@ func Move(fdst fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Ob } // See if we have Move available if doMove := fdst.Features().Move; doMove != nil && (SameConfig(src.Fs(), fdst) || (SameRemoteType(src.Fs(), fdst) && fdst.Features().ServerSideAcrossConfigs)) { - // Delete destination if it exists - if dst != nil { + // Delete destination if it exists and is not the same file as src (could be same file while seemingly different if the remote is case insensitive) + if dst != nil && (!fdst.Features().CaseInsensitive || strings.ToLower(dst.Remote()) != strings.ToLower(src.Remote())) { err = DeleteFile(dst) if err != nil { return newDst, err @@ -1478,6 +1479,31 @@ func moveOrCopyFile(fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName stri return err } + // Special case for changing case of a file on a case insensitive remote + // This will move the file to a temporary name then + // move it back to the intended destination. This is required + // to avoid issues with certain remotes and avoid file deletion. + if !cp && fdst.Name() == fsrc.Name() && fdst.Features().CaseInsensitive && dstFileName != srcFileName && strings.ToLower(dstFilePath) == strings.ToLower(srcFilePath) { + // Create random name to temporarily move file to + tmpObjName := dstFileName + "-rclone-move-" + random(8) + _, err := fdst.NewObject(tmpObjName) + if err != fs.ErrorObjectNotFound { + if err == nil { + return errors.New("found an already existing file with a randomly generated name. Try the operation again") + } + return errors.Wrap(err, "error while attempting to move file to a temporary location") + } + accounting.Stats.Transferring(srcFileName) + tmpObj, err := Op(fdst, nil, tmpObjName, srcObj) + if err != nil { + accounting.Stats.DoneTransferring(srcFileName, false) + return errors.Wrap(err, "error while moving file to temporary location") + } + _, err = Op(fdst, nil, dstFileName, tmpObj) + accounting.Stats.DoneTransferring(srcFileName, err == nil) + return err + } + if NeedTransfer(dstObj, srcObj) { // If destination already exists, then we must move it into --backup-dir if required if dstObj != nil && fs.Config.BackupDir != "" { @@ -1506,6 +1532,17 @@ func moveOrCopyFile(fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName stri return err } +// random generates a pseudorandom alphanumeric string +func random(length int) string { + randomOutput := make([]byte, length) + possibleCharacters := "123567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + rand.Seed(time.Now().Unix()) + for i := range randomOutput { + randomOutput[i] = possibleCharacters[rand.Intn(len(possibleCharacters))] + } + return string(randomOutput) +} + // MoveFile moves a single file possibly to a new name func MoveFile(fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string) (err error) { return moveOrCopyFile(fdst, fsrc, dstFileName, srcFileName, false) diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go index 507af4d5a..8634b5618 100644 --- a/fs/operations/operations_test.go +++ b/fs/operations/operations_test.go @@ -739,6 +739,41 @@ func TestMoveFile(t *testing.T) { fstest.CheckItems(t, r.Fremote, file2) } +func TestCaseInsensitiveMoveFile(t *testing.T) { + r := fstest.NewRun(t) + defer r.Finalise() + if !r.Fremote.Features().CaseInsensitive { + return + } + + file1 := r.WriteFile("file1", "file1 contents", t1) + fstest.CheckItems(t, r.Flocal, file1) + + file2 := file1 + file2.Path = "sub/file2" + + err := operations.MoveFile(r.Fremote, r.Flocal, file2.Path, file1.Path) + require.NoError(t, err) + fstest.CheckItems(t, r.Flocal) + fstest.CheckItems(t, r.Fremote, file2) + + r.WriteFile("file1", "file1 contents", t1) + fstest.CheckItems(t, r.Flocal, file1) + + err = operations.MoveFile(r.Fremote, r.Flocal, file2.Path, file1.Path) + require.NoError(t, err) + fstest.CheckItems(t, r.Flocal) + fstest.CheckItems(t, r.Fremote, file2) + + file2Capitalized := file2 + file2Capitalized.Path = "sub/File2" + + err = operations.MoveFile(r.Fremote, r.Fremote, file2Capitalized.Path, file2.Path) + require.NoError(t, err) + fstest.CheckItems(t, r.Flocal) + fstest.CheckItems(t, r.Fremote, file2Capitalized) +} + func TestMoveFileBackupDir(t *testing.T) { r := fstest.NewRun(t) defer r.Finalise()