github.com/artpar/rclone@v1.67.3/backend/sharefile/sharefile.go (about) 1 // Package sharefile provides an interface to the Citrix Sharefile 2 // object storage system. 3 package sharefile 4 5 //go:generate ./update-timezone.sh 6 7 /* NOTES 8 9 ## for docs 10 11 Detail standard/chunked/streaming uploads? 12 13 ## Bugs in API 14 15 The times in updateItem are being parsed in EST/DST local time 16 updateItem only sets times accurate to 1 second 17 18 https://community.sharefilesupport.com/citrixsharefile/topics/bug-report-for-update-item-patch-items-id-setting-clientmodifieddate-ignores-timezone-and-milliseconds 19 20 When doing a rename+move directory, the server appears to do the 21 rename first in the local directory which can overwrite files of the 22 same name in the local directory. 23 24 https://community.sharefilesupport.com/citrixsharefile/topics/bug-report-for-update-item-patch-items-id-file-overwrite-under-certain-conditions 25 26 The Copy command can't change the name at the same time which means we 27 have to copy via a temporary directory. 28 29 https://community.sharefilesupport.com/citrixsharefile/topics/copy-item-needs-to-be-able-to-set-a-new-name 30 31 ## Allowed characters 32 33 https://api.sharefile.com/rest/index/odata.aspx 34 35 $select to limit returned fields 36 https://www.odata.org/documentation/odata-version-3-0/odata-version-3-0-core-protocol/#theselectsystemqueryoption 37 38 Also $filter to select only things we need 39 40 https://support.citrix.com/article/CTX234774 41 42 The following characters should not be used in folder or file names. 43 44 \ 45 / 46 . 47 , 48 : 49 ; 50 * 51 ? 52 " 53 < 54 > 55 A filename ending with a period without an extension 56 File names with leading or trailing whitespaces. 57 58 59 // sharefile 60 stringNeedsEscaping = []byte{ 61 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x2A, 0x2E, 0x2F, 0x3A, 0x3C, 0x3E, 0x3F, 0x7C, 0xEFBCBC 62 } 63 maxFileLength = 256 64 canWriteUnnormalized = true 65 canReadUnnormalized = true 66 canReadRenormalized = false 67 canStream = true 68 69 Which is control chars + [' ', '*', '.', '/', ':', '<', '>', '?', '|'] 70 - also \ and " 71 72 */ 73 74 import ( 75 "context" 76 "encoding/json" 77 "errors" 78 "fmt" 79 "io" 80 "net/http" 81 "net/url" 82 "path" 83 "strings" 84 "time" 85 86 "github.com/artpar/rclone/backend/sharefile/api" 87 "github.com/artpar/rclone/fs" 88 "github.com/artpar/rclone/fs/config" 89 "github.com/artpar/rclone/fs/config/configmap" 90 "github.com/artpar/rclone/fs/config/configstruct" 91 "github.com/artpar/rclone/fs/config/obscure" 92 "github.com/artpar/rclone/fs/fserrors" 93 "github.com/artpar/rclone/fs/hash" 94 "github.com/artpar/rclone/lib/dircache" 95 "github.com/artpar/rclone/lib/encoder" 96 "github.com/artpar/rclone/lib/oauthutil" 97 "github.com/artpar/rclone/lib/pacer" 98 "github.com/artpar/rclone/lib/random" 99 "github.com/artpar/rclone/lib/rest" 100 "golang.org/x/oauth2" 101 ) 102 103 const ( 104 rcloneClientID = "djQUPlHTUM9EvayYBWuKC5IrVIoQde46" 105 rcloneEncryptedClientSecret = "v7572bKhUindQL3yDnUAebmgP-QxiwT38JLxVPolcZBl6SSs329MtFzH73x7BeELmMVZtneUPvALSopUZ6VkhQ" 106 minSleep = 10 * time.Millisecond 107 maxSleep = 2 * time.Second 108 decayConstant = 2 // bigger for slower decay, exponential 109 apiPath = "/sf/v3" // add to endpoint to get API path 110 tokenPath = "/oauth/token" // add to endpoint to get Token path 111 minChunkSize = 256 * fs.Kibi 112 maxChunkSize = 2 * fs.Gibi 113 defaultChunkSize = 64 * fs.Mebi 114 defaultUploadCutoff = 128 * fs.Mebi 115 ) 116 117 // Generate a new oauth2 config which we will update when we know the TokenURL 118 func newOauthConfig(tokenURL string) *oauth2.Config { 119 return &oauth2.Config{ 120 Scopes: nil, 121 Endpoint: oauth2.Endpoint{ 122 AuthURL: "https://secure.sharefile.com/oauth/authorize", 123 TokenURL: tokenURL, 124 }, 125 ClientID: rcloneClientID, 126 ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret), 127 RedirectURL: oauthutil.RedirectPublicSecureURL, 128 } 129 } 130 131 // Register with Fs 132 func init() { 133 fs.Register(&fs.RegInfo{ 134 Name: "sharefile", 135 Description: "Citrix Sharefile", 136 NewFs: NewFs, 137 Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) { 138 oauthConfig := newOauthConfig("") 139 checkAuth := func(oauthConfig *oauth2.Config, auth *oauthutil.AuthResult) error { 140 if auth == nil || auth.Form == nil { 141 return errors.New("endpoint not found in response") 142 } 143 subdomain := auth.Form.Get("subdomain") 144 apicp := auth.Form.Get("apicp") 145 if subdomain == "" || apicp == "" { 146 return fmt.Errorf("subdomain or apicp not found in response: %+v", auth.Form) 147 } 148 endpoint := "https://" + subdomain + "." + apicp 149 m.Set("endpoint", endpoint) 150 oauthConfig.Endpoint.TokenURL = endpoint + tokenPath 151 return nil 152 } 153 return oauthutil.ConfigOut("", &oauthutil.Options{ 154 OAuth2Config: oauthConfig, 155 CheckAuth: checkAuth, 156 }) 157 }, 158 Options: append(oauthutil.SharedOptions, []fs.Option{{ 159 Name: "upload_cutoff", 160 Help: "Cutoff for switching to multipart upload.", 161 Default: defaultUploadCutoff, 162 Advanced: true, 163 }, { 164 Name: "root_folder_id", 165 Help: `ID of the root folder. 166 167 Leave blank to access "Personal Folders". You can use one of the 168 standard values here or any folder ID (long hex number ID).`, 169 Examples: []fs.OptionExample{{ 170 Value: "", 171 Help: `Access the Personal Folders (default).`, 172 }, { 173 Value: "favorites", 174 Help: "Access the Favorites folder.", 175 }, { 176 Value: "allshared", 177 Help: "Access all the shared folders.", 178 }, { 179 Value: "connectors", 180 Help: "Access all the individual connectors.", 181 }, { 182 Value: "top", 183 Help: "Access the home, favorites, and shared folders as well as the connectors.", 184 }}, 185 Sensitive: true, 186 }, { 187 Name: "chunk_size", 188 Default: defaultChunkSize, 189 Help: `Upload chunk size. 190 191 Must a power of 2 >= 256k. 192 193 Making this larger will improve performance, but note that each chunk 194 is buffered in memory one per transfer. 195 196 Reducing this will reduce memory usage but decrease performance.`, 197 Advanced: true, 198 }, { 199 Name: "endpoint", 200 Help: `Endpoint for API calls. 201 202 This is usually auto discovered as part of the oauth process, but can 203 be set manually to something like: https://XXX.sharefile.com 204 `, 205 Advanced: true, 206 Default: "", 207 }, { 208 Name: config.ConfigEncoding, 209 Help: config.ConfigEncodingHelp, 210 Advanced: true, 211 Default: (encoder.Base | 212 encoder.EncodeWin | // :?"*<>| 213 encoder.EncodeBackSlash | // \ 214 encoder.EncodeCtl | 215 encoder.EncodeRightSpace | 216 encoder.EncodeRightPeriod | 217 encoder.EncodeLeftSpace | 218 encoder.EncodeLeftPeriod | 219 encoder.EncodeInvalidUtf8), 220 }}...), 221 }) 222 } 223 224 // Options defines the configuration for this backend 225 type Options struct { 226 RootFolderID string `config:"root_folder_id"` 227 UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` 228 ChunkSize fs.SizeSuffix `config:"chunk_size"` 229 Endpoint string `config:"endpoint"` 230 Enc encoder.MultiEncoder `config:"encoding"` 231 } 232 233 // Fs represents a remote cloud storage system 234 type Fs struct { 235 name string // name of this remote 236 root string // the path we are working on 237 opt Options // parsed options 238 ci *fs.ConfigInfo // global config 239 features *fs.Features // optional features 240 srv *rest.Client // the connection to the server 241 dirCache *dircache.DirCache // Map of directory path to directory id 242 pacer *fs.Pacer // pacer for API calls 243 bufferTokens chan []byte // control concurrency of multipart uploads 244 tokenRenewer *oauthutil.Renew // renew the token on expiry 245 rootID string // ID of the users root folder 246 location *time.Location // timezone of server for SetModTime workaround 247 } 248 249 // Object describes a file 250 type Object struct { 251 fs *Fs // what this object is part of 252 remote string // The remote path 253 hasMetaData bool // metadata is present and correct 254 size int64 // size of the object 255 modTime time.Time // modification time of the object 256 id string // ID of the object 257 md5 string // hash of the object 258 } 259 260 // ------------------------------------------------------------ 261 262 // Name of the remote (as passed into NewFs) 263 func (f *Fs) Name() string { 264 return f.name 265 } 266 267 // Root of the remote (as passed into NewFs) 268 func (f *Fs) Root() string { 269 return f.root 270 } 271 272 // String converts this Fs to a string 273 func (f *Fs) String() string { 274 return fmt.Sprintf("sharefile root '%s'", f.root) 275 } 276 277 // Features returns the optional features of this Fs 278 func (f *Fs) Features() *fs.Features { 279 return f.features 280 } 281 282 // parsePath parses a sharefile 'url' 283 func parsePath(path string) (root string) { 284 root = strings.Trim(path, "/") 285 return 286 } 287 288 // retryErrorCodes is a slice of error codes that we will retry 289 var retryErrorCodes = []int{ 290 429, // Too Many Requests. 291 500, // Internal Server Error 292 502, // Bad Gateway 293 503, // Service Unavailable 294 504, // Gateway Timeout 295 509, // Bandwidth Limit Exceeded 296 } 297 298 // shouldRetry returns a boolean as to whether this resp and err 299 // deserve to be retried. It returns the err as a convenience 300 func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { 301 if fserrors.ContextError(ctx, &err) { 302 return false, err 303 } 304 return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 305 } 306 307 // Reads the metadata for the id passed in. If id is "" then it returns the root 308 // if path is not "" then the item read use id as the root and the path is relative 309 func (f *Fs) readMetaDataForIDPath(ctx context.Context, id, path string, directoriesOnly bool, filesOnly bool) (info *api.Item, err error) { 310 opts := rest.Opts{ 311 Method: "GET", 312 Path: "/Items", 313 Parameters: url.Values{ 314 "$select": {api.ListRequestSelect}, 315 }, 316 } 317 if id != "" { 318 opts.Path += "(" + id + ")" 319 } 320 if path != "" { 321 opts.Path += "/ByPath" 322 opts.Parameters.Set("path", "/"+f.opt.Enc.FromStandardPath(path)) 323 } 324 var item api.Item 325 var resp *http.Response 326 err = f.pacer.Call(func() (bool, error) { 327 resp, err = f.srv.CallJSON(ctx, &opts, nil, &item) 328 return shouldRetry(ctx, resp, err) 329 }) 330 if err != nil { 331 if resp != nil && resp.StatusCode == http.StatusNotFound { 332 if filesOnly { 333 return nil, fs.ErrorObjectNotFound 334 } 335 return nil, fs.ErrorDirNotFound 336 } 337 return nil, fmt.Errorf("couldn't find item: %w", err) 338 } 339 if directoriesOnly && item.Type != api.ItemTypeFolder { 340 return nil, fs.ErrorIsFile 341 } 342 if filesOnly { 343 if item.Type == api.ItemTypeFolder { 344 return nil, fs.ErrorIsDir 345 } else if item.Type != api.ItemTypeFile { 346 return nil, fs.ErrorNotAFile 347 } 348 } 349 return &item, nil 350 } 351 352 // Reads the metadata for the id passed in. If id is "" then it returns the root 353 func (f *Fs) readMetaDataForID(ctx context.Context, id string, directoriesOnly bool, filesOnly bool) (info *api.Item, err error) { 354 return f.readMetaDataForIDPath(ctx, id, "", directoriesOnly, filesOnly) 355 } 356 357 // readMetaDataForPath reads the metadata from the path 358 func (f *Fs) readMetaDataForPath(ctx context.Context, path string, directoriesOnly bool, filesOnly bool) (info *api.Item, err error) { 359 leaf, directoryID, err := f.dirCache.FindPath(ctx, path, false) 360 if err != nil { 361 if err == fs.ErrorDirNotFound { 362 return nil, fs.ErrorObjectNotFound 363 } 364 return nil, err 365 } 366 return f.readMetaDataForIDPath(ctx, directoryID, leaf, directoriesOnly, filesOnly) 367 } 368 369 // errorHandler parses a non 2xx error response into an error 370 func errorHandler(resp *http.Response) error { 371 body, err := rest.ReadBody(resp) 372 if err != nil { 373 body = nil 374 } 375 var e = api.Error{ 376 Code: fmt.Sprint(resp.StatusCode), 377 Reason: resp.Status, 378 } 379 e.Message.Lang = "en" 380 e.Message.Value = string(body) 381 if body != nil { 382 _ = json.Unmarshal(body, &e) 383 } 384 return &e 385 } 386 387 func checkUploadChunkSize(cs fs.SizeSuffix) error { 388 if cs < minChunkSize { 389 return fmt.Errorf("ChunkSize: %s is less than %s", cs, minChunkSize) 390 } 391 if cs > maxChunkSize { 392 return fmt.Errorf("ChunkSize: %s is greater than %s", cs, maxChunkSize) 393 } 394 return nil 395 } 396 397 func (f *Fs) setUploadChunkSize(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) { 398 err = checkUploadChunkSize(cs) 399 if err == nil { 400 old, f.opt.ChunkSize = f.opt.ChunkSize, cs 401 f.fillBufferTokens() // reset the buffer tokens 402 } 403 return 404 } 405 406 func checkUploadCutoff(cs fs.SizeSuffix) error { 407 return nil 408 } 409 410 func (f *Fs) setUploadCutoff(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) { 411 err = checkUploadCutoff(cs) 412 if err == nil { 413 old, f.opt.UploadCutoff = f.opt.UploadCutoff, cs 414 } 415 return 416 } 417 418 // NewFs constructs an Fs from the path, container:path 419 func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) { 420 // Parse config into Options struct 421 opt := new(Options) 422 err := configstruct.Set(m, opt) 423 if err != nil { 424 return nil, err 425 } 426 427 // Check parameters OK 428 if opt.Endpoint == "" { 429 return nil, errors.New("endpoint not set: rebuild the remote or set manually") 430 } 431 err = checkUploadChunkSize(opt.ChunkSize) 432 if err != nil { 433 return nil, err 434 } 435 err = checkUploadCutoff(opt.UploadCutoff) 436 if err != nil { 437 return nil, err 438 } 439 440 root = parsePath(root) 441 442 oauthConfig := newOauthConfig(opt.Endpoint + tokenPath) 443 var client *http.Client 444 var ts *oauthutil.TokenSource 445 client, ts, err = oauthutil.NewClient(ctx, name, m, oauthConfig) 446 if err != nil { 447 return nil, fmt.Errorf("failed to configure sharefile: %w", err) 448 } 449 450 ci := fs.GetConfig(ctx) 451 f := &Fs{ 452 name: name, 453 root: root, 454 opt: *opt, 455 ci: ci, 456 srv: rest.NewClient(client).SetRoot(opt.Endpoint + apiPath), 457 pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), 458 } 459 f.features = (&fs.Features{ 460 CaseInsensitive: true, 461 CanHaveEmptyDirectories: true, 462 ReadMimeType: false, 463 }).Fill(ctx, f) 464 f.srv.SetErrorHandler(errorHandler) 465 f.fillBufferTokens() 466 467 // Renew the token in the background 468 if ts != nil { 469 f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error { 470 _, err := f.List(ctx, "") 471 return err 472 }) 473 } 474 475 // Load the server timezone from an internal file 476 // Used to correct the time in SetModTime 477 const serverTimezone = "America/New_York" 478 timezone, err := tzdata.Open(serverTimezone) 479 if err != nil { 480 return nil, fmt.Errorf("failed to open timezone db: %w", err) 481 } 482 tzdata, err := io.ReadAll(timezone) 483 if err != nil { 484 return nil, fmt.Errorf("failed to read timezone: %w", err) 485 } 486 _ = timezone.Close() 487 f.location, err = time.LoadLocationFromTZData(serverTimezone, tzdata) 488 if err != nil { 489 return nil, fmt.Errorf("failed to load location from timezone: %w", err) 490 } 491 492 // Find ID of user's root folder 493 if opt.RootFolderID == "" { 494 item, err := f.readMetaDataForID(ctx, opt.RootFolderID, true, false) 495 if err != nil { 496 return nil, fmt.Errorf("couldn't find root ID: %w", err) 497 } 498 f.rootID = item.ID 499 } else { 500 f.rootID = opt.RootFolderID 501 } 502 503 // Get rootID 504 f.dirCache = dircache.New(root, f.rootID, f) 505 506 // Find the current root 507 err = f.dirCache.FindRoot(ctx, false) 508 if err != nil { 509 // Assume it is a file 510 newRoot, remote := dircache.SplitPath(root) 511 tempF := *f 512 tempF.dirCache = dircache.New(newRoot, f.rootID, &tempF) 513 tempF.root = newRoot 514 // Make new Fs which is the parent 515 err = tempF.dirCache.FindRoot(ctx, false) 516 if err != nil { 517 // No root so return old f 518 return f, nil 519 } 520 _, err := tempF.newObjectWithInfo(ctx, remote, nil) 521 if err != nil { 522 if err == fs.ErrorObjectNotFound { 523 // File doesn't exist so return old f 524 return f, nil 525 } 526 return nil, err 527 } 528 f.features.Fill(ctx, &tempF) 529 // XXX: update the old f here instead of returning tempF, since 530 // `features` were already filled with functions having *f as a receiver. 531 // See https://github.com/artpar/rclone/issues/2182 532 f.dirCache = tempF.dirCache 533 f.root = tempF.root 534 // return an error with an fs which points to the parent 535 return f, fs.ErrorIsFile 536 } 537 return f, nil 538 } 539 540 // Fill up (or reset) the buffer tokens 541 func (f *Fs) fillBufferTokens() { 542 f.bufferTokens = make(chan []byte, f.ci.Transfers) 543 for i := 0; i < f.ci.Transfers; i++ { 544 f.bufferTokens <- nil 545 } 546 } 547 548 // getUploadBlock gets a block from the pool of size chunkSize 549 func (f *Fs) getUploadBlock() []byte { 550 buf := <-f.bufferTokens 551 if buf == nil { 552 buf = make([]byte, f.opt.ChunkSize) 553 } 554 // fs.Debugf(f, "Getting upload block %p", buf) 555 return buf 556 } 557 558 // putUploadBlock returns a block to the pool of size chunkSize 559 func (f *Fs) putUploadBlock(buf []byte) { 560 buf = buf[:cap(buf)] 561 if len(buf) != int(f.opt.ChunkSize) { 562 panic("bad blocksize returned to pool") 563 } 564 // fs.Debugf(f, "Returning upload block %p", buf) 565 f.bufferTokens <- buf 566 } 567 568 // Return an Object from a path 569 // 570 // If it can't be found it returns the error fs.ErrorObjectNotFound. 571 func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.Item) (fs.Object, error) { 572 o := &Object{ 573 fs: f, 574 remote: remote, 575 } 576 var err error 577 if info != nil { 578 // Set info 579 err = o.setMetaData(info) 580 } else { 581 err = o.readMetaData(ctx) // reads info and meta, returning an error 582 } 583 if err != nil { 584 return nil, err 585 } 586 return o, nil 587 } 588 589 // NewObject finds the Object at remote. If it can't be found 590 // it returns the error fs.ErrorObjectNotFound. 591 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 592 return f.newObjectWithInfo(ctx, remote, nil) 593 } 594 595 // FindLeaf finds a directory of name leaf in the folder with ID pathID 596 func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) { 597 if pathID == "top" { 598 // Find the leaf in pathID 599 found, err = f.listAll(ctx, pathID, true, false, func(item *api.Item) bool { 600 if item.Name == leaf { 601 pathIDOut = item.ID 602 return true 603 } 604 return false 605 }) 606 return pathIDOut, found, err 607 } 608 info, err := f.readMetaDataForIDPath(ctx, pathID, leaf, true, false) 609 if err == nil { 610 found = true 611 pathIDOut = info.ID 612 } else if err == fs.ErrorDirNotFound { 613 err = nil // don't return an error if not found 614 } 615 return pathIDOut, found, err 616 } 617 618 // CreateDir makes a directory with pathID as parent and name leaf 619 func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { 620 var resp *http.Response 621 leaf = f.opt.Enc.FromStandardName(leaf) 622 var req = api.Item{ 623 Name: leaf, 624 FileName: leaf, 625 CreatedAt: time.Now(), 626 } 627 var info api.Item 628 opts := rest.Opts{ 629 Method: "POST", 630 Path: "/Items(" + pathID + ")/Folder", 631 Parameters: url.Values{ 632 "$select": {api.ListRequestSelect}, 633 "overwrite": {"false"}, 634 "passthrough": {"false"}, 635 }, 636 } 637 err = f.pacer.Call(func() (bool, error) { 638 resp, err = f.srv.CallJSON(ctx, &opts, &req, &info) 639 return shouldRetry(ctx, resp, err) 640 }) 641 if err != nil { 642 return "", fmt.Errorf("CreateDir: %w", err) 643 } 644 return info.ID, nil 645 } 646 647 // list the objects into the function supplied 648 // 649 // If directories is set it only sends directories 650 // User function to process a File item from listAll 651 // 652 // Should return true to finish processing 653 type listAllFn func(*api.Item) bool 654 655 // Lists the directory required calling the user function on each item found 656 // 657 // If the user fn ever returns true then it early exits with found = true 658 func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) { 659 opts := rest.Opts{ 660 Method: "GET", 661 Path: "/Items(" + dirID + ")/Children", 662 Parameters: url.Values{ 663 "$select": {api.ListRequestSelect}, 664 }, 665 } 666 667 var result api.ListResponse 668 var resp *http.Response 669 err = f.pacer.Call(func() (bool, error) { 670 resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) 671 return shouldRetry(ctx, resp, err) 672 }) 673 if err != nil { 674 return found, fmt.Errorf("couldn't list files: %w", err) 675 } 676 for i := range result.Value { 677 item := &result.Value[i] 678 if item.Type == api.ItemTypeFolder { 679 if filesOnly { 680 continue 681 } 682 } else if item.Type == api.ItemTypeFile { 683 if directoriesOnly { 684 continue 685 } 686 } else { 687 fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type) 688 continue 689 } 690 item.Name = f.opt.Enc.ToStandardName(item.Name) 691 if fn(item) { 692 found = true 693 break 694 } 695 } 696 697 return 698 } 699 700 // List the objects and directories in dir into entries. The 701 // entries can be returned in any order but should be for a 702 // complete directory. 703 // 704 // dir should be "" to list the root, and should not have 705 // trailing slashes. 706 // 707 // This should return ErrDirNotFound if the directory isn't 708 // found. 709 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 710 directoryID, err := f.dirCache.FindDir(ctx, dir, false) 711 if err != nil { 712 return nil, err 713 } 714 var iErr error 715 _, err = f.listAll(ctx, directoryID, false, false, func(info *api.Item) bool { 716 remote := path.Join(dir, info.Name) 717 if info.Type == api.ItemTypeFolder { 718 // cache the directory ID for later lookups 719 f.dirCache.Put(remote, info.ID) 720 d := fs.NewDir(remote, info.CreatedAt).SetID(info.ID).SetSize(info.Size).SetItems(int64(info.FileCount)) 721 entries = append(entries, d) 722 } else if info.Type == api.ItemTypeFile { 723 o, err := f.newObjectWithInfo(ctx, remote, info) 724 if err != nil { 725 iErr = err 726 return true 727 } 728 entries = append(entries, o) 729 } 730 return false 731 }) 732 if err != nil { 733 return nil, err 734 } 735 if iErr != nil { 736 return nil, iErr 737 } 738 return entries, nil 739 } 740 741 // Creates from the parameters passed in a half finished Object which 742 // must have setMetaData called on it 743 // 744 // Returns the object, leaf, directoryID and error. 745 // 746 // Used to create new objects 747 func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) { 748 // Create the directory for the object if it doesn't exist 749 leaf, directoryID, err = f.dirCache.FindPath(ctx, remote, true) 750 if err != nil { 751 return 752 } 753 // Temporary Object under construction 754 o = &Object{ 755 fs: f, 756 remote: remote, 757 } 758 return o, leaf, directoryID, nil 759 } 760 761 // Put the object 762 // 763 // Copy the reader in to the new object which is returned. 764 // 765 // The new object may have been created if an error is returned 766 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 767 existingObj, err := f.newObjectWithInfo(ctx, src.Remote(), nil) 768 switch err { 769 case nil: 770 return existingObj, existingObj.Update(ctx, in, src, options...) 771 case fs.ErrorObjectNotFound: 772 // Not found so create it 773 return f.PutUnchecked(ctx, in, src) 774 default: 775 return nil, err 776 } 777 } 778 779 // FIXMEPutStream uploads to the remote path with the modTime given of indeterminate size 780 // 781 // PutStream no longer appears to work - the streamed uploads need the 782 // size specified at the start otherwise we get this error: 783 // 784 // upload failed: file size does not match (-2) 785 func (f *Fs) FIXMEPutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 786 return f.Put(ctx, in, src, options...) 787 } 788 789 // PutUnchecked the object into the container 790 // 791 // This will produce an error if the object already exists. 792 // 793 // Copy the reader in to the new object which is returned. 794 // 795 // The new object may have been created if an error is returned 796 func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 797 remote := src.Remote() 798 size := src.Size() 799 modTime := src.ModTime(ctx) 800 801 o, _, _, err := f.createObject(ctx, remote, modTime, size) 802 if err != nil { 803 return nil, err 804 } 805 return o, o.Update(ctx, in, src, options...) 806 } 807 808 // Mkdir creates the container if it doesn't exist 809 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 810 _, err := f.dirCache.FindDir(ctx, dir, true) 811 return err 812 } 813 814 // purgeCheck removes the directory, if check is set then it refuses 815 // to do so if it has anything in 816 func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error { 817 root := path.Join(f.root, dir) 818 if root == "" { 819 return errors.New("can't purge root directory") 820 } 821 dc := f.dirCache 822 rootID, err := dc.FindDir(ctx, dir, false) 823 if err != nil { 824 return err 825 } 826 827 // need to check if empty as it will delete recursively by default 828 if check { 829 found, err := f.listAll(ctx, rootID, false, false, func(item *api.Item) bool { 830 return true 831 }) 832 if err != nil { 833 return fmt.Errorf("purgeCheck: %w", err) 834 } 835 if found { 836 return fs.ErrorDirectoryNotEmpty 837 } 838 } 839 840 err = f.remove(ctx, rootID) 841 f.dirCache.FlushDir(dir) 842 if err != nil { 843 return err 844 } 845 return nil 846 } 847 848 // Rmdir deletes the root folder 849 // 850 // Returns an error if it isn't empty 851 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 852 return f.purgeCheck(ctx, dir, true) 853 } 854 855 // Precision return the precision of this Fs 856 func (f *Fs) Precision() time.Duration { 857 // sharefile returns times accurate to the millisecond, but 858 // for some reason these seem only accurate 2ms. 859 // updateItem seems to only set times accurate to 1 second though. 860 return time.Second // this doesn't appear to be documented anywhere 861 } 862 863 // Purge deletes all the files and the container 864 // 865 // Optional interface: Only implement this if you have a way of 866 // deleting all the files quicker than just running Remove() on the 867 // result of List() 868 func (f *Fs) Purge(ctx context.Context, dir string) error { 869 return f.purgeCheck(ctx, dir, false) 870 } 871 872 // updateItem patches a file or folder 873 // 874 // if leaf = "" or directoryID = "" or modTime == nil then it will be 875 // left alone 876 // 877 // Note that this seems to work by renaming first, then moving to a 878 // new directory which means that it can overwrite existing objects 879 // :-( 880 func (f *Fs) updateItem(ctx context.Context, id, leaf, directoryID string, modTime *time.Time) (info *api.Item, err error) { 881 // Move the object 882 opts := rest.Opts{ 883 Method: "PATCH", 884 Path: "/Items(" + id + ")", 885 Parameters: url.Values{ 886 "$select": {api.ListRequestSelect}, 887 "overwrite": {"false"}, 888 }, 889 } 890 leaf = f.opt.Enc.FromStandardName(leaf) 891 // FIXME this appears to be a bug in the API 892 // 893 // If you set the modified time via PATCH then the server 894 // appears to parse it as a local time for America/New_York 895 // 896 // However if you set it when uploading the file then it is fine... 897 // 898 // Also it only sets the time to 1 second resolution where it 899 // uses 1ms resolution elsewhere 900 if modTime != nil && f.location != nil { 901 newTime := modTime.In(f.location) 902 isoTime := newTime.Format(time.RFC3339Nano) 903 // Chop TZ -05:00 off the end and replace with Z 904 isoTime = isoTime[:len(isoTime)-6] + "Z" 905 // Parse it back into a time 906 newModTime, err := time.Parse(time.RFC3339Nano, isoTime) 907 if err != nil { 908 return nil, fmt.Errorf("updateItem: time parse: %w", err) 909 } 910 modTime = &newModTime 911 } 912 update := api.UpdateItemRequest{ 913 Name: leaf, 914 FileName: leaf, 915 ModifiedAt: modTime, 916 } 917 if directoryID != "" { 918 update.Parent = &api.Parent{ 919 ID: directoryID, 920 } 921 } 922 var resp *http.Response 923 err = f.pacer.Call(func() (bool, error) { 924 resp, err = f.srv.CallJSON(ctx, &opts, &update, &info) 925 return shouldRetry(ctx, resp, err) 926 }) 927 if err != nil { 928 return nil, err 929 } 930 return info, nil 931 } 932 933 // move a file or folder 934 // 935 // This is complicated by the fact that we can't use updateItem to move 936 // to a different directory AND rename at the same time as it can 937 // overwrite files in the source directory. 938 func (f *Fs) move(ctx context.Context, isFile bool, id, oldLeaf, newLeaf, oldDirectoryID, newDirectoryID string) (item *api.Item, err error) { 939 // To demonstrate bug 940 // item, err = f.updateItem(ctx, id, newLeaf, newDirectoryID, nil) 941 // if err != nil { 942 // return nil, fmt.Errorf("Move rename leaf: %w", err) 943 // } 944 // return item, nil 945 doRenameLeaf := oldLeaf != newLeaf 946 doMove := oldDirectoryID != newDirectoryID 947 948 // Now rename the leaf to a temporary name if we are moving to 949 // another directory to make sure we don't overwrite something 950 // in the source directory by accident 951 if doRenameLeaf && doMove { 952 tmpLeaf := newLeaf + "." + random.String(8) 953 item, err = f.updateItem(ctx, id, tmpLeaf, "", nil) 954 if err != nil { 955 return nil, fmt.Errorf("Move rename leaf: %w", err) 956 } 957 } 958 959 // Move the object to a new directory (with the existing name) 960 // if required 961 if doMove { 962 item, err = f.updateItem(ctx, id, "", newDirectoryID, nil) 963 if err != nil { 964 return nil, fmt.Errorf("Move directory: %w", err) 965 } 966 } 967 968 // Rename the leaf to its final name if required 969 if doRenameLeaf { 970 item, err = f.updateItem(ctx, id, newLeaf, "", nil) 971 if err != nil { 972 return nil, fmt.Errorf("Move rename leaf: %w", err) 973 } 974 } 975 976 return item, nil 977 } 978 979 // Move src to this remote using server-side move operations. 980 // 981 // This is stored with the remote path given. 982 // 983 // It returns the destination Object and a possible error. 984 // 985 // Will only be called if src.Fs().Name() == f.Name() 986 // 987 // If it isn't possible then return fs.ErrorCantMove 988 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 989 srcObj, ok := src.(*Object) 990 if !ok { 991 fs.Debugf(src, "Can't move - not same remote type") 992 return nil, fs.ErrorCantMove 993 } 994 995 // Find ID of src parent, not creating subdirs 996 srcLeaf, srcParentID, err := srcObj.fs.dirCache.FindPath(ctx, srcObj.remote, false) 997 if err != nil { 998 return nil, err 999 } 1000 1001 // Create temporary object 1002 dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 1003 if err != nil { 1004 return nil, err 1005 } 1006 1007 // Do the move 1008 info, err := f.move(ctx, true, srcObj.id, srcLeaf, leaf, srcParentID, directoryID) 1009 if err != nil { 1010 return nil, err 1011 } 1012 1013 err = dstObj.setMetaData(info) 1014 if err != nil { 1015 return nil, err 1016 } 1017 return dstObj, nil 1018 } 1019 1020 // DirMove moves src, srcRemote to this remote at dstRemote 1021 // using server-side move operations. 1022 // 1023 // Will only be called if src.Fs().Name() == f.Name() 1024 // 1025 // If it isn't possible then return fs.ErrorCantDirMove 1026 // 1027 // If destination exists then return fs.ErrorDirExists 1028 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 1029 srcFs, ok := src.(*Fs) 1030 if !ok { 1031 fs.Debugf(srcFs, "Can't move directory - not same remote type") 1032 return fs.ErrorCantDirMove 1033 } 1034 1035 srcID, srcDirectoryID, srcLeaf, dstDirectoryID, dstLeaf, err := f.dirCache.DirMove(ctx, srcFs.dirCache, srcFs.root, srcRemote, f.root, dstRemote) 1036 if err != nil { 1037 return err 1038 } 1039 1040 // Do the move 1041 _, err = f.move(ctx, false, srcID, srcLeaf, dstLeaf, srcDirectoryID, dstDirectoryID) 1042 if err != nil { 1043 return err 1044 } 1045 srcFs.dirCache.FlushDir(srcRemote) 1046 return nil 1047 } 1048 1049 // Copy src to this remote using server-side copy operations. 1050 // 1051 // This is stored with the remote path given. 1052 // 1053 // It returns the destination Object and a possible error. 1054 // 1055 // Will only be called if src.Fs().Name() == f.Name() 1056 // 1057 // If it isn't possible then return fs.ErrorCantCopy 1058 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Object, err error) { 1059 srcObj, ok := src.(*Object) 1060 if !ok { 1061 fs.Debugf(src, "Can't copy - not same remote type") 1062 return nil, fs.ErrorCantCopy 1063 } 1064 1065 err = srcObj.readMetaData(ctx) 1066 if err != nil { 1067 return nil, err 1068 } 1069 1070 // Find ID of src parent, not creating subdirs 1071 srcLeaf, srcParentID, err := srcObj.fs.dirCache.FindPath(ctx, srcObj.remote, false) 1072 if err != nil { 1073 return nil, err 1074 } 1075 srcLeaf = f.opt.Enc.FromStandardName(srcLeaf) 1076 _ = srcParentID 1077 1078 // Create temporary object 1079 dstObj, dstLeaf, dstParentID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 1080 if err != nil { 1081 return nil, err 1082 } 1083 dstLeaf = f.opt.Enc.FromStandardName(dstLeaf) 1084 1085 sameName := strings.EqualFold(srcLeaf, dstLeaf) 1086 if sameName && srcParentID == dstParentID { 1087 return nil, fmt.Errorf("copy: can't copy to a file in the same directory whose name only differs in case: %q vs %q", srcLeaf, dstLeaf) 1088 } 1089 1090 // Discover whether we can just copy directly or not 1091 directCopy := false 1092 if sameName { 1093 // if copying to same name can copy directly 1094 directCopy = true 1095 } else { 1096 // if (dstParentID, srcLeaf) does not exist then can 1097 // Copy then Rename without fear of overwriting 1098 // something 1099 _, err := f.readMetaDataForIDPath(ctx, dstParentID, srcLeaf, false, false) 1100 if err == fs.ErrorObjectNotFound || err == fs.ErrorDirNotFound { 1101 directCopy = true 1102 } else if err != nil { 1103 return nil, fmt.Errorf("copy: failed to examine destination dir: %w", err) 1104 //} else { 1105 // otherwise need to copy via a temporary directory 1106 } 1107 } 1108 1109 // Copy direct to destination unless !directCopy in which case 1110 // copy via a temporary directory 1111 copyTargetDirID := dstParentID 1112 if !directCopy { 1113 // Create a temporary directory to copy the object in to 1114 tmpDir := "rclone-temp-dir-" + random.String(16) 1115 err = f.Mkdir(ctx, tmpDir) 1116 if err != nil { 1117 return nil, fmt.Errorf("copy: failed to make temp dir: %w", err) 1118 } 1119 defer func() { 1120 rmdirErr := f.Rmdir(ctx, tmpDir) 1121 if rmdirErr != nil && err == nil { 1122 err = fmt.Errorf("copy: failed to remove temp dir: %w", rmdirErr) 1123 } 1124 }() 1125 tmpDirID, err := f.dirCache.FindDir(ctx, tmpDir, false) 1126 if err != nil { 1127 return nil, fmt.Errorf("copy: failed to find temp dir: %w", err) 1128 } 1129 copyTargetDirID = tmpDirID 1130 } 1131 1132 // Copy the object 1133 opts := rest.Opts{ 1134 Method: "POST", 1135 Path: "/Items(" + srcObj.id + ")/Copy", 1136 Parameters: url.Values{ 1137 "$select": {api.ListRequestSelect}, 1138 "overwrite": {"false"}, 1139 "targetid": {copyTargetDirID}, 1140 }, 1141 } 1142 var resp *http.Response 1143 var info *api.Item 1144 err = f.pacer.Call(func() (bool, error) { 1145 resp, err = f.srv.CallJSON(ctx, &opts, nil, &info) 1146 return shouldRetry(ctx, resp, err) 1147 }) 1148 if err != nil { 1149 return nil, err 1150 } 1151 1152 // Rename into the correct name and directory if required and 1153 // set the modtime since the copy doesn't preserve it 1154 var updateParentID, updateLeaf string // only set these if necessary 1155 if srcLeaf != dstLeaf { 1156 updateLeaf = dstLeaf 1157 } 1158 if !directCopy { 1159 updateParentID = dstParentID 1160 } 1161 // set new modtime regardless 1162 info, err = f.updateItem(ctx, info.ID, updateLeaf, updateParentID, &srcObj.modTime) 1163 if err != nil { 1164 return nil, err 1165 } 1166 err = dstObj.setMetaData(info) 1167 if err != nil { 1168 return nil, err 1169 } 1170 return dstObj, nil 1171 } 1172 1173 // DirCacheFlush resets the directory cache - used in testing as an 1174 // optional interface 1175 func (f *Fs) DirCacheFlush() { 1176 f.dirCache.ResetRoot() 1177 } 1178 1179 // Shutdown shutdown the fs 1180 func (f *Fs) Shutdown(ctx context.Context) error { 1181 f.tokenRenewer.Shutdown() 1182 return nil 1183 } 1184 1185 // Hashes returns the supported hash sets. 1186 func (f *Fs) Hashes() hash.Set { 1187 return hash.Set(hash.MD5) 1188 } 1189 1190 // ------------------------------------------------------------ 1191 1192 // Fs returns the parent Fs 1193 func (o *Object) Fs() fs.Info { 1194 return o.fs 1195 } 1196 1197 // Return a string version 1198 func (o *Object) String() string { 1199 if o == nil { 1200 return "<nil>" 1201 } 1202 return o.remote 1203 } 1204 1205 // Remote returns the remote path 1206 func (o *Object) Remote() string { 1207 return o.remote 1208 } 1209 1210 // Hash returns the SHA-1 of an object returning a lowercase hex string 1211 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 1212 if t != hash.MD5 { 1213 return "", hash.ErrUnsupported 1214 } 1215 err := o.readMetaData(ctx) 1216 if err != nil { 1217 return "", err 1218 } 1219 return o.md5, nil 1220 } 1221 1222 // Size returns the size of an object in bytes 1223 func (o *Object) Size() int64 { 1224 err := o.readMetaData(context.TODO()) 1225 if err != nil { 1226 fs.Logf(o, "Failed to read metadata: %v", err) 1227 return 0 1228 } 1229 return o.size 1230 } 1231 1232 // setMetaData sets the metadata from info 1233 func (o *Object) setMetaData(info *api.Item) (err error) { 1234 if info.Type != api.ItemTypeFile { 1235 return fmt.Errorf("%q is %q: %w", o.remote, info.Type, fs.ErrorNotAFile) 1236 } 1237 o.hasMetaData = true 1238 o.size = info.Size 1239 if !info.ModifiedAt.IsZero() { 1240 o.modTime = info.ModifiedAt 1241 } else { 1242 o.modTime = info.CreatedAt 1243 } 1244 o.id = info.ID 1245 o.md5 = info.Hash 1246 return nil 1247 } 1248 1249 // readMetaData gets the metadata if it hasn't already been fetched 1250 // 1251 // it also sets the info 1252 func (o *Object) readMetaData(ctx context.Context) (err error) { 1253 if o.hasMetaData { 1254 return nil 1255 } 1256 var info *api.Item 1257 if o.id != "" { 1258 info, err = o.fs.readMetaDataForID(ctx, o.id, false, true) 1259 } else { 1260 info, err = o.fs.readMetaDataForPath(ctx, o.remote, false, true) 1261 } 1262 if err != nil { 1263 return err 1264 } 1265 return o.setMetaData(info) 1266 } 1267 1268 // ModTime returns the modification time of the object 1269 // 1270 // It attempts to read the objects mtime and if that isn't present the 1271 // LastModified returned in the http headers 1272 func (o *Object) ModTime(ctx context.Context) time.Time { 1273 err := o.readMetaData(ctx) 1274 if err != nil { 1275 fs.Logf(o, "Failed to read metadata: %v", err) 1276 return time.Now() 1277 } 1278 return o.modTime 1279 } 1280 1281 // SetModTime sets the modification time of the local fs object 1282 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) (err error) { 1283 info, err := o.fs.updateItem(ctx, o.id, "", "", &modTime) 1284 if err != nil { 1285 return err 1286 } 1287 err = o.setMetaData(info) 1288 if err != nil { 1289 return err 1290 } 1291 return nil 1292 } 1293 1294 // Storable returns a boolean showing whether this object storable 1295 func (o *Object) Storable() bool { 1296 return true 1297 } 1298 1299 // Open an object for read 1300 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 1301 opts := rest.Opts{ 1302 Method: "GET", 1303 Path: "/Items(" + o.id + ")/Download", 1304 Parameters: url.Values{ 1305 "redirect": {"false"}, 1306 }, 1307 } 1308 var resp *http.Response 1309 var dl api.DownloadSpecification 1310 err = o.fs.pacer.Call(func() (bool, error) { 1311 resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl) 1312 return shouldRetry(ctx, resp, err) 1313 }) 1314 if err != nil { 1315 return nil, fmt.Errorf("open: fetch download specification: %w", err) 1316 } 1317 1318 fs.FixRangeOption(options, o.size) 1319 opts = rest.Opts{ 1320 Path: "", 1321 RootURL: dl.URL, 1322 Method: "GET", 1323 Options: options, 1324 } 1325 err = o.fs.pacer.Call(func() (bool, error) { 1326 resp, err = o.fs.srv.Call(ctx, &opts) 1327 return shouldRetry(ctx, resp, err) 1328 }) 1329 if err != nil { 1330 return nil, fmt.Errorf("open: %w", err) 1331 } 1332 return resp.Body, err 1333 } 1334 1335 // Update the object with the contents of the io.Reader, modTime and size 1336 // 1337 // If existing is set then it updates the object rather than creating a new one. 1338 // 1339 // The new object may have been created if an error is returned 1340 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 1341 remote := o.Remote() 1342 size := src.Size() 1343 modTime := src.ModTime(ctx) 1344 isLargeFile := size < 0 || size > int64(o.fs.opt.UploadCutoff) 1345 1346 // Create the directory for the object if it doesn't exist 1347 leaf, directoryID, err := o.fs.dirCache.FindPath(ctx, remote, true) 1348 if err != nil { 1349 return err 1350 } 1351 leaf = o.fs.opt.Enc.FromStandardName(leaf) 1352 var req = api.UploadRequest{ 1353 Method: "standard", 1354 Raw: true, 1355 Filename: leaf, 1356 Overwrite: true, 1357 CreatedDate: modTime, 1358 ModifiedDate: modTime, 1359 Tool: o.fs.ci.UserAgent, 1360 } 1361 1362 if isLargeFile { 1363 if size < 0 { 1364 // For files of indeterminate size, use streamed 1365 req.Method = "streamed" 1366 } else { 1367 // otherwise use threaded which is more efficient 1368 req.Method = "threaded" 1369 req.ThreadCount = &o.fs.ci.Transfers 1370 req.Filesize = &size 1371 } 1372 } 1373 1374 var resp *http.Response 1375 var info api.UploadSpecification 1376 opts := rest.Opts{ 1377 Method: "POST", 1378 Path: "/Items(" + directoryID + ")/Upload2", 1379 Options: options, 1380 } 1381 err = o.fs.pacer.Call(func() (bool, error) { 1382 resp, err = o.fs.srv.CallJSON(ctx, &opts, &req, &info) 1383 return shouldRetry(ctx, resp, err) 1384 }) 1385 if err != nil { 1386 return fmt.Errorf("upload get specification: %w", err) 1387 } 1388 1389 // If file is large then upload in parts 1390 if isLargeFile { 1391 up, err := o.fs.newLargeUpload(ctx, o, in, src, &info) 1392 if err != nil { 1393 return err 1394 } 1395 return up.Upload(ctx) 1396 } 1397 1398 // Single part upload 1399 opts = rest.Opts{ 1400 Method: "POST", 1401 RootURL: info.ChunkURI + "&fmt=json", 1402 Body: in, 1403 ContentLength: &size, 1404 } 1405 var finish api.UploadFinishResponse 1406 err = o.fs.pacer.CallNoRetry(func() (bool, error) { 1407 resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &finish) 1408 return shouldRetry(ctx, resp, err) 1409 }) 1410 if err != nil { 1411 return fmt.Errorf("upload file: %w", err) 1412 } 1413 return o.checkUploadResponse(ctx, &finish) 1414 } 1415 1416 // Check the upload response and update the metadata on the object 1417 func (o *Object) checkUploadResponse(ctx context.Context, finish *api.UploadFinishResponse) (err error) { 1418 // Find returned ID 1419 id, err := finish.ID() 1420 if err != nil { 1421 return err 1422 } 1423 1424 // Read metadata 1425 o.id = id 1426 o.hasMetaData = false 1427 return o.readMetaData(ctx) 1428 } 1429 1430 // Remove an object by ID 1431 func (f *Fs) remove(ctx context.Context, id string) (err error) { 1432 opts := rest.Opts{ 1433 Method: "DELETE", 1434 Path: "/Items(" + id + ")", 1435 Parameters: url.Values{ 1436 "singleversion": {"false"}, 1437 "forceSync": {"true"}, 1438 }, 1439 NoResponse: true, 1440 } 1441 var resp *http.Response 1442 err = f.pacer.Call(func() (bool, error) { 1443 resp, err = f.srv.Call(ctx, &opts) 1444 return shouldRetry(ctx, resp, err) 1445 }) 1446 if err != nil { 1447 return fmt.Errorf("remove: %w", err) 1448 } 1449 return nil 1450 } 1451 1452 // Remove an object 1453 func (o *Object) Remove(ctx context.Context) error { 1454 err := o.readMetaData(ctx) 1455 if err != nil { 1456 return fmt.Errorf("Remove: Failed to read metadata: %w", err) 1457 } 1458 return o.fs.remove(ctx, o.id) 1459 } 1460 1461 // ID returns the ID of the Object if known, or "" if not 1462 func (o *Object) ID() string { 1463 return o.id 1464 } 1465 1466 // Check the interfaces are satisfied 1467 var ( 1468 _ fs.Fs = (*Fs)(nil) 1469 _ fs.Purger = (*Fs)(nil) 1470 _ fs.Mover = (*Fs)(nil) 1471 _ fs.DirMover = (*Fs)(nil) 1472 _ fs.Copier = (*Fs)(nil) 1473 // _ fs.PutStreamer = (*Fs)(nil) 1474 _ fs.DirCacheFlusher = (*Fs)(nil) 1475 _ fs.Shutdowner = (*Fs)(nil) 1476 _ fs.Object = (*Object)(nil) 1477 _ fs.IDer = (*Object)(nil) 1478 )