forked from TrueCloudLab/rclone
This commit is contained in:
parent
554ee0d963
commit
49da220b65
2 changed files with 132 additions and 48 deletions
|
@ -285,6 +285,7 @@ type AsyncOperationStatus struct {
|
||||||
|
|
||||||
// GetID returns a normalized ID of the item
|
// GetID returns a normalized ID of the item
|
||||||
// If DriveID is known it will be prefixed to the ID with # seperator
|
// If DriveID is known it will be prefixed to the ID with # seperator
|
||||||
|
// Can be parsed using onedrive.parseNormalizedID(normalizedID)
|
||||||
func (i *Item) GetID() string {
|
func (i *Item) GetID() string {
|
||||||
if i.IsRemote() && i.RemoteItem.ID != "" {
|
if i.IsRemote() && i.RemoteItem.ID != "" {
|
||||||
return i.RemoteItem.ParentReference.DriveID + "#" + i.RemoteItem.ID
|
return i.RemoteItem.ParentReference.DriveID + "#" + i.RemoteItem.ID
|
||||||
|
|
|
@ -334,8 +334,23 @@ func shouldRetry(resp *http.Response, err error) (bool, error) {
|
||||||
return authRety || fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
|
return authRety || fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// readMetaDataForPath reads the metadata from the path
|
// readMetaDataForPathRelativeToID reads the metadata for a path relative to an item that is addressed by its normalized ID.
|
||||||
|
// if `relPath` == "", it reads the metadata for the item with that ID.
|
||||||
|
func (f *Fs) readMetaDataForPathRelativeToID(normalizedID string, relPath string) (info *api.Item, resp *http.Response, err error) {
|
||||||
|
opts := newOptsCall(normalizedID, "GET", ":/"+rest.URLPathEscape(replaceReservedChars(relPath)))
|
||||||
|
err = f.pacer.Call(func() (bool, error) {
|
||||||
|
resp, err = f.srv.CallJSON(&opts, nil, &info)
|
||||||
|
return shouldRetry(resp, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return info, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMetaDataForPath reads the metadata from the path (relative to the absolute root)
|
||||||
func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Response, err error) {
|
func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Response, err error) {
|
||||||
|
firstSlashIndex := strings.IndexRune(path, '/')
|
||||||
|
|
||||||
|
if f.driveType != driveTypePersonal || firstSlashIndex == -1 {
|
||||||
var opts rest.Opts
|
var opts rest.Opts
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
opts = rest.Opts{
|
opts = rest.Opts{
|
||||||
|
@ -352,10 +367,51 @@ func (f *Fs) readMetaDataForPath(path string) (info *api.Item, resp *http.Respon
|
||||||
resp, err = f.srv.CallJSON(&opts, nil, &info)
|
resp, err = f.srv.CallJSON(&opts, nil, &info)
|
||||||
return shouldRetry(resp, err)
|
return shouldRetry(resp, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
return info, resp, err
|
return info, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following branch handles the case when we're using OneDrive Personal and the path is in a folder.
|
||||||
|
// For OneDrive Personal, we need to consider the "shared with me" folders.
|
||||||
|
// An item in such a folder can only be addressed by its ID relative to the sharer's driveID or
|
||||||
|
// by its path relative to the folder's ID relative to the sharer's driveID.
|
||||||
|
// Note: A "shared with me" folder can only be placed in the sharee's absolute root.
|
||||||
|
// So we read metadata relative to a suitable folder's normalized ID.
|
||||||
|
var dirCacheFoundRoot bool
|
||||||
|
var rootNormalizedID string
|
||||||
|
if f.dirCache != nil {
|
||||||
|
var ok bool
|
||||||
|
if rootNormalizedID, ok = f.dirCache.Get(""); ok {
|
||||||
|
dirCacheFoundRoot = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, insideRoot := getRelativePathInsideBase(f.root, path)
|
||||||
|
var firstDir, baseNormalizedID string
|
||||||
|
if !insideRoot || !dirCacheFoundRoot {
|
||||||
|
// We do not have the normalized ID in dirCache for our query to base on. Query it manually.
|
||||||
|
firstDir, relPath = path[:firstSlashIndex], path[firstSlashIndex+1:]
|
||||||
|
info, resp, err := f.readMetaDataForPath(firstDir)
|
||||||
|
if err != nil {
|
||||||
|
return info, resp, err
|
||||||
|
}
|
||||||
|
baseNormalizedID = info.GetID()
|
||||||
|
} else {
|
||||||
|
if f.root != "" {
|
||||||
|
// Read metadata based on root
|
||||||
|
baseNormalizedID = rootNormalizedID
|
||||||
|
} else {
|
||||||
|
// Read metadata based on firstDir
|
||||||
|
firstDir, relPath = path[:firstSlashIndex], path[firstSlashIndex+1:]
|
||||||
|
baseNormalizedID, err = f.dirCache.FindDir(firstDir, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.readMetaDataForPathRelativeToID(baseNormalizedID, relPath)
|
||||||
|
}
|
||||||
|
|
||||||
// errorHandler parses a non 2xx error response into an error
|
// errorHandler parses a non 2xx error response into an error
|
||||||
func errorHandler(resp *http.Response) error {
|
func errorHandler(resp *http.Response) error {
|
||||||
// Decode error response
|
// Decode error response
|
||||||
|
@ -514,18 +570,11 @@ 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) {
|
||||||
// fs.Debugf(f, "FindLeaf(%q, %q)", pathID, leaf)
|
// fs.Debugf(f, "FindLeaf(%q, %q)", pathID, leaf)
|
||||||
parent, ok := f.dirCache.GetInv(pathID)
|
_, ok := f.dirCache.GetInv(pathID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", false, errors.New("couldn't find parent ID")
|
return "", false, errors.New("couldn't find parent ID")
|
||||||
}
|
}
|
||||||
path := leaf
|
info, resp, err := f.readMetaDataForPathRelativeToID(pathID, leaf)
|
||||||
if parent != "" {
|
|
||||||
path = parent + "/" + path
|
|
||||||
}
|
|
||||||
if f.dirCache.FoundRoot() {
|
|
||||||
path = f.rootSlash() + path
|
|
||||||
}
|
|
||||||
info, resp, err := f.readMetaDataForPath(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||||
return "", false, nil
|
return "", false, nil
|
||||||
|
@ -867,13 +916,13 @@ func (f *Fs) Copy(src fs.Object, remote string) (fs.Object, error) {
|
||||||
opts.ExtraHeaders = map[string]string{"Prefer": "respond-async"}
|
opts.ExtraHeaders = map[string]string{"Prefer": "respond-async"}
|
||||||
opts.NoResponse = true
|
opts.NoResponse = true
|
||||||
|
|
||||||
id, _, _ := parseDirID(directoryID)
|
id, dstDriveID, _ := parseNormalizedID(directoryID)
|
||||||
|
|
||||||
replacedLeaf := replaceReservedChars(leaf)
|
replacedLeaf := replaceReservedChars(leaf)
|
||||||
copyReq := api.CopyItemRequest{
|
copyReq := api.CopyItemRequest{
|
||||||
Name: &replacedLeaf,
|
Name: &replacedLeaf,
|
||||||
ParentReference: api.ItemReference{
|
ParentReference: api.ItemReference{
|
||||||
DriveID: f.driveID,
|
DriveID: dstDriveID,
|
||||||
ID: id,
|
ID: id,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -940,14 +989,22 @@ func (f *Fs) Move(src fs.Object, remote string) (fs.Object, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id, dstDriveID, _ := parseNormalizedID(directoryID)
|
||||||
|
_, srcObjDriveID, _ := parseNormalizedID(srcObj.id)
|
||||||
|
|
||||||
|
if dstDriveID != srcObjDriveID {
|
||||||
|
// https://docs.microsoft.com/en-us/graph/api/driveitem-move?view=graph-rest-1.0
|
||||||
|
// "Items cannot be moved between Drives using this request."
|
||||||
|
return nil, fs.ErrorCantMove
|
||||||
|
}
|
||||||
|
|
||||||
// Move the object
|
// Move the object
|
||||||
opts := newOptsCall(srcObj.id, "PATCH", "")
|
opts := newOptsCall(srcObj.id, "PATCH", "")
|
||||||
|
|
||||||
id, _, _ := parseDirID(directoryID)
|
|
||||||
|
|
||||||
move := api.MoveItemRequest{
|
move := api.MoveItemRequest{
|
||||||
Name: replaceReservedChars(leaf),
|
Name: replaceReservedChars(leaf),
|
||||||
ParentReference: &api.ItemReference{
|
ParentReference: &api.ItemReference{
|
||||||
|
DriveID: dstDriveID,
|
||||||
ID: id,
|
ID: id,
|
||||||
},
|
},
|
||||||
// We set the mod time too as it gets reset otherwise
|
// We set the mod time too as it gets reset otherwise
|
||||||
|
@ -1024,7 +1081,20 @@ func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
parsedDstDirID, _, _ := parseDirID(dstDirectoryID)
|
parsedDstDirID, dstDriveID, _ := parseNormalizedID(dstDirectoryID)
|
||||||
|
|
||||||
|
// Find ID of src
|
||||||
|
srcID, err := srcFs.dirCache.FindDir(srcRemote, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, srcDriveID, _ := parseNormalizedID(srcID)
|
||||||
|
|
||||||
|
if dstDriveID != srcDriveID {
|
||||||
|
// https://docs.microsoft.com/en-us/graph/api/driveitem-move?view=graph-rest-1.0
|
||||||
|
// "Items cannot be moved between Drives using this request."
|
||||||
|
return fs.ErrorCantDirMove
|
||||||
|
}
|
||||||
|
|
||||||
// Check destination does not exist
|
// Check destination does not exist
|
||||||
if dstRemote != "" {
|
if dstRemote != "" {
|
||||||
|
@ -1038,14 +1108,8 @@ func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find ID of src
|
|
||||||
srcID, err := srcFs.dirCache.FindDir(srcRemote, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get timestamps of src so they can be preserved
|
// Get timestamps of src so they can be preserved
|
||||||
srcInfo, _, err := srcFs.readMetaDataForPath(srcPath)
|
srcInfo, _, err := srcFs.readMetaDataForPathRelativeToID(srcID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1055,6 +1119,7 @@ func (f *Fs) DirMove(src fs.Fs, srcRemote, dstRemote string) error {
|
||||||
move := api.MoveItemRequest{
|
move := api.MoveItemRequest{
|
||||||
Name: replaceReservedChars(leaf),
|
Name: replaceReservedChars(leaf),
|
||||||
ParentReference: &api.ItemReference{
|
ParentReference: &api.ItemReference{
|
||||||
|
DriveID: dstDriveID,
|
||||||
ID: parsedDstDirID,
|
ID: parsedDstDirID,
|
||||||
},
|
},
|
||||||
// We set the mod time too as it gets reset otherwise
|
// We set the mod time too as it gets reset otherwise
|
||||||
|
@ -1122,7 +1187,7 @@ func (f *Fs) PublicLink(remote string) (link string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
opts := newOptsCall(info.ID, "POST", "/createLink")
|
opts := newOptsCall(info.GetID(), "POST", "/createLink")
|
||||||
|
|
||||||
share := api.CreateShareLinkRequest{
|
share := api.CreateShareLinkRequest{
|
||||||
Type: "view",
|
Type: "view",
|
||||||
|
@ -1270,13 +1335,13 @@ func (o *Object) ModTime() time.Time {
|
||||||
// setModTime sets the modification time of the local fs object
|
// setModTime sets the modification time of the local fs object
|
||||||
func (o *Object) setModTime(modTime time.Time) (*api.Item, error) {
|
func (o *Object) setModTime(modTime time.Time) (*api.Item, error) {
|
||||||
var opts rest.Opts
|
var opts rest.Opts
|
||||||
_, directoryID, _ := o.fs.dirCache.FindPath(o.remote, false)
|
leaf, directoryID, _ := o.fs.dirCache.FindPath(o.remote, false)
|
||||||
_, drive, rootURL := parseDirID(directoryID)
|
trueDirID, drive, rootURL := parseNormalizedID(directoryID)
|
||||||
if drive != "" {
|
if drive != "" {
|
||||||
opts = rest.Opts{
|
opts = rest.Opts{
|
||||||
Method: "PATCH",
|
Method: "PATCH",
|
||||||
RootURL: rootURL,
|
RootURL: rootURL,
|
||||||
Path: "/" + drive + "/root:/" + rest.URLPathEscape(o.srvPath()),
|
Path: "/" + drive + "/items/" + trueDirID + ":/" + rest.URLPathEscape(leaf),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
opts = rest.Opts{
|
opts = rest.Opts{
|
||||||
|
@ -1344,7 +1409,7 @@ func (o *Object) Open(options ...fs.OpenOption) (in io.ReadCloser, err error) {
|
||||||
// createUploadSession creates an upload session for the object
|
// createUploadSession creates an upload session for the object
|
||||||
func (o *Object) createUploadSession(modTime time.Time) (response *api.CreateUploadResponse, err error) {
|
func (o *Object) createUploadSession(modTime time.Time) (response *api.CreateUploadResponse, err error) {
|
||||||
leaf, directoryID, _ := o.fs.dirCache.FindPath(o.remote, false)
|
leaf, directoryID, _ := o.fs.dirCache.FindPath(o.remote, false)
|
||||||
id, drive, rootURL := parseDirID(directoryID)
|
id, drive, rootURL := parseNormalizedID(directoryID)
|
||||||
var opts rest.Opts
|
var opts rest.Opts
|
||||||
if drive != "" {
|
if drive != "" {
|
||||||
opts = rest.Opts{
|
opts = rest.Opts{
|
||||||
|
@ -1477,13 +1542,13 @@ func (o *Object) uploadSinglepart(in io.Reader, size int64, modTime time.Time) (
|
||||||
fs.Debugf(o, "Starting singlepart upload")
|
fs.Debugf(o, "Starting singlepart upload")
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
var opts rest.Opts
|
var opts rest.Opts
|
||||||
_, directoryID, _ := o.fs.dirCache.FindPath(o.remote, false)
|
leaf, directoryID, _ := o.fs.dirCache.FindPath(o.remote, false)
|
||||||
_, drive, rootURL := parseDirID(directoryID)
|
trueDirID, drive, rootURL := parseNormalizedID(directoryID)
|
||||||
if drive != "" {
|
if drive != "" {
|
||||||
opts = rest.Opts{
|
opts = rest.Opts{
|
||||||
Method: "PUT",
|
Method: "PUT",
|
||||||
RootURL: rootURL,
|
RootURL: rootURL,
|
||||||
Path: "/" + drive + "/root:/" + rest.URLPathEscape(o.srvPath()) + ":/content",
|
Path: "/" + drive + "/items/" + trueDirID + ":/" + rest.URLPathEscape(leaf) + ":/content",
|
||||||
ContentLength: &size,
|
ContentLength: &size,
|
||||||
Body: in,
|
Body: in,
|
||||||
}
|
}
|
||||||
|
@ -1566,8 +1631,8 @@ func (o *Object) ID() string {
|
||||||
return o.id
|
return o.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOptsCall(id string, method string, route string) (opts rest.Opts) {
|
func newOptsCall(normalizedID string, method string, route string) (opts rest.Opts) {
|
||||||
id, drive, rootURL := parseDirID(id)
|
id, drive, rootURL := parseNormalizedID(normalizedID)
|
||||||
|
|
||||||
if drive != "" {
|
if drive != "" {
|
||||||
return rest.Opts{
|
return rest.Opts{
|
||||||
|
@ -1582,7 +1647,10 @@ func newOptsCall(id string, method string, route string) (opts rest.Opts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDirID(ID string) (string, string, string) {
|
// parseNormalizedID parses a normalized ID (may be in the form `driveID#itemID` or just `itemID`)
|
||||||
|
// and returns itemID, driveID, rootURL.
|
||||||
|
// Such a normalized ID can come from (*Item).GetID()
|
||||||
|
func parseNormalizedID(ID string) (string, string, string) {
|
||||||
if strings.Index(ID, "#") >= 0 {
|
if strings.Index(ID, "#") >= 0 {
|
||||||
s := strings.Split(ID, "#")
|
s := strings.Split(ID, "#")
|
||||||
return s[1], s[0], graphURL + "/drives"
|
return s[1], s[0], graphURL + "/drives"
|
||||||
|
@ -1590,6 +1658,21 @@ func parseDirID(ID string) (string, string, string) {
|
||||||
return ID, "", ""
|
return ID, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRelativePathInsideBase checks if `target` is inside `base`. If so, it
|
||||||
|
// returns a relative path for `target` based on `base` and a boolean `true`.
|
||||||
|
// Otherwise returns "", false.
|
||||||
|
func getRelativePathInsideBase(base, target string) (string, bool) {
|
||||||
|
if base == "" {
|
||||||
|
return target, true
|
||||||
|
}
|
||||||
|
|
||||||
|
baseSlash := base + "/"
|
||||||
|
if strings.HasPrefix(target+"/", baseSlash) {
|
||||||
|
return target[len(baseSlash):], true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// Check the interfaces are satisfied
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.Fs = (*Fs)(nil)
|
_ fs.Fs = (*Fs)(nil)
|
||||||
|
|
Loading…
Reference in a new issue