diff --git a/backend/drive/drive.go b/backend/drive/drive.go index 8e878751a..600043072 100644 --- a/backend/drive/drive.go +++ b/backend/drive/drive.go @@ -3525,14 +3525,14 @@ func (f *Fs) unTrashDir(ctx context.Context, dir string, recurse bool) (r unTras return f.unTrash(ctx, dir, directoryID, true) } -// copy file with id to dest -func (f *Fs) copyID(ctx context.Context, id, dest string) (err error) { +// copy or move file with id to dest +func (f *Fs) copyOrMoveID(ctx context.Context, operation string, id, dest string) (err error) { info, err := f.getFile(ctx, id, f.getFileFields(ctx)) if err != nil { return fmt.Errorf("couldn't find id: %w", err) } if info.MimeType == driveFolderType { - return fmt.Errorf("can't copy directory use: rclone copy --drive-root-folder-id %s %s %s", id, fs.ConfigString(f), dest) + return fmt.Errorf("can't %s directory use: rclone %s --drive-root-folder-id %s %s %s", operation, operation, id, fs.ConfigString(f), dest) } info.Name = f.opt.Enc.ToStandardName(info.Name) o, err := f.newObjectWithInfo(ctx, info.Name, info) @@ -3553,9 +3553,15 @@ func (f *Fs) copyID(ctx context.Context, id, dest string) (err error) { if err != nil { return err } - _, err = operations.Copy(ctx, dstFs, nil, destLeaf, o) - if err != nil { - return fmt.Errorf("copy failed: %w", err) + + var opErr error + if operation == "moveid" { + _, opErr = operations.Move(ctx, dstFs, nil, destLeaf, o) + } else { + _, opErr = operations.Copy(ctx, dstFs, nil, destLeaf, o) + } + if opErr != nil { + return fmt.Errorf("%s failed: %w", operation, opErr) } return nil } @@ -3792,6 +3798,28 @@ attempted if possible. Use the --interactive/-i or --dry-run flag to see what would be copied before copying. `, +}, { + Name: "moveid", + Short: "Move files by ID", + Long: `This command moves files by ID + +Usage: + + rclone backend moveid drive: ID path + rclone backend moveid drive: ID1 path1 ID2 path2 + +It moves the drive file with ID given to the path (an rclone path which +will be passed internally to rclone moveto). + +The path should end with a / to indicate move the file as named to +this directory. If it doesn't end with a / then the last path +component will be used as the file name. + +If the destination is a drive backend then server-side moving will be +attempted if possible. + +Use the --interactive/-i or --dry-run flag to see what would be moved beforehand. +`, }, { Name: "exportformats", Short: "Dump the export formats for debug purposes", @@ -3970,16 +3998,16 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str dir = arg[0] } return f.unTrashDir(ctx, dir, true) - case "copyid": + case "copyid", "moveid": if len(arg)%2 != 0 { return nil, errors.New("need an even number of arguments") } for len(arg) > 0 { id, dest := arg[0], arg[1] arg = arg[2:] - err = f.copyID(ctx, id, dest) + err = f.copyOrMoveID(ctx, name, id, dest) if err != nil { - return nil, fmt.Errorf("failed copying %q to %q: %w", id, dest, err) + return nil, fmt.Errorf("failed %s %q to %q: %w", name, id, dest, err) } } return nil, nil diff --git a/backend/drive/drive_internal_test.go b/backend/drive/drive_internal_test.go index 023dd42da..976ceb50e 100644 --- a/backend/drive/drive_internal_test.go +++ b/backend/drive/drive_internal_test.go @@ -479,8 +479,8 @@ func (f *Fs) InternalTestUnTrash(t *testing.T) { require.NoError(t, f.Purge(ctx, "trashDir")) } -// TestIntegration/FsMkdir/FsPutFiles/Internal/CopyID -func (f *Fs) InternalTestCopyID(t *testing.T) { +// TestIntegration/FsMkdir/FsPutFiles/Internal/CopyOrMoveID +func (f *Fs) InternalTestCopyOrMoveID(t *testing.T) { ctx := context.Background() obj, err := f.NewObject(ctx, existingFile) require.NoError(t, err) @@ -498,7 +498,7 @@ func (f *Fs) InternalTestCopyID(t *testing.T) { } t.Run("BadID", func(t *testing.T) { - err = f.copyID(ctx, "ID-NOT-FOUND", dir+"/") + err = f.copyOrMoveID(ctx, "moveid", "ID-NOT-FOUND", dir+"/") require.Error(t, err) assert.Contains(t, err.Error(), "couldn't find id") }) @@ -506,19 +506,31 @@ func (f *Fs) InternalTestCopyID(t *testing.T) { t.Run("Directory", func(t *testing.T) { rootID, err := f.dirCache.RootID(ctx, false) require.NoError(t, err) - err = f.copyID(ctx, rootID, dir+"/") + err = f.copyOrMoveID(ctx, "moveid", rootID, dir+"/") require.Error(t, err) - assert.Contains(t, err.Error(), "can't copy directory") + assert.Contains(t, err.Error(), "can't moveid directory") }) - t.Run("WithoutDestName", func(t *testing.T) { - err = f.copyID(ctx, o.id, dir+"/") + t.Run("MoveWithoutDestName", func(t *testing.T) { + err = f.copyOrMoveID(ctx, "moveid", o.id, dir+"/") require.NoError(t, err) checkFile(path.Base(existingFile)) }) - t.Run("WithDestName", func(t *testing.T) { - err = f.copyID(ctx, o.id, dir+"/potato.txt") + t.Run("CopyWithoutDestName", func(t *testing.T) { + err = f.copyOrMoveID(ctx, "copyid", o.id, dir+"/") + require.NoError(t, err) + checkFile(path.Base(existingFile)) + }) + + t.Run("MoveWithDestName", func(t *testing.T) { + err = f.copyOrMoveID(ctx, "moveid", o.id, dir+"/potato.txt") + require.NoError(t, err) + checkFile("potato.txt") + }) + + t.Run("CopyWithDestName", func(t *testing.T) { + err = f.copyOrMoveID(ctx, "copyid", o.id, dir+"/potato.txt") require.NoError(t, err) checkFile("potato.txt") }) @@ -647,7 +659,7 @@ func (f *Fs) InternalTest(t *testing.T) { }) t.Run("Shortcuts", f.InternalTestShortcuts) t.Run("UnTrash", f.InternalTestUnTrash) - t.Run("CopyID", f.InternalTestCopyID) + t.Run("CopyOrMoveID", f.InternalTestCopyOrMoveID) t.Run("Query", f.InternalTestQuery) t.Run("AgeQuery", f.InternalTestAgeQuery) t.Run("ShouldRetry", f.InternalTestShouldRetry)