diff --git a/docs/content/overview.md b/docs/content/overview.md index 9ff8055c1..7b784dc0e 100644 --- a/docs/content/overview.md +++ b/docs/content/overview.md @@ -28,8 +28,8 @@ Here is an overview of the major features of each cloud storage system. | Backblaze B2 | SHA1 | Yes | No | No | R/W | | Yandex Disk | MD5 | Yes | No | No | R/W | | SFTP | - | Yes | Depends | No | - | -| The local filesystem | All | Yes | Depends | No | - | | FTP | None | No | Yes | No | - | +| The local filesystem | All | Yes | Depends | No | - | ### Hash ### @@ -117,8 +117,8 @@ operations more efficient. | Backblaze B2 | No | No | No | No | Yes | | Yandex Disk | Yes | No | No | No | No [#575](https://github.com/ncw/rclone/issues/575) | | SFTP | No | No | Yes | Yes | No | +| FTP | No | No | Yes | Yes | No | | The local filesystem | Yes | No | Yes | Yes | No | -| FTP | No | No | No | No | No | ### Purge ### diff --git a/ftp/ftp.go b/ftp/ftp.go index 0f9d44ecb..97d1611ec 100644 --- a/ftp/ftp.go +++ b/ftp/ftp.go @@ -433,6 +433,86 @@ func (f *Fs) Rmdir(dir string) error { return translateErrorDir(err) } +// Move renames a remote file object +func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) { + srcObj, ok := src.(*Object) + if !ok { + fs.Debugf(src, "Can't move - not same remote type") + return nil, fs.ErrorCantMove + } + err := f.mkParentDir(remote) + if err != nil { + return nil, errors.Wrap(err, "Move mkParentDir failed") + } + c, err := f.getFtpConnection() + if err != nil { + return nil, errors.Wrap(err, "Move") + } + err = c.Rename( + path.Join(srcObj.fs.root, srcObj.remote), + path.Join(f.root, remote), + ) + f.putFtpConnection(&c) + if err != nil { + return nil, errors.Wrap(err, "Move Rename failed") + } + dstObj, err := f.NewObject(remote) + if err != nil { + return nil, errors.Wrap(err, "Move NewObject failed") + } + return dstObj, nil +} + +// DirMove moves src, srcRemote to this remote at dstRemote +// using server side move operations. +// +// Will only be called if src.Fs().Name() == f.Name() +// +// If it isn't possible then return fs.ErrorCantDirMove +// +// If destination exists then return fs.ErrorDirExists +func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error { + srcFs, ok := src.(*Fs) + if !ok { + fs.Debugf(srcFs, "Can't move directory - not same remote type") + return fs.ErrorCantDirMove + } + srcPath := path.Join(srcFs.root, srcRemote) + dstPath := path.Join(f.root, dstRemote) + + // Check if destination exists + fi, err := f.getInfo(dstPath) + if err == nil { + if fi.IsDir { + return fs.ErrorDirExists + } + return fs.ErrorIsFile + } else if err != fs.ErrorObjectNotFound { + return errors.Wrapf(err, "DirMove getInfo failed") + } + + // Make sure the parent directory exists + err = f.mkdir(path.Dir(dstPath)) + if err != nil { + return errors.Wrap(err, "DirMove mkParentDir dst failed") + } + + // Do the move + c, err := f.getFtpConnection() + if err != nil { + return errors.Wrap(err, "DirMove") + } + err = c.Rename( + srcPath, + dstPath, + ) + f.putFtpConnection(&c) + if err != nil { + return errors.Wrapf(err, "DirMove Rename(%q,%q) failed", srcPath, dstPath) + } + return nil +} + // ------------------------------------------------------------ // Fs returns the parent Fs @@ -579,6 +659,8 @@ func (o *Object) Remove() (err error) { // Check the interfaces are satisfied var ( - _ fs.Fs = &Fs{} - _ fs.Object = &Object{} + _ fs.Fs = &Fs{} + _ fs.Mover = &Fs{} + _ fs.DirMover = &Fs{} + _ fs.Object = &Object{} )