diff --git a/backend/hdfs/fs.go b/backend/hdfs/fs.go index 1fbd44b1e..8de9ddbac 100644 --- a/backend/hdfs/fs.go +++ b/backend/hdfs/fs.go @@ -263,6 +263,98 @@ func (f *Fs) Purge(ctx context.Context, dir string) error { return f.client.RemoveAll(realpath) } +// Move src to this remote using server-side move operations. +// +// This is stored with the remote path given +// +// It returns the destination Object and a possible error +// +// Will only be called if src.Fs().Name() == f.Name() +// +// If it isn't possible then return fs.ErrorCantMove +func (f *Fs) Move(ctx context.Context, 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 + } + + // Get the real paths from the remote specs: + sourcePath := srcObj.fs.realpath(srcObj.remote) + targetPath := f.realpath(remote) + fs.Debugf(f, "rename [%s] to [%s]", sourcePath, targetPath) + + // Make sure the target folder exists: + dirname := path.Dir(targetPath) + err := f.client.MkdirAll(dirname, 0755) + if err != nil { + return nil, err + } + + // Do the move + // Note that the underlying HDFS library hard-codes Overwrite=True, but this is expected rclone behaviour. + err = f.client.Rename(sourcePath, targetPath) + if err != nil { + return nil, err + } + + // Look up the resulting object + info, err := f.client.Stat(targetPath) + if err != nil { + return nil, err + } + + // And return it: + return &Object{ + fs: f, + remote: remote, + size: info.Size(), + modTime: info.ModTime(), + }, 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(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) (err error) { + srcFs, ok := src.(*Fs) + if !ok { + return fs.ErrorCantDirMove + } + + // Get the real paths from the remote specs: + sourcePath := srcFs.realpath(srcRemote) + targetPath := f.realpath(dstRemote) + fs.Debugf(f, "rename [%s] to [%s]", sourcePath, targetPath) + + // Check if the destination exists: + info, err := f.client.Stat(targetPath) + if err == nil { + fs.Debugf(f, "target directory already exits, IsDir = [%t]", info.IsDir()) + return fs.ErrorDirExists + } + + // Make sure the targets parent folder exists: + dirname := path.Dir(targetPath) + err = f.client.MkdirAll(dirname, 0755) + if err != nil { + return err + } + + // Do the move + err = f.client.Rename(sourcePath, targetPath) + if err != nil { + return err + } + + return nil +} + // About gets quota information from the Fs func (f *Fs) About(ctx context.Context) (*fs.Usage, error) { info, err := f.client.StatFs() @@ -318,4 +410,6 @@ var ( _ fs.Purger = (*Fs)(nil) _ fs.PutStreamer = (*Fs)(nil) _ fs.Abouter = (*Fs)(nil) + _ fs.Mover = (*Fs)(nil) + _ fs.DirMover = (*Fs)(nil) ) diff --git a/docs/content/overview.md b/docs/content/overview.md index 7a1de1b09..74773af89 100644 --- a/docs/content/overview.md +++ b/docs/content/overview.md @@ -422,7 +422,7 @@ upon backend-specific capabilities. | Google Cloud Storage | Yes | Yes | No | No | No | Yes | Yes | No | No | No | | Google Drive | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Google Photos | No | No | No | No | No | No | No | No | No | No | -| HDFS | Yes | No | No | No | No | No | Yes | No | Yes | Yes | +| HDFS | Yes | No | Yes | Yes | No | No | Yes | No | Yes | Yes | | HTTP | No | No | No | No | No | No | No | No | No | Yes | | Hubic | Yes † | Yes | No | No | No | Yes | Yes | No | Yes | No | | Jottacloud | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes |