drive: Fix Rmdir on directories with trashed files - fixes #1040

When we try to delete a directory which is empty other than with
trashed files, we trash the directory rather than deleting it.
This commit is contained in:
Nick Craig-Wood 2017-02-16 12:29:37 +00:00
parent dac4bb22d3
commit 33c2873ae9

View File

@ -203,25 +203,31 @@ type listAllFn func(*drive.File) bool
// If the user fn ever returns true then it early exits with found = true // If the user fn ever returns true then it early exits with found = true
// //
// Search params: https://developers.google.com/drive/search-parameters // Search params: https://developers.google.com/drive/search-parameters
func (f *Fs) listAll(dirID string, title string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) { func (f *Fs) listAll(dirID string, title string, directoriesOnly bool, filesOnly bool, includeTrashed bool, fn listAllFn) (found bool, err error) {
query := fmt.Sprintf("trashed=false") var query []string
if !includeTrashed {
query = append(query, "trashed=false")
}
if dirID != "" { if dirID != "" {
query += fmt.Sprintf(" and '%s' in parents", dirID) query = append(query, fmt.Sprintf("'%s' in parents", dirID))
} }
if title != "" { if title != "" {
// Escaping the backslash isn't documented but seems to work // Escaping the backslash isn't documented but seems to work
title = strings.Replace(title, `\`, `\\`, -1) title = strings.Replace(title, `\`, `\\`, -1)
title = strings.Replace(title, `'`, `\'`, -1) title = strings.Replace(title, `'`, `\'`, -1)
query += fmt.Sprintf(" and title='%s'", title) query = append(query, fmt.Sprintf("title='%s'", title))
} }
if directoriesOnly { if directoriesOnly {
query += fmt.Sprintf(" and mimeType='%s'", driveFolderType) query = append(query, fmt.Sprintf("mimeType='%s'", driveFolderType))
} }
if filesOnly { if filesOnly {
query += fmt.Sprintf(" and mimeType!='%s'", driveFolderType) query = append(query, fmt.Sprintf("mimeType!='%s'", driveFolderType))
} }
// fmt.Printf("listAll Query = %q\n", query) // fmt.Printf("listAll Query = %q\n", query)
list := f.svc.Files.List().Q(query) list := f.svc.Files.List()
if len(query) > 0 {
list = list.Q(strings.Join(query, " and "))
}
if *driveListChunk > 0 { if *driveListChunk > 0 {
list = list.MaxResults(*driveListChunk) list = list.MaxResults(*driveListChunk)
} }
@ -390,7 +396,7 @@ func (f *Fs) NewObject(remote string) (fs.Object, error) {
// FindLeaf finds a directory of name leaf in the folder with ID pathID // FindLeaf finds a directory of name leaf in the folder with ID pathID
func (f *Fs) FindLeaf(pathID, leaf string) (pathIDOut string, found bool, err error) { func (f *Fs) FindLeaf(pathID, leaf string) (pathIDOut string, found bool, err error) {
// Find the leaf in pathID // Find the leaf in pathID
found, err = f.listAll(pathID, leaf, true, false, func(item *drive.File) bool { found, err = f.listAll(pathID, leaf, true, false, false, func(item *drive.File) bool {
if item.Title == leaf { if item.Title == leaf {
pathIDOut = item.Id pathIDOut = item.Id
return true return true
@ -459,7 +465,7 @@ func (f *Fs) findExportFormat(filepath string, item *drive.File) (extension, lin
// ListDir reads the directory specified by the job into out, returning any more jobs // ListDir reads the directory specified by the job into out, returning any more jobs
func (f *Fs) ListDir(out fs.ListOpts, job dircache.ListDirJob) (jobs []dircache.ListDirJob, err error) { func (f *Fs) ListDir(out fs.ListOpts, job dircache.ListDirJob) (jobs []dircache.ListDirJob, err error) {
fs.Debugf(f, "Reading %q", job.Path) fs.Debugf(f, "Reading %q", job.Path)
_, err = f.listAll(job.DirID, "", false, false, func(item *drive.File) bool { _, err = f.listAll(job.DirID, "", false, false, false, func(item *drive.File) bool {
remote := job.Path + item.Title remote := job.Path + item.Title
switch { switch {
case *driveAuthOwnerOnly && !isAuthOwned(item): case *driveAuthOwnerOnly && !isAuthOwned(item):
@ -620,34 +626,42 @@ func (f *Fs) Mkdir(dir string) error {
return err return err
} }
// Rmdir deletes the container // Rmdir deletes a directory
// //
// Returns an error if it isn't empty // Returns an error if it isn't empty
func (f *Fs) Rmdir(dir string) error { func (f *Fs) Rmdir(dir string) error {
root := path.Join(f.root, dir) root := path.Join(f.root, dir)
dc := f.dirCache dc := f.dirCache
rootID, err := dc.FindDir(dir, false) directoryID, err := dc.FindDir(dir, false)
if err != nil { if err != nil {
return err return err
} }
var children *drive.ChildList var trashedFiles = false
err = f.pacer.Call(func() (bool, error) { found, err := f.listAll(directoryID, "", false, false, true, func(item *drive.File) bool {
children, err = f.svc.Children.List(rootID).MaxResults(10).Do() if item.Labels == nil || !item.Labels.Trashed {
return shouldRetry(err) fs.Debugf(dir, "Rmdir: contains file: %q", item.Title)
return true
}
fs.Debugf(dir, "Rmdir: contains trashed file: %q", item.Title)
trashedFiles = true
return false
}) })
if err != nil { if err != nil {
return err return err
} }
if len(children.Items) > 0 { if found {
return errors.Errorf("directory not empty: %#v", children.Items) return errors.Errorf("directory not empty")
} }
// Delete the directory if it isn't the root // Delete the directory if it isn't the root
if root != "" { if root != "" {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
if *driveUseTrash { // trash the directory if it had trashed files
_, err = f.svc.Files.Trash(rootID).Do() // in or the user wants to trash, otherwise
// delete it.
if trashedFiles || *driveUseTrash {
_, err = f.svc.Files.Trash(directoryID).Do()
} else { } else {
err = f.svc.Files.Delete(rootID).Do() err = f.svc.Files.Delete(directoryID).Do()
} }
return shouldRetry(err) return shouldRetry(err)
}) })
@ -905,7 +919,7 @@ func (o *Object) readMetaData() (err error) {
return err return err
} }
found, err := o.fs.listAll(directoryID, leaf, false, true, func(item *drive.File) bool { found, err := o.fs.listAll(directoryID, leaf, false, true, false, func(item *drive.File) bool {
if item.Title == leaf { if item.Title == leaf {
o.setMetaData(item) o.setMetaData(item)
return true return true