acd: add support for server side DirMove #122

This commit is contained in:
Stefan Breunig 2016-11-07 21:38:57 +01:00 committed by Nick Craig-Wood
parent 2243b065e8
commit fb1458815a

View file

@ -13,6 +13,7 @@ we ignore assets completely!
*/ */
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -630,96 +631,60 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
return nil, fs.ErrorCantMove return nil, fs.ErrorCantMove
} }
fs.Debug(src, "Attempting to move to %q", remote) fs.Debug(src, "Attempting to move to %q", remote)
return srcObj.move(remote, false)
}
// Temporary Object under construction // DirMove moves src directory to this remote using server side move
dstObj := &Object{ // operations.
fs: f, //
remote: remote, // Will only be called if src.Fs().Name() == f.Name()
} //
// check if there's already a directory with the same name // If it isn't possible then return fs.ErrorCantDirMove
pathID, err := f.dirCache.FindDir(remote, false) //
if err == nil || pathID != "" { // If destination exists then return fs.ErrorDirExists
fs.Debug(src, "Can't move - there is already a folder with the same name in the target location") func (f *Fs) DirMove(src fs.Fs) (err error) {
return nil, fs.ErrorCantMove fs.Debug(src, "trying to move to %q", f)
}
// Check if there's already a file with the same name srcFs, ok := src.(*Fs)
err = dstObj.readMetaData() if !ok {
switch err { fs.Debug(src, "Can't move - not same remote type")
case nil: return fs.ErrorCantDirMove
fs.Debug(src, "Can't move - there is already a file with the same name in the target location")
return nil, fs.ErrorCantMove
case fs.ErrorObjectNotFound:
// Not found so we can move it there
default:
return nil, err
} }
sLeaf, sDirectoryID, err := f.dirCache.FindPath(srcObj.Remote(), false) // Move does not care about this "no op" and will do nothing. DirMove
if err != nil { // requires this case to return an error though.
return nil, err if srcFs.root == f.root {
} return fs.ErrorDirExists
tLeaf, tDirectoryID, err := f.dirCache.FindPath(remote, true)
if err != nil {
return nil, err
} }
if len(srcObj.info.Parents) > 1 && sLeaf != tLeaf { err = srcFs.dirCache.FindRoot(false)
fs.Debug(src, "Can't move - object is attached to multiple parents and should be renamed. This would change the name of the node in all parents.") if err != nil {
return nil, fs.ErrorCantMove return
}
node := acd.NodeFromId(srcFs.dirCache.RootID(), f.c.Nodes)
var jsonStr string
err = f.pacer.Call(func() (bool, error) {
jsonStr, err = node.GetMetadata()
return f.shouldRetry(nil, err)
})
if err != nil {
return
}
err = json.Unmarshal([]byte(jsonStr), &node)
if err != nil {
return
} }
if sLeaf != tLeaf { srcObj := &Object{
// fs.Debug(src, "renaming") fs: srcFs,
err = srcObj.rename(tLeaf) remote: srcFs.root,
if err != nil { info: node,
fs.Debug(src, "Move: quick path rename failed: %v", err)
goto OnConflict
}
} }
if sDirectoryID != tDirectoryID {
// fs.Debug(src, "moving")
err = srcObj.replaceParent(sDirectoryID, tDirectoryID)
if err != nil {
fs.Debug(src, "Move: quick path parent replace failed: %v", err)
return nil, err
}
}
f.dirCache.Flush()
return dstObj, nil
OnConflict: _, err = srcObj.move(f.root, true)
fs.Debug(src, "Could not directly rename file, presumably because there was a file with the same name already. Instead, the file will now be trashed where such operations do not cause errors. It will be restored to the correct parent after. If any of the subsequent calls fails, the rename/move will be in an invalid state.") srcFs.dirCache.ResetRoot()
return
// fs.Debug(src, "Trashing file")
err = srcObj.Remove()
if err != nil {
return nil, err
}
// fs.Debug(src, "Renaming file")
err = srcObj.rename(tLeaf)
if err != nil {
return nil, err
}
// note: replacing parent is forbidden by API, modifying them individually is
// okay though
// fs.Debug(src, "Adding target parent")
err = srcObj.addParent(tDirectoryID)
if err != nil {
return nil, err
}
// fs.Debug(src, "removing original parent")
err = srcObj.removeParent(sDirectoryID)
if err != nil {
return nil, err
}
// fs.Debug(src, "Restoring")
err = srcObj.restore()
if err != nil {
return nil, err
} else {
f.dirCache.Flush()
return dstObj, nil
}
} }
// purgeCheck remotes the root directory, if check is set then it // purgeCheck remotes the root directory, if check is set then it
@ -793,51 +758,6 @@ func (f *Fs) Hashes() fs.HashSet {
return fs.HashSet(fs.HashMD5) return fs.HashSet(fs.HashMD5)
} }
func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
srcObj, ok := src.(*Object)
if !ok {
fs.Debug(src, "Can't move - not same remote type")
return nil, fs.ErrorCantMove
}
// Temporary Object under construction
dstObj := &Object{
fs: f,
remote: remote,
}
var err error
_, directoryID, err := f.dirCache.FindPath(remote, false)
if err != nil {
return nil, err
}
var info *acd.Node
var resp *http.Response
if directoryID == srcObj.info.Parents[0] {
// Do the rename
err = f.pacer.Call(func() (bool, error) {
info, resp, err = srcObj.info.Rename(remote)
return srcObj.fs.shouldRetry(resp, err)
})
if err != nil {
return nil, err
}
} else {
// Do the move
err = f.pacer.Call(func() (bool, error) {
info, resp, err = srcObj.info.Move(directoryID)
return srcObj.fs.shouldRetry(resp, err)
})
if err != nil {
return nil, err
}
}
dstObj.info = info
return dstObj, nil
}
// Copy src to this remote using server side copy operations. // Copy src to this remote using server side copy operations.
// //
// This is stored with the remote path given // This is stored with the remote path given
@ -1035,7 +955,7 @@ func (o *Object) Remove() error {
// Restore an object // Restore an object
func (o *Object) restore() error { func (o *Object) restore() error {
var info *acd.Node var info *acd.Node
var resp *http.Response var resp *http.Response
var err error var err error
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
info, resp, err = o.info.Restore() info, resp, err = o.info.Restore()
@ -1064,32 +984,129 @@ func (o *Object) rename(newName string) error {
// Replaces one parent with another, effectively moving the file. Leaves other // Replaces one parent with another, effectively moving the file. Leaves other
// parents untouched. ReplaceParent cannot be used when the file is trashed. // parents untouched. ReplaceParent cannot be used when the file is trashed.
func (o *Object) replaceParent(oldParentId string, newParentId string) error { func (o *Object) replaceParent(oldParentID string, newParentID string) error {
fs.Debug(o, "trying parent replace: %s -> %s", oldParentId, newParentId) fs.Debug(o, "trying parent replace: %s -> %s", oldParentID, newParentID)
return o.fs.pacer.Call(func() (bool, error) { return o.fs.pacer.Call(func() (bool, error) {
resp, err := o.info.ReplaceParent(oldParentId, newParentId) resp, err := o.info.ReplaceParent(oldParentID, newParentID)
return o.fs.shouldRetry(resp, err) return o.fs.shouldRetry(resp, err)
}) })
} }
// Adds one additional parent to object. // Adds one additional parent to object.
func (o *Object) addParent(newParentId string) error { func (o *Object) addParent(newParentID string) error {
return o.fs.pacer.Call(func() (bool, error) { return o.fs.pacer.Call(func() (bool, error) {
resp, err := o.info.AddParent(newParentId) resp, err := o.info.AddParent(newParentID)
return o.fs.shouldRetry(resp, err) return o.fs.shouldRetry(resp, err)
}) })
} }
// Remove given parent from object, leaving the other possible // Remove given parent from object, leaving the other possible
// parents untouched. Object can end up having no parents. // parents untouched. Object can end up having no parents.
func (o *Object) removeParent(parentId string) error { func (o *Object) removeParent(parentID string) error {
return o.fs.pacer.Call(func() (bool, error) { return o.fs.pacer.Call(func() (bool, error) {
resp, err := o.info.RemoveParent(parentId) resp, err := o.info.RemoveParent(parentID)
return o.fs.shouldRetry(resp, err) return o.fs.shouldRetry(resp, err)
}) })
} }
func (o *Object) move(remote string, useDirErrorMsgs bool) (fs.Object, error) {
cantMove := fs.ErrorCantMove
existsAlready := fs.ErrorCantMove
if useDirErrorMsgs {
cantMove = fs.ErrorCantDirMove
existsAlready = fs.ErrorDirExists
}
// Temporary Object under construction
dstObj := &Object{
fs: o.fs,
remote: remote,
}
// check if there's already a directory with the same name
pathID, err := o.fs.dirCache.FindDir(remote, false)
if err == nil || pathID != "" {
fs.Debug(o, "Can't move - there is already a folder with the same name in the target location")
return nil, existsAlready
}
// Check if there's already a file with the same name
err = dstObj.readMetaData()
switch err {
case nil:
fs.Debug(o, "Can't move - there is already a file with the same name in the target location")
return nil, existsAlready
case fs.ErrorObjectNotFound:
// Not found so we can move it there
default:
return nil, err
}
sLeaf, sDirectoryID, err := o.fs.dirCache.FindPath(o.Remote(), false)
if err != nil {
return nil, err
}
tLeaf, tDirectoryID, err := o.fs.dirCache.FindPath(remote, true)
if err != nil {
return nil, err
}
if len(o.info.Parents) > 1 && sLeaf != tLeaf {
fs.Debug(o, "Can't move - object is attached to multiple parents and should be renamed. This would change the name of the node in all parents.")
return nil, cantMove
}
if sLeaf != tLeaf {
// fs.Debug(o, "renaming")
err = o.rename(tLeaf)
if err != nil {
fs.Debug(o, "Move: quick path rename failed: %v", err)
goto OnConflict
}
}
if sDirectoryID != tDirectoryID {
// fs.Debug(o, "moving")
err = o.replaceParent(sDirectoryID, tDirectoryID)
if err != nil {
// fs.Debug(o, "Move: quick path parent replace failed: %v", err)
return nil, err
}
}
return dstObj, nil
OnConflict:
fs.Debug(o, "Could not directly rename file, presumably because there was a file with the same name already. Instead, the file will now be trashed where such operations do not cause errors. It will be restored to the correct parent after. If any of the subsequent calls fails, the rename/move will be in an invalid state.")
// fs.Debug(o, "Trashing file")
err = o.Remove()
if err != nil {
return nil, err
}
// fs.Debug(o, "Renaming file")
err = o.rename(tLeaf)
if err != nil {
return nil, err
}
// note: replacing parent is forbidden by API, modifying them individually is
// okay though
// fs.Debug(o, "Adding target parent")
err = o.addParent(tDirectoryID)
if err != nil {
return nil, err
}
// fs.Debug(o, "removing original parent")
err = o.removeParent(sDirectoryID)
if err != nil {
return nil, err
}
// fs.Debug(o, "Restoring")
err = o.restore()
if err != nil {
return nil, err
}
return dstObj, nil
}
// MimeType of an Object if known, "" otherwise // MimeType of an Object if known, "" otherwise
func (o *Object) MimeType() string { func (o *Object) MimeType() string {
if o.info.ContentProperties.ContentType != nil { if o.info.ContentProperties.ContentType != nil {
@ -1103,8 +1120,8 @@ var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)
_ fs.Purger = (*Fs)(nil) _ fs.Purger = (*Fs)(nil)
// _ fs.Copier = (*Fs)(nil) // _ fs.Copier = (*Fs)(nil)
_ fs.Mover = (*Fs)(nil) _ fs.Mover = (*Fs)(nil)
// _ fs.DirMover = (*Fs)(nil) _ fs.DirMover = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.MimeTyper = &Object{} _ fs.MimeTyper = &Object{}
) )