diff --git a/backend/amazonclouddrive/amazonclouddrive.go b/backend/amazonclouddrive/amazonclouddrive.go index ebe5c51d5..273f89535 100644 --- a/backend/amazonclouddrive/amazonclouddrive.go +++ b/backend/amazonclouddrive/amazonclouddrive.go @@ -937,8 +937,8 @@ func (f *Fs) Hashes() hash.Set { // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // ------------------------------------------------------------ diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go index 6ed2a1e3e..cb54f94fd 100644 --- a/backend/azureblob/azureblob.go +++ b/backend/azureblob/azureblob.go @@ -967,8 +967,7 @@ func (f *Fs) Hashes() hash.Set { } // Purge deletes all the files and directories including the old versions. -func (f *Fs) Purge(ctx context.Context) error { - dir := "" // forward compat! +func (f *Fs) Purge(ctx context.Context, dir string) error { container, directory := f.split(dir) if container == "" || directory != "" { // Delegate to caller if not root of a container diff --git a/backend/b2/b2.go b/backend/b2/b2.go index 8b06ff8ed..e40adc224 100644 --- a/backend/b2/b2.go +++ b/backend/b2/b2.go @@ -1143,7 +1143,8 @@ func (f *Fs) deleteByID(ctx context.Context, ID, Name string) error { // if oldOnly is true then it deletes only non current files. // // Implemented here so we can make sure we delete old versions. -func (f *Fs) purge(ctx context.Context, bucket, directory string, oldOnly bool) error { +func (f *Fs) purge(ctx context.Context, dir string, oldOnly bool) error { + bucket, directory := f.split(dir) if bucket == "" { return errors.New("can't purge from root") } @@ -1218,19 +1219,19 @@ func (f *Fs) purge(ctx context.Context, bucket, directory string, oldOnly bool) wg.Wait() if !oldOnly { - checkErr(f.Rmdir(ctx, "")) + checkErr(f.Rmdir(ctx, dir)) } return errReturn } // Purge deletes all the files and directories including the old versions. -func (f *Fs) Purge(ctx context.Context) error { - return f.purge(ctx, f.rootBucket, f.rootDirectory, false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purge(ctx, dir, false) } // CleanUp deletes all the hidden files. func (f *Fs) CleanUp(ctx context.Context) error { - return f.purge(ctx, f.rootBucket, f.rootDirectory, true) + return f.purge(ctx, "", true) } // copy does a server side copy from dstObj <- srcObj diff --git a/backend/box/box.go b/backend/box/box.go index 25b8d3ba8..9903b4c30 100644 --- a/backend/box/box.go +++ b/backend/box/box.go @@ -862,8 +862,8 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // move a file or folder diff --git a/backend/cache/cache.go b/backend/cache/cache.go index d06e757ca..01140c358 100644 --- a/backend/cache/cache.go +++ b/backend/cache/cache.go @@ -1702,17 +1702,20 @@ func (f *Fs) Hashes() hash.Set { return f.Fs.Hashes() } -// Purge all files in the root and the root directory -func (f *Fs) Purge(ctx context.Context) error { - fs.Infof(f, "purging cache") - f.cache.Purge() +// Purge all files in the directory +func (f *Fs) Purge(ctx context.Context, dir string) error { + if dir == "" { + // FIXME this isn't quite right as it should purge the dir prefix + fs.Infof(f, "purging cache") + f.cache.Purge() + } do := f.Fs.Features().Purge if do == nil { - return nil + return fs.ErrorCantPurge } - err := do(ctx) + err := do(ctx, dir) if err != nil { return err } diff --git a/backend/cache/cache_internal_test.go b/backend/cache/cache_internal_test.go index be2cbfcba..becbfd92b 100644 --- a/backend/cache/cache_internal_test.go +++ b/backend/cache/cache_internal_test.go @@ -946,7 +946,7 @@ func (r *run) newCacheFs(t *testing.T, remote, id string, needRemote, purge bool } if purge { - _ = f.Features().Purge(context.Background()) + _ = f.Features().Purge(context.Background(), "") require.NoError(t, err) } err = f.Mkdir(context.Background(), "") @@ -955,7 +955,7 @@ func (r *run) newCacheFs(t *testing.T, remote, id string, needRemote, purge bool } func (r *run) cleanupFs(t *testing.T, f fs.Fs, b *cache.Persistent) { - err := f.Features().Purge(context.Background()) + err := f.Features().Purge(context.Background(), "") require.NoError(t, err) cfs, err := r.getCacheFs(f) require.NoError(t, err) diff --git a/backend/chunker/chunker.go b/backend/chunker/chunker.go index 42c9cf5b4..74bebacef 100644 --- a/backend/chunker/chunker.go +++ b/backend/chunker/chunker.go @@ -1333,7 +1333,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return f.base.Rmdir(ctx, dir) } -// Purge all files in the root and the root directory +// Purge all files in the directory // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() @@ -1344,12 +1344,12 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { // As a result it removes not only composite chunker files with their // active chunks but also all hidden temporary chunks in the directory. // -func (f *Fs) Purge(ctx context.Context) error { +func (f *Fs) Purge(ctx context.Context, dir string) error { do := f.base.Features().Purge if do == nil { return fs.ErrorCantPurge } - return do(ctx) + return do(ctx, dir) } // Remove an object (chunks and metadata, if any) diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go index 41c3a1925..831af4261 100644 --- a/backend/crypt/crypt.go +++ b/backend/crypt/crypt.go @@ -427,18 +427,18 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return f.Fs.Rmdir(ctx, f.cipher.EncryptDirName(dir)) } -// Purge all files in the root and the root directory +// Purge all files in the directory specified // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() // // Return an error if it doesn't exist -func (f *Fs) Purge(ctx context.Context) error { +func (f *Fs) Purge(ctx context.Context, dir string) error { do := f.Fs.Features().Purge if do == nil { return fs.ErrorCantPurge } - return do(ctx) + return do(ctx, dir) } // Copy src to this remote using server side copy operations. diff --git a/backend/drive/drive.go b/backend/drive/drive.go index bcc2d8d18..2ef55f0fe 100755 --- a/backend/drive/drive.go +++ b/backend/drive/drive.go @@ -2208,10 +2208,9 @@ func (f *Fs) delete(ctx context.Context, id string, useTrash bool) error { }) } -// Rmdir deletes a directory -// -// Returns an error if it isn't empty -func (f *Fs) Rmdir(ctx context.Context, dir string) error { +// purgeCheck removes the dir directory, if check is set then it +// refuses to do so if it has anything in +func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error { root := path.Join(f.root, dir) dc := f.dirCache directoryID, err := dc.FindDir(ctx, dir, false) @@ -2224,20 +2223,22 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return f.delete(ctx, shortcutID, f.opt.UseTrash) } var trashedFiles = false - found, err := f.list(ctx, []string{directoryID}, "", false, false, true, func(item *drive.File) bool { - if !item.Trashed { - fs.Debugf(dir, "Rmdir: contains file: %q", item.Name) - return true + if check { + found, err := f.list(ctx, []string{directoryID}, "", false, false, true, func(item *drive.File) bool { + if !item.Trashed { + fs.Debugf(dir, "Rmdir: contains file: %q", item.Name) + return true + } + fs.Debugf(dir, "Rmdir: contains trashed file: %q", item.Name) + trashedFiles = true + return false + }) + if err != nil { + return err + } + if found { + return errors.Errorf("directory not empty") } - fs.Debugf(dir, "Rmdir: contains trashed file: %q", item.Name) - trashedFiles = true - return false - }) - if err != nil { - return err - } - if found { - return errors.Errorf("directory not empty") } if root != "" { // trash the directory if it had trashed files @@ -2247,6 +2248,8 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { if err != nil { return err } + } else if check { + return errors.New("can't purge root directory") } f.dirCache.FlushDir(dir) if err != nil { @@ -2255,6 +2258,13 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return nil } +// Rmdir deletes a directory +// +// Returns an error if it isn't empty +func (f *Fs) Rmdir(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, true) +} + // Precision of the object storage system func (f *Fs) Precision() time.Duration { return time.Millisecond @@ -2348,23 +2358,11 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - if f.root == "" { - return errors.New("can't purge root directory") - } +func (f *Fs) Purge(ctx context.Context, dir string) error { if f.opt.TrashedOnly { return errors.New("Can't purge with --drive-trashed-only. Use delete if you want to selectively delete files") } - rootID, err := f.dirCache.RootID(ctx, false) - if err != nil { - return err - } - err = f.delete(ctx, shortcutID(rootID), f.opt.UseTrash) - f.dirCache.ResetRoot() - if err != nil { - return err - } - return nil + return f.purgeCheck(ctx, dir, false) } // CleanUp empties the trash diff --git a/backend/dropbox/dropbox.go b/backend/dropbox/dropbox.go index 02c7af30c..ad740bf2f 100755 --- a/backend/dropbox/dropbox.go +++ b/backend/dropbox/dropbox.go @@ -611,10 +611,9 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error { return err } -// Rmdir deletes the container -// -// Returns an error if it isn't empty -func (f *Fs) Rmdir(ctx context.Context, dir string) error { +// purgeCheck removes the root directory, if check is set then it +// refuses to do so if it has anything in +func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) (err error) { root := path.Join(f.slashRoot, dir) // can't remove root @@ -622,31 +621,33 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return errors.New("can't remove root directory") } - // check directory exists - _, err := f.getDirMetadata(root) - if err != nil { - return errors.Wrap(err, "Rmdir") - } + if check { + // check directory exists + _, err = f.getDirMetadata(root) + if err != nil { + return errors.Wrap(err, "Rmdir") + } - root = f.opt.Enc.FromStandardPath(root) - // check directory empty - arg := files.ListFolderArg{ - Path: root, - Recursive: false, - } - if root == "/" { - arg.Path = "" // Specify root folder as empty string - } - var res *files.ListFolderResult - err = f.pacer.Call(func() (bool, error) { - res, err = f.srv.ListFolder(&arg) - return shouldRetry(err) - }) - if err != nil { - return errors.Wrap(err, "Rmdir") - } - if len(res.Entries) != 0 { - return errors.New("directory not empty") + root = f.opt.Enc.FromStandardPath(root) + // check directory empty + arg := files.ListFolderArg{ + Path: root, + Recursive: false, + } + if root == "/" { + arg.Path = "" // Specify root folder as empty string + } + var res *files.ListFolderResult + err = f.pacer.Call(func() (bool, error) { + res, err = f.srv.ListFolder(&arg) + return shouldRetry(err) + }) + if err != nil { + return errors.Wrap(err, "Rmdir") + } + if len(res.Entries) != 0 { + return errors.New("directory not empty") + } } // remove it @@ -657,6 +658,13 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return err } +// Rmdir deletes the container +// +// Returns an error if it isn't empty +func (f *Fs) Rmdir(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, true) +} + // Precision returns the precision func (f *Fs) Precision() time.Duration { return time.Second @@ -719,15 +727,8 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) (err error) { - // Let dropbox delete the filesystem tree - err = f.pacer.Call(func() (bool, error) { - _, err = f.srv.DeleteV2(&files.DeleteArg{ - Path: f.opt.Enc.FromStandardPath(f.slashRoot), - }) - return shouldRetry(err) - }) - return err +func (f *Fs) Purge(ctx context.Context, dir string) (err error) { + return f.purgeCheck(ctx, dir, false) } // Move src to this remote using server side move operations. diff --git a/backend/jottacloud/jottacloud.go b/backend/jottacloud/jottacloud.go index 9c7958858..10eb955e4 100644 --- a/backend/jottacloud/jottacloud.go +++ b/backend/jottacloud/jottacloud.go @@ -1070,8 +1070,8 @@ func (f *Fs) Precision() time.Duration { } // Purge deletes all the files and the container -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // copyOrMoves copies or moves directories or files depending on the method parameter diff --git a/backend/local/local.go b/backend/local/local.go index 85210ddbb..90aad3c96 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -616,20 +616,21 @@ func (f *Fs) readPrecision() (precision time.Duration) { return } -// Purge deletes all the files and directories +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - fi, err := f.lstat(f.root) +func (f *Fs) Purge(ctx context.Context, dir string) error { + dir = f.localPath(dir) + fi, err := f.lstat(dir) if err != nil { return err } if !fi.Mode().IsDir() { - return errors.Errorf("can't purge non directory: %q", f.root) + return errors.Errorf("can't purge non directory: %q", dir) } - return os.RemoveAll(f.root) + return os.RemoveAll(dir) } // Move src to this remote using server side move operations. diff --git a/backend/mailru/mailru.go b/backend/mailru/mailru.go index b1a61b76e..316e39558 100644 --- a/backend/mailru/mailru.go +++ b/backend/mailru/mailru.go @@ -1162,12 +1162,12 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return f.purgeWithCheck(ctx, dir, true, "rmdir") } -// Purge deletes all the files and the root directory +// Purge deletes all the files in the directory // Optional interface: Only implement this if you have a way of deleting // all the files quicker than just running Remove() on the result of List() -func (f *Fs) Purge(ctx context.Context) error { +func (f *Fs) Purge(ctx context.Context, dir string) error { // fs.Debugf(f, ">>> Purge") - return f.purgeWithCheck(ctx, "", false, "purge") + return f.purgeWithCheck(ctx, dir, false, "purge") } // purgeWithCheck() removes the root directory. diff --git a/backend/mega/mega.go b/backend/mega/mega.go index efb23ff9d..bcd007aa2 100644 --- a/backend/mega/mega.go +++ b/backend/mega/mega.go @@ -669,13 +669,13 @@ func (f *Fs) Precision() time.Duration { return fs.ModTimeNotSupported } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck("", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(dir, false) } // move a file or folder (srcFs, srcRemote, info) to (f, dstRemote) diff --git a/backend/onedrive/onedrive.go b/backend/onedrive/onedrive.go index a41766bfe..ea4007b79 100755 --- a/backend/onedrive/onedrive.go +++ b/backend/onedrive/onedrive.go @@ -1073,13 +1073,13 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, return dstObj, nil } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // Move src to this remote using server side move operations. diff --git a/backend/opendrive/opendrive.go b/backend/opendrive/opendrive.go index 35f01b8be..7ba80a00f 100644 --- a/backend/opendrive/opendrive.go +++ b/backend/opendrive/opendrive.go @@ -506,13 +506,13 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string return nil } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // Return an Object from a path diff --git a/backend/pcloud/pcloud.go b/backend/pcloud/pcloud.go index 465b35ea6..b020c1144 100644 --- a/backend/pcloud/pcloud.go +++ b/backend/pcloud/pcloud.go @@ -671,13 +671,13 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, return dstObj, nil } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // CleanUp empties the trash diff --git a/backend/premiumizeme/premiumizeme.go b/backend/premiumizeme/premiumizeme.go index 1d9b580e2..3fb0270cd 100644 --- a/backend/premiumizeme/premiumizeme.go +++ b/backend/premiumizeme/premiumizeme.go @@ -609,13 +609,13 @@ func (f *Fs) Precision() time.Duration { return fs.ModTimeNotSupported } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // move a file or folder diff --git a/backend/putio/fs.go b/backend/putio/fs.go index 42b751ccc..c6f3c0e9e 100644 --- a/backend/putio/fs.go +++ b/backend/putio/fs.go @@ -458,10 +458,9 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) { return err } -// Rmdir deletes the container -// -// Returns an error if it isn't empty -func (f *Fs) Rmdir(ctx context.Context, dir string) (err error) { +// purgeCheck removes the root directory, if check is set then it +// refuses to do so if it has anything in +func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) (err error) { // defer log.Trace(f, "dir=%v", dir)("err=%v", &err) root := strings.Trim(path.Join(f.root, dir), "/") @@ -478,18 +477,20 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) (err error) { } dirID := atoi(directoryID) - // check directory empty - var children []putio.File - err = f.pacer.Call(func() (bool, error) { - // fs.Debugf(f, "listing files: %d", dirID) - children, _, err = f.client.Files.List(ctx, dirID) - return shouldRetry(err) - }) - if err != nil { - return errors.Wrap(err, "Rmdir") - } - if len(children) != 0 { - return errors.New("directory not empty") + if check { + // check directory empty + var children []putio.File + err = f.pacer.Call(func() (bool, error) { + // fs.Debugf(f, "listing files: %d", dirID) + children, _, err = f.client.Files.List(ctx, dirID) + return shouldRetry(err) + }) + if err != nil { + return errors.Wrap(err, "Rmdir") + } + if len(children) != 0 { + return errors.New("directory not empty") + } } // remove it @@ -502,35 +503,26 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) (err error) { return err } +// Rmdir deletes the container +// +// Returns an error if it isn't empty +func (f *Fs) Rmdir(ctx context.Context, dir string) (err error) { + return f.purgeCheck(ctx, dir, true) +} + // Precision returns the precision func (f *Fs) Precision() time.Duration { return time.Second } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) (err error) { +func (f *Fs) Purge(ctx context.Context, dir string) (err error) { // defer log.Trace(f, "")("err=%v", &err) - - if f.root == "" { - return errors.New("can't purge root directory") - } - rootIDs, err := f.dirCache.RootID(ctx, false) - if err != nil { - return err - } - rootID := atoi(rootIDs) - // Let putio delete the filesystem tree - err = f.pacer.Call(func() (bool, error) { - // fs.Debugf(f, "deleting file: %d", rootID) - err = f.client.Files.Delete(ctx, rootID) - return shouldRetry(err) - }) - f.dirCache.ResetRoot() - return err + return f.purgeCheck(ctx, dir, false) } // Copy src to this remote using server side copy operations. diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go index 544e72a4e..7eb36080d 100644 --- a/backend/seafile/seafile.go +++ b/backend/seafile/seafile.go @@ -584,29 +584,38 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error { return nil } -// Rmdir removes the directory or library if empty -// -// Return an error if it doesn't exist or isn't empty -func (f *Fs) Rmdir(ctx context.Context, dir string) error { +// purgeCheck removes the root directory, if check is set then it +// refuses to do so if it has anything in +func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error { libraryName, dirPath := f.splitPath(dir) libraryID, err := f.getLibraryID(ctx, libraryName) if err != nil { return err } - directoryEntries, err := f.getDirectoryEntries(ctx, libraryID, dirPath, false) - if err != nil { - return err - } - if len(directoryEntries) > 0 { - return fs.ErrorDirectoryNotEmpty + if check { + directoryEntries, err := f.getDirectoryEntries(ctx, libraryID, dirPath, false) + if err != nil { + return err + } + if len(directoryEntries) > 0 { + return fs.ErrorDirectoryNotEmpty + } } + if dirPath == "" || dirPath == "/" { return f.deleteLibrary(ctx, libraryID) } return f.deleteDir(ctx, libraryID, dirPath) } +// Rmdir removes the directory or library if empty +// +// Return an error if it doesn't exist or isn't empty +func (f *Fs) Rmdir(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, true) +} + // ==================== Optional Interface fs.ListRer ==================== // ListR lists the objects and directories of the Fs starting @@ -893,33 +902,14 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string // ==================== Optional Interface fs.Purger ==================== -// Purge all files in the root and the root directory +// Purge all files in the directory // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() // // Return an error if it doesn't exist -func (f *Fs) Purge(ctx context.Context) error { - if f.libraryName == "" { - return errors.New("Cannot delete from the root of the server. Please select a library") - } - libraryID, err := f.getLibraryID(ctx, f.libraryName) - if err != nil { - return err - } - if f.rootDirectory == "" { - // Delete library - err = f.deleteLibrary(ctx, libraryID) - if err != nil { - return err - } - return nil - } - err = f.deleteDir(ctx, libraryID, f.rootDirectory) - if err != nil { - return err - } - return nil +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // ==================== Optional Interface fs.CleanUpper ==================== diff --git a/backend/sharefile/sharefile.go b/backend/sharefile/sharefile.go index 7741f34eb..ee392f490 100644 --- a/backend/sharefile/sharefile.go +++ b/backend/sharefile/sharefile.go @@ -853,8 +853,8 @@ func (f *Fs) Precision() time.Duration { // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // updateItem patches a file or folder diff --git a/backend/sugarsync/sugarsync.go b/backend/sugarsync/sugarsync.go index 235c381c1..b4a71553e 100644 --- a/backend/sugarsync/sugarsync.go +++ b/backend/sugarsync/sugarsync.go @@ -895,12 +895,12 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, return dstObj, nil } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { +func (f *Fs) Purge(ctx context.Context, dir string) error { // Caution: Deleting a folder may orphan objects. It's important // to remove the contents of the folder before you delete the // folder. That's because removing a folder using DELETE does not @@ -920,7 +920,7 @@ func (f *Fs) Purge(ctx context.Context) error { if f.opt.HardDelete { return fs.ErrorCantPurge } - return f.purgeCheck(ctx, "", false) + return f.purgeCheck(ctx, dir, false) } // moveFile moves a file server side diff --git a/backend/swift/swift.go b/backend/swift/swift.go index 127ea0402..761b8f9c8 100644 --- a/backend/swift/swift.go +++ b/backend/swift/swift.go @@ -840,17 +840,21 @@ func (f *Fs) Precision() time.Duration { return time.Nanosecond } -// Purge deletes all the files and directories +// Purge deletes all the files in the directory // // Implemented here so we can make sure we delete directory markers -func (f *Fs) Purge(ctx context.Context) error { +func (f *Fs) Purge(ctx context.Context, dir string) error { + container, directory := f.split(dir) + if container == "" { + return fs.ErrorListBucketRequired + } // Delete all the files including the directory markers toBeDeleted := make(chan fs.Object, fs.Config.Transfers) delErr := make(chan error, 1) go func() { delErr <- operations.DeleteFiles(ctx, toBeDeleted) }() - err := f.list(f.rootContainer, f.rootDirectory, f.rootDirectory, f.rootContainer == "", true, true, func(entry fs.DirEntry) error { + err := f.list(container, directory, f.rootDirectory, false, true, true, func(entry fs.DirEntry) error { if o, ok := entry.(*Object); ok { toBeDeleted <- o } @@ -864,7 +868,7 @@ func (f *Fs) Purge(ctx context.Context) error { if err != nil { return err } - return f.Rmdir(ctx, "") + return f.Rmdir(ctx, dir) } // Copy src to this remote using server side copy operations. diff --git a/backend/union/union.go b/backend/union/union.go index 3ce07d756..a803a7e51 100644 --- a/backend/union/union.go +++ b/backend/union/union.go @@ -162,13 +162,13 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error { return errs.Err() } -// Purge all files in the root and the root directory +// Purge all files in the directory // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() // // Return an error if it doesn't exist -func (f *Fs) Purge(ctx context.Context) error { +func (f *Fs) Purge(ctx context.Context, dir string) error { for _, r := range f.upstreams { if r.Features().Purge == nil { return fs.ErrorCantPurge @@ -180,7 +180,7 @@ func (f *Fs) Purge(ctx context.Context) error { } errs := Errors(make([]error, len(upstreams))) multithread(len(upstreams), func(i int) { - err := upstreams[i].Features().Purge(ctx) + err := upstreams[i].Features().Purge(ctx, dir) errs[i] = errors.Wrap(err, upstreams[i].Name()) }) return errs.Err() diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go index 8ac1a47fb..ebbf2befe 100644 --- a/backend/webdav/webdav.go +++ b/backend/webdav/webdav.go @@ -899,13 +899,13 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, return f.copyOrMove(ctx, src, remote, "COPY") } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // Move src to this remote using server side move operations. diff --git a/backend/yandex/yandex.go b/backend/yandex/yandex.go index 570f3891d..04d97f975 100644 --- a/backend/yandex/yandex.go +++ b/backend/yandex/yandex.go @@ -637,13 +637,13 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error { return f.purgeCheck(ctx, dir, true) } -// Purge deletes all the files and the container +// Purge deletes all the files in the directory // // Optional interface: Only implement this if you have a way of // deleting all the files quicker than just running Remove() on the // result of List() -func (f *Fs) Purge(ctx context.Context) error { - return f.purgeCheck(ctx, "", false) +func (f *Fs) Purge(ctx context.Context, dir string) error { + return f.purgeCheck(ctx, dir, false) } // copyOrMoves copies or moves directories or files depending on the method parameter diff --git a/fs/fs.go b/fs/fs.go index 0348f1296..c564ec809 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -518,13 +518,13 @@ type Features struct { SlowModTime bool // if calling ModTime() generally takes an extra transaction SlowHash bool // if calling Hash() generally takes an extra transaction - // Purge all files in the root and the root directory + // Purge all files in the directory specified // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() // // Return an error if it doesn't exist - Purge func(ctx context.Context) error + Purge func(ctx context.Context, dir string) error // Copy src to this remote using server side copy operations. // @@ -883,13 +883,13 @@ func (ft *Features) WrapsFs(f Fs, w Fs) *Features { // Purger is an optional interfaces for Fs type Purger interface { - // Purge all files in the root and the root directory + // Purge all files in the directory specified // // Implement this if you have a way of deleting all the files // quicker than just running Remove() on the result of List() // // Return an error if it doesn't exist - Purge(ctx context.Context) error + Purge(ctx context.Context, dir string) error } // Copier is an optional interface for Fs diff --git a/fs/fs_test.go b/fs/fs_test.go index d9e2788e6..981b74d0c 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -49,7 +49,7 @@ func TestFeaturesList(t *testing.T) { func TestFeaturesEnabled(t *testing.T) { ft := new(Features) ft.CaseInsensitive = true - ft.Purge = func(ctx context.Context) error { return nil } + ft.Purge = func(ctx context.Context, dir string) error { return nil } enabled := ft.Enabled() flag, ok := enabled["CaseInsensitive"] diff --git a/fs/operations/operations.go b/fs/operations/operations.go index 2d3a65108..915a15c4f 100644 --- a/fs/operations/operations.go +++ b/fs/operations/operations.go @@ -921,20 +921,16 @@ func Rmdir(ctx context.Context, f fs.Fs, dir string) error { } // Purge removes a directory and all of its contents -func Purge(ctx context.Context, f fs.Fs, dir string) error { +func Purge(ctx context.Context, f fs.Fs, dir string) (err error) { doFallbackPurge := true - var err error - if dir == "" { - // FIXME change the Purge interface so it takes a dir - see #1891 - if doPurge := f.Features().Purge; doPurge != nil { - doFallbackPurge = false - if SkipDestructive(ctx, fs.LogDirName(f, dir), "purge directory") { - return nil - } - err = doPurge(ctx) - if err == fs.ErrorCantPurge { - doFallbackPurge = true - } + if doPurge := f.Features().Purge; doPurge != nil { + doFallbackPurge = false + if SkipDestructive(ctx, fs.LogDirName(f, dir), "purge directory") { + return nil + } + err = doPurge(ctx, dir) + if err == fs.ErrorCantPurge { + doFallbackPurge = true } } if doFallbackPurge { diff --git a/fstest/fstest.go b/fstest/fstest.go index b253abefe..8b30c3c96 100644 --- a/fstest/fstest.go +++ b/fstest/fstest.go @@ -482,7 +482,7 @@ func Purge(f fs.Fs) { if doPurge := f.Features().Purge; doPurge != nil { doFallbackPurge = false fs.Debugf(f, "Purge remote") - err = doPurge(ctx) + err = doPurge(ctx, "") if err == fs.ErrorCantPurge { doFallbackPurge = true } diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go index 5e2de4eca..857686853 100644 --- a/fstest/fstests/fstests.go +++ b/fstest/fstests/fstests.go @@ -974,6 +974,43 @@ func Run(t *testing.T, opt *Opt) { assert.NotNil(t, err) }) + // TestFsPurge tests Purge + t.Run("FsPurge", func(t *testing.T) { + skipIfNotOk(t) + + // Check have Purge + doPurge := remote.Features().Purge + if doPurge == nil { + t.Skip("FS has no Purge interface") + } + + // put up a file to purge + fileToPurge := fstest.Item{ + ModTime: fstest.Time("2001-02-03T04:05:06.499999999Z"), + Path: "dirToPurge/fileToPurge.txt", + } + _, _ = testPut(ctx, t, remote, &fileToPurge) + + fstest.CheckListingWithPrecision(t, remote, []fstest.Item{file1, file2, fileToPurge}, []string{ + "dirToPurge", + "hello? sausage", + "hello? sausage/êé", + "hello? sausage/êé/Hello, 世界", + "hello? sausage/êé/Hello, 世界/ \" ' @ < > & ? + ≠", + }, fs.GetModifyWindow(remote)) + + // Now purge it + err = operations.Purge(ctx, remote, "dirToPurge") + require.NoError(t, err) + + fstest.CheckListingWithPrecision(t, remote, []fstest.Item{file1, file2}, []string{ + "hello? sausage", + "hello? sausage/êé", + "hello? sausage/êé/Hello, 世界", + "hello? sausage/êé/Hello, 世界/ \" ' @ < > & ? + ≠", + }, fs.GetModifyWindow(remote)) + }) + // TestFsCopy tests Copy t.Run("FsCopy", func(t *testing.T) { skipIfNotOk(t)