mirror of
https://github.com/rclone/rclone.git
synced 2026-06-28 18:05:12 -04:00
drime: fix files being uploaded to the wrong directory
Large files (sent as multipart uploads) were placed in the wrong folder for two reasons: - the parent folder was sent as "parent_id", but the API ignores that and expects "parentId", so the parent was never honoured - relativePath was sent as the full path from the drive root, which made the server build folders from it and silently drop any "0" path segment (e.g. ".../data/0/file" lost the "0") Send the parent as "parentId" and use just the leaf as relativePath, matching the working single-part upload. This also lets us remove the now-unneeded absolute-path resolution code. Fixes #9392 Co-authored-by: Brian King <BrianDKing@gmail.com>
This commit is contained in:
@@ -171,7 +171,7 @@ type MultiPartCreateRequest struct {
|
||||
Mime string `json:"mime"`
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"extension"`
|
||||
ParentID json.Number `json:"parent_id"`
|
||||
ParentID json.Number `json:"parentId"`
|
||||
RelativePath string `json:"relativePath"`
|
||||
WorkspaceID string `json:"workspaceId,omitempty"`
|
||||
}
|
||||
@@ -222,7 +222,7 @@ type MultiPartEntriesRequest struct {
|
||||
Filename string `json:"filename"`
|
||||
Size int64 `json:"size"`
|
||||
ClientExtension string `json:"clientExtension"`
|
||||
ParentID json.Number `json:"parent_id"`
|
||||
ParentID json.Number `json:"parentId"`
|
||||
RelativePath string `json:"relativePath"`
|
||||
WorkspaceID string `json:"workspaceId,omitempty"`
|
||||
}
|
||||
@@ -238,14 +238,6 @@ type MultiPartAbort struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// FolderPathResponse is returned by GET /folders/{hash}/path
|
||||
//
|
||||
// Path is the breadcrumb from the drive root down to the requested folder.
|
||||
type FolderPathResponse struct {
|
||||
Status string `json:"status"`
|
||||
Path []Item `json:"path"`
|
||||
}
|
||||
|
||||
// SpaceUsageResponse is returned by GET /user/space-usage
|
||||
type SpaceUsageResponse struct {
|
||||
Used int64 `json:"used"`
|
||||
|
||||
@@ -15,7 +15,6 @@ should stay under that.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -200,16 +199,13 @@ type Options struct {
|
||||
|
||||
// Fs represents a remote drime
|
||||
type Fs struct {
|
||||
name string // name of this remote
|
||||
root string // the path we are working on
|
||||
opt Options // parsed options
|
||||
features *fs.Features // optional features
|
||||
srv *rest.Client // the connection to the server
|
||||
dirCache *dircache.DirCache // Map of directory path to directory id
|
||||
pacer *fs.Pacer // pacer for API calls
|
||||
absRootOnce *sync.Once // protects absRoot computation
|
||||
absRoot string // absolute path of f.root from drive root
|
||||
absRootErr error // error from computing absRoot, if any
|
||||
name string // name of this remote
|
||||
root string // the path we are working on
|
||||
opt Options // parsed options
|
||||
features *fs.Features // optional features
|
||||
srv *rest.Client // the connection to the server
|
||||
dirCache *dircache.DirCache // Map of directory path to directory id
|
||||
pacer *fs.Pacer // pacer for API calls
|
||||
}
|
||||
|
||||
// Object describes a drime object
|
||||
@@ -339,62 +335,6 @@ func (f *Fs) getItem(ctx context.Context, id string, dirID string, leaf string)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// idToAbsolutePath returns the absolute path (from the user's drive root) of
|
||||
// the folder with the given ID.
|
||||
//
|
||||
// Drime exposes GET /folders/{hash}/path which returns the breadcrumb from
|
||||
// drive root down to the folder. The hash format is undocumented but
|
||||
// observed to be base64("<id>|") - every item drime returns has a `hash`
|
||||
// field of that shape (e.g. id 704791396 => "NzA0NzkxMzk2fA"). The docs
|
||||
// example "MTExMzQ0fHBhZA" decodes to "111344|pad", so a non-empty suffix
|
||||
// after the pipe is also accepted; if drime ever starts requiring a
|
||||
// specific suffix this will break.
|
||||
//
|
||||
// Returns "" for an empty/zero ID (drive root).
|
||||
func (f *Fs) idToAbsolutePath(ctx context.Context, id string) (string, error) {
|
||||
if id == "" || id == "0" {
|
||||
return "", nil
|
||||
}
|
||||
hash := base64.StdEncoding.EncodeToString([]byte(id + "|"))
|
||||
opts := rest.Opts{
|
||||
Method: "GET",
|
||||
Path: "/folders/" + hash + "/path",
|
||||
Parameters: url.Values{},
|
||||
}
|
||||
if f.opt.WorkspaceID != "" {
|
||||
opts.Parameters.Set("workspaceId", f.opt.WorkspaceID)
|
||||
}
|
||||
var result api.FolderPathResponse
|
||||
var resp *http.Response
|
||||
err := f.pacer.Call(func() (bool, error) {
|
||||
var err error
|
||||
resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
|
||||
return shouldRetry(ctx, resp, err)
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get folder path for %q: %w", id, err)
|
||||
}
|
||||
parts := make([]string, 0, len(result.Path))
|
||||
for _, item := range result.Path {
|
||||
parts = append(parts, f.opt.Enc.ToStandardName(item.Name))
|
||||
}
|
||||
return path.Join(parts...), nil
|
||||
}
|
||||
|
||||
// absoluteRoot returns the absolute path from the drive root to the Fs root.
|
||||
// Computed once and cached.
|
||||
func (f *Fs) absoluteRoot(ctx context.Context) (string, error) {
|
||||
f.absRootOnce.Do(func() {
|
||||
rootID, err := f.dirCache.RootID(ctx, false)
|
||||
if err != nil {
|
||||
f.absRootErr = err
|
||||
return
|
||||
}
|
||||
f.absRoot, f.absRootErr = f.idToAbsolutePath(ctx, rootID)
|
||||
})
|
||||
return f.absRoot, f.absRootErr
|
||||
}
|
||||
|
||||
// errorHandler parses a non 2xx error response into an error
|
||||
func errorHandler(resp *http.Response) error {
|
||||
body, err := rest.ReadBody(resp)
|
||||
@@ -435,12 +375,11 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
|
||||
client := fshttp.NewClient(ctx)
|
||||
|
||||
f := &Fs{
|
||||
name: name,
|
||||
root: root,
|
||||
opt: *opt,
|
||||
srv: rest.NewClient(client).SetRoot(rootURL),
|
||||
pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
|
||||
absRootOnce: new(sync.Once),
|
||||
name: name,
|
||||
root: root,
|
||||
opt: *opt,
|
||||
srv: rest.NewClient(client).SetRoot(rootURL),
|
||||
pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
|
||||
}
|
||||
f.features = (&fs.Features{
|
||||
CanHaveEmptyDirectories: true,
|
||||
@@ -1173,14 +1112,10 @@ func (f *Fs) OpenChunkWriter(ctx context.Context, remote string, src fs.ObjectIn
|
||||
return info, nil, err
|
||||
}
|
||||
|
||||
// The /s3/multipart/create and /s3/entries endpoints interpret
|
||||
// relativePath as an absolute path from the drive root, not relative to
|
||||
// parent_id. Resolve our root's absolute path so we can build it.
|
||||
absRoot, err := f.absoluteRoot(ctx)
|
||||
if err != nil {
|
||||
return info, nil, fmt.Errorf("failed to resolve absolute path of root: %w", err)
|
||||
}
|
||||
relPath := f.opt.Enc.FromStandardPath(path.Join(absRoot, remote))
|
||||
// Send just the leaf as relativePath, matching the single-part /uploads
|
||||
// convention. The file is placed by parentId; sending an absolute path
|
||||
// here makes the server build folders from it and drop "0" path segments.
|
||||
relPath := f.opt.Enc.FromStandardName(leaf)
|
||||
|
||||
// Temporary Object under construction
|
||||
o := &Object{
|
||||
|
||||
Reference in New Issue
Block a user