github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/box/box.go (about) 1 // Package box provides an interface to the Box 2 // object storage system. 3 package box 4 5 // FIXME Box only supports file names of 255 characters or less. Names 6 // that will not be supported are those that contain non-printable 7 // ascii, / or \, names with trailing spaces, and the special names 8 // “.” and “..”. 9 10 // FIXME box can copy a directory 11 12 import ( 13 "context" 14 "fmt" 15 "io" 16 "log" 17 "net/http" 18 "net/url" 19 "path" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/ncw/rclone/backend/box/api" 25 "github.com/ncw/rclone/fs" 26 "github.com/ncw/rclone/fs/config" 27 "github.com/ncw/rclone/fs/config/configmap" 28 "github.com/ncw/rclone/fs/config/configstruct" 29 "github.com/ncw/rclone/fs/config/obscure" 30 "github.com/ncw/rclone/fs/fserrors" 31 "github.com/ncw/rclone/fs/hash" 32 "github.com/ncw/rclone/lib/dircache" 33 "github.com/ncw/rclone/lib/oauthutil" 34 "github.com/ncw/rclone/lib/pacer" 35 "github.com/ncw/rclone/lib/rest" 36 "github.com/pkg/errors" 37 "golang.org/x/oauth2" 38 ) 39 40 const ( 41 rcloneClientID = "d0374ba6pgmaguie02ge15sv1mllndho" 42 rcloneEncryptedClientSecret = "sYbJYm99WB8jzeaLPU0OPDMJKIkZvD2qOn3SyEMfiJr03RdtDt3xcZEIudRhbIDL" 43 minSleep = 10 * time.Millisecond 44 maxSleep = 2 * time.Second 45 decayConstant = 2 // bigger for slower decay, exponential 46 rootID = "0" // ID of root folder is always this 47 rootURL = "https://api.box.com/2.0" 48 uploadURL = "https://upload.box.com/api/2.0" 49 listChunks = 1000 // chunk size to read directory listings 50 minUploadCutoff = 50000000 // upload cutoff can be no lower than this 51 defaultUploadCutoff = 50 * 1024 * 1024 52 ) 53 54 // Globals 55 var ( 56 // Description of how to auth for this app 57 oauthConfig = &oauth2.Config{ 58 Scopes: nil, 59 Endpoint: oauth2.Endpoint{ 60 AuthURL: "https://app.box.com/api/oauth2/authorize", 61 TokenURL: "https://app.box.com/api/oauth2/token", 62 }, 63 ClientID: rcloneClientID, 64 ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret), 65 RedirectURL: oauthutil.RedirectURL, 66 } 67 ) 68 69 // Register with Fs 70 func init() { 71 fs.Register(&fs.RegInfo{ 72 Name: "box", 73 Description: "Box", 74 NewFs: NewFs, 75 Config: func(name string, m configmap.Mapper) { 76 err := oauthutil.Config("box", name, m, oauthConfig) 77 if err != nil { 78 log.Fatalf("Failed to configure token: %v", err) 79 } 80 }, 81 Options: []fs.Option{{ 82 Name: config.ConfigClientID, 83 Help: "Box App Client Id.\nLeave blank normally.", 84 }, { 85 Name: config.ConfigClientSecret, 86 Help: "Box App Client Secret\nLeave blank normally.", 87 }, { 88 Name: "upload_cutoff", 89 Help: "Cutoff for switching to multipart upload (>= 50MB).", 90 Default: fs.SizeSuffix(defaultUploadCutoff), 91 Advanced: true, 92 }, { 93 Name: "commit_retries", 94 Help: "Max number of times to try committing a multipart file.", 95 Default: 100, 96 Advanced: true, 97 }}, 98 }) 99 } 100 101 // Options defines the configuration for this backend 102 type Options struct { 103 UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` 104 CommitRetries int `config:"commit_retries"` 105 } 106 107 // Fs represents a remote box 108 type Fs struct { 109 name string // name of this remote 110 root string // the path we are working on 111 opt Options // parsed options 112 features *fs.Features // optional features 113 srv *rest.Client // the connection to the one drive server 114 dirCache *dircache.DirCache // Map of directory path to directory id 115 pacer *fs.Pacer // pacer for API calls 116 tokenRenewer *oauthutil.Renew // renew the token on expiry 117 uploadToken *pacer.TokenDispenser // control concurrency 118 } 119 120 // Object describes a box object 121 // 122 // Will definitely have info but maybe not meta 123 type Object struct { 124 fs *Fs // what this object is part of 125 remote string // The remote path 126 hasMetaData bool // whether info below has been set 127 size int64 // size of the object 128 modTime time.Time // modification time of the object 129 id string // ID of the object 130 publicLink string // Public Link for the object 131 sha1 string // SHA-1 of the object content 132 } 133 134 // ------------------------------------------------------------ 135 136 // Name of the remote (as passed into NewFs) 137 func (f *Fs) Name() string { 138 return f.name 139 } 140 141 // Root of the remote (as passed into NewFs) 142 func (f *Fs) Root() string { 143 return f.root 144 } 145 146 // String converts this Fs to a string 147 func (f *Fs) String() string { 148 return fmt.Sprintf("box root '%s'", f.root) 149 } 150 151 // Features returns the optional features of this Fs 152 func (f *Fs) Features() *fs.Features { 153 return f.features 154 } 155 156 // parsePath parses an box 'url' 157 func parsePath(path string) (root string) { 158 root = strings.Trim(path, "/") 159 return 160 } 161 162 // retryErrorCodes is a slice of error codes that we will retry 163 var retryErrorCodes = []int{ 164 429, // Too Many Requests. 165 500, // Internal Server Error 166 502, // Bad Gateway 167 503, // Service Unavailable 168 504, // Gateway Timeout 169 509, // Bandwidth Limit Exceeded 170 } 171 172 // shouldRetry returns a boolean as to whether this resp and err 173 // deserve to be retried. It returns the err as a convenience 174 func shouldRetry(resp *http.Response, err error) (bool, error) { 175 authRetry := false 176 177 if resp != nil && resp.StatusCode == 401 && len(resp.Header["Www-Authenticate"]) == 1 && strings.Index(resp.Header["Www-Authenticate"][0], "expired_token") >= 0 { 178 authRetry = true 179 fs.Debugf(nil, "Should retry: %v", err) 180 } 181 return authRetry || fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 182 } 183 184 // substitute reserved characters for box 185 func replaceReservedChars(x string) string { 186 // Backslash for FULLWIDTH REVERSE SOLIDUS 187 return strings.Replace(x, "\\", "\", -1) 188 } 189 190 // restore reserved characters for box 191 func restoreReservedChars(x string) string { 192 // FULLWIDTH REVERSE SOLIDUS for Backslash 193 return strings.Replace(x, "\", "\\", -1) 194 } 195 196 // readMetaDataForPath reads the metadata from the path 197 func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.Item, err error) { 198 // defer fs.Trace(f, "path=%q", path)("info=%+v, err=%v", &info, &err) 199 leaf, directoryID, err := f.dirCache.FindRootAndPath(ctx, path, false) 200 if err != nil { 201 if err == fs.ErrorDirNotFound { 202 return nil, fs.ErrorObjectNotFound 203 } 204 return nil, err 205 } 206 207 found, err := f.listAll(directoryID, false, true, func(item *api.Item) bool { 208 if item.Name == leaf { 209 info = item 210 return true 211 } 212 return false 213 }) 214 if err != nil { 215 return nil, err 216 } 217 if !found { 218 return nil, fs.ErrorObjectNotFound 219 } 220 return info, nil 221 } 222 223 // errorHandler parses a non 2xx error response into an error 224 func errorHandler(resp *http.Response) error { 225 // Decode error response 226 errResponse := new(api.Error) 227 err := rest.DecodeJSON(resp, &errResponse) 228 if err != nil { 229 fs.Debugf(nil, "Couldn't decode error response: %v", err) 230 } 231 if errResponse.Code == "" { 232 errResponse.Code = resp.Status 233 } 234 if errResponse.Status == 0 { 235 errResponse.Status = resp.StatusCode 236 } 237 return errResponse 238 } 239 240 // NewFs constructs an Fs from the path, container:path 241 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 242 ctx := context.Background() 243 // Parse config into Options struct 244 opt := new(Options) 245 err := configstruct.Set(m, opt) 246 if err != nil { 247 return nil, err 248 } 249 250 if opt.UploadCutoff < minUploadCutoff { 251 return nil, errors.Errorf("box: upload cutoff (%v) must be greater than equal to %v", opt.UploadCutoff, fs.SizeSuffix(minUploadCutoff)) 252 } 253 254 root = parsePath(root) 255 oAuthClient, ts, err := oauthutil.NewClient(name, m, oauthConfig) 256 if err != nil { 257 return nil, errors.Wrap(err, "failed to configure Box") 258 } 259 260 f := &Fs{ 261 name: name, 262 root: root, 263 opt: *opt, 264 srv: rest.NewClient(oAuthClient).SetRoot(rootURL), 265 pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), 266 uploadToken: pacer.NewTokenDispenser(fs.Config.Transfers), 267 } 268 f.features = (&fs.Features{ 269 CaseInsensitive: true, 270 CanHaveEmptyDirectories: true, 271 }).Fill(f) 272 f.srv.SetErrorHandler(errorHandler) 273 274 // Renew the token in the background 275 f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error { 276 _, err := f.readMetaDataForPath(ctx, "") 277 return err 278 }) 279 280 // Get rootID 281 f.dirCache = dircache.New(root, rootID, f) 282 283 // Find the current root 284 err = f.dirCache.FindRoot(ctx, false) 285 if err != nil { 286 // Assume it is a file 287 newRoot, remote := dircache.SplitPath(root) 288 tempF := *f 289 tempF.dirCache = dircache.New(newRoot, rootID, &tempF) 290 tempF.root = newRoot 291 // Make new Fs which is the parent 292 err = tempF.dirCache.FindRoot(ctx, false) 293 if err != nil { 294 // No root so return old f 295 return f, nil 296 } 297 _, err := tempF.newObjectWithInfo(ctx, remote, nil) 298 if err != nil { 299 if err == fs.ErrorObjectNotFound { 300 // File doesn't exist so return old f 301 return f, nil 302 } 303 return nil, err 304 } 305 f.features.Fill(&tempF) 306 // XXX: update the old f here instead of returning tempF, since 307 // `features` were already filled with functions having *f as a receiver. 308 // See https://github.com/ncw/rclone/issues/2182 309 f.dirCache = tempF.dirCache 310 f.root = tempF.root 311 // return an error with an fs which points to the parent 312 return f, fs.ErrorIsFile 313 } 314 return f, nil 315 } 316 317 // rootSlash returns root with a slash on if it is empty, otherwise empty string 318 func (f *Fs) rootSlash() string { 319 if f.root == "" { 320 return f.root 321 } 322 return f.root + "/" 323 } 324 325 // Return an Object from a path 326 // 327 // If it can't be found it returns the error fs.ErrorObjectNotFound. 328 func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.Item) (fs.Object, error) { 329 o := &Object{ 330 fs: f, 331 remote: remote, 332 } 333 var err error 334 if info != nil { 335 // Set info 336 err = o.setMetaData(info) 337 } else { 338 err = o.readMetaData(ctx) // reads info and meta, returning an error 339 } 340 if err != nil { 341 return nil, err 342 } 343 return o, nil 344 } 345 346 // NewObject finds the Object at remote. If it can't be found 347 // it returns the error fs.ErrorObjectNotFound. 348 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 349 return f.newObjectWithInfo(ctx, remote, nil) 350 } 351 352 // FindLeaf finds a directory of name leaf in the folder with ID pathID 353 func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) { 354 // Find the leaf in pathID 355 found, err = f.listAll(pathID, true, false, func(item *api.Item) bool { 356 if item.Name == leaf { 357 pathIDOut = item.ID 358 return true 359 } 360 return false 361 }) 362 return pathIDOut, found, err 363 } 364 365 // fieldsValue creates a url.Values with fields set to those in api.Item 366 func fieldsValue() url.Values { 367 values := url.Values{} 368 values.Set("fields", api.ItemFields) 369 return values 370 } 371 372 // CreateDir makes a directory with pathID as parent and name leaf 373 func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { 374 // fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, leaf) 375 var resp *http.Response 376 var info *api.Item 377 opts := rest.Opts{ 378 Method: "POST", 379 Path: "/folders", 380 Parameters: fieldsValue(), 381 } 382 mkdir := api.CreateFolder{ 383 Name: replaceReservedChars(leaf), 384 Parent: api.Parent{ 385 ID: pathID, 386 }, 387 } 388 err = f.pacer.Call(func() (bool, error) { 389 resp, err = f.srv.CallJSON(&opts, &mkdir, &info) 390 return shouldRetry(resp, err) 391 }) 392 if err != nil { 393 //fmt.Printf("...Error %v\n", err) 394 return "", err 395 } 396 // fmt.Printf("...Id %q\n", *info.Id) 397 return info.ID, nil 398 } 399 400 // list the objects into the function supplied 401 // 402 // If directories is set it only sends directories 403 // User function to process a File item from listAll 404 // 405 // Should return true to finish processing 406 type listAllFn func(*api.Item) bool 407 408 // Lists the directory required calling the user function on each item found 409 // 410 // If the user fn ever returns true then it early exits with found = true 411 func (f *Fs) listAll(dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) { 412 opts := rest.Opts{ 413 Method: "GET", 414 Path: "/folders/" + dirID + "/items", 415 Parameters: fieldsValue(), 416 } 417 opts.Parameters.Set("limit", strconv.Itoa(listChunks)) 418 offset := 0 419 OUTER: 420 for { 421 opts.Parameters.Set("offset", strconv.Itoa(offset)) 422 423 var result api.FolderItems 424 var resp *http.Response 425 err = f.pacer.Call(func() (bool, error) { 426 resp, err = f.srv.CallJSON(&opts, nil, &result) 427 return shouldRetry(resp, err) 428 }) 429 if err != nil { 430 return found, errors.Wrap(err, "couldn't list files") 431 } 432 for i := range result.Entries { 433 item := &result.Entries[i] 434 if item.Type == api.ItemTypeFolder { 435 if filesOnly { 436 continue 437 } 438 } else if item.Type == api.ItemTypeFile { 439 if directoriesOnly { 440 continue 441 } 442 } else { 443 fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type) 444 continue 445 } 446 if item.ItemStatus != api.ItemStatusActive { 447 continue 448 } 449 item.Name = restoreReservedChars(item.Name) 450 if fn(item) { 451 found = true 452 break OUTER 453 } 454 } 455 offset += result.Limit 456 if offset >= result.TotalCount { 457 break 458 } 459 } 460 return 461 } 462 463 // List the objects and directories in dir into entries. The 464 // entries can be returned in any order but should be for a 465 // complete directory. 466 // 467 // dir should be "" to list the root, and should not have 468 // trailing slashes. 469 // 470 // This should return ErrDirNotFound if the directory isn't 471 // found. 472 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 473 err = f.dirCache.FindRoot(ctx, false) 474 if err != nil { 475 return nil, err 476 } 477 directoryID, err := f.dirCache.FindDir(ctx, dir, false) 478 if err != nil { 479 return nil, err 480 } 481 var iErr error 482 _, err = f.listAll(directoryID, false, false, func(info *api.Item) bool { 483 remote := path.Join(dir, info.Name) 484 if info.Type == api.ItemTypeFolder { 485 // cache the directory ID for later lookups 486 f.dirCache.Put(remote, info.ID) 487 d := fs.NewDir(remote, info.ModTime()).SetID(info.ID) 488 // FIXME more info from dir? 489 entries = append(entries, d) 490 } else if info.Type == api.ItemTypeFile { 491 o, err := f.newObjectWithInfo(ctx, remote, info) 492 if err != nil { 493 iErr = err 494 return true 495 } 496 entries = append(entries, o) 497 } 498 return false 499 }) 500 if err != nil { 501 return nil, err 502 } 503 if iErr != nil { 504 return nil, iErr 505 } 506 return entries, nil 507 } 508 509 // Creates from the parameters passed in a half finished Object which 510 // must have setMetaData called on it 511 // 512 // Returns the object, leaf, directoryID and error 513 // 514 // Used to create new objects 515 func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) { 516 // Create the directory for the object if it doesn't exist 517 leaf, directoryID, err = f.dirCache.FindRootAndPath(ctx, remote, true) 518 if err != nil { 519 return 520 } 521 // Temporary Object under construction 522 o = &Object{ 523 fs: f, 524 remote: remote, 525 } 526 return o, leaf, directoryID, nil 527 } 528 529 // Put the object 530 // 531 // Copy the reader in to the new object which is returned 532 // 533 // The new object may have been created if an error is returned 534 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 535 existingObj, err := f.newObjectWithInfo(ctx, src.Remote(), nil) 536 switch err { 537 case nil: 538 return existingObj, existingObj.Update(ctx, in, src, options...) 539 case fs.ErrorObjectNotFound: 540 // Not found so create it 541 return f.PutUnchecked(ctx, in, src) 542 default: 543 return nil, err 544 } 545 } 546 547 // PutStream uploads to the remote path with the modTime given of indeterminate size 548 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 549 return f.Put(ctx, in, src, options...) 550 } 551 552 // PutUnchecked the object into the container 553 // 554 // This will produce an error if the object already exists 555 // 556 // Copy the reader in to the new object which is returned 557 // 558 // The new object may have been created if an error is returned 559 func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 560 remote := src.Remote() 561 size := src.Size() 562 modTime := src.ModTime(ctx) 563 564 o, _, _, err := f.createObject(ctx, remote, modTime, size) 565 if err != nil { 566 return nil, err 567 } 568 return o, o.Update(ctx, in, src, options...) 569 } 570 571 // Mkdir creates the container if it doesn't exist 572 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 573 err := f.dirCache.FindRoot(ctx, true) 574 if err != nil { 575 return err 576 } 577 if dir != "" { 578 _, err = f.dirCache.FindDir(ctx, dir, true) 579 } 580 return err 581 } 582 583 // deleteObject removes an object by ID 584 func (f *Fs) deleteObject(id string) error { 585 opts := rest.Opts{ 586 Method: "DELETE", 587 Path: "/files/" + id, 588 NoResponse: true, 589 } 590 return f.pacer.Call(func() (bool, error) { 591 resp, err := f.srv.Call(&opts) 592 return shouldRetry(resp, err) 593 }) 594 } 595 596 // purgeCheck removes the root directory, if check is set then it 597 // refuses to do so if it has anything in 598 func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error { 599 root := path.Join(f.root, dir) 600 if root == "" { 601 return errors.New("can't purge root directory") 602 } 603 dc := f.dirCache 604 err := dc.FindRoot(ctx, false) 605 if err != nil { 606 return err 607 } 608 rootID, err := dc.FindDir(ctx, dir, false) 609 if err != nil { 610 return err 611 } 612 613 opts := rest.Opts{ 614 Method: "DELETE", 615 Path: "/folders/" + rootID, 616 Parameters: url.Values{}, 617 NoResponse: true, 618 } 619 opts.Parameters.Set("recursive", strconv.FormatBool(!check)) 620 var resp *http.Response 621 err = f.pacer.Call(func() (bool, error) { 622 resp, err = f.srv.Call(&opts) 623 return shouldRetry(resp, err) 624 }) 625 if err != nil { 626 return errors.Wrap(err, "rmdir failed") 627 } 628 f.dirCache.FlushDir(dir) 629 if err != nil { 630 return err 631 } 632 return nil 633 } 634 635 // Rmdir deletes the root folder 636 // 637 // Returns an error if it isn't empty 638 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 639 return f.purgeCheck(ctx, dir, true) 640 } 641 642 // Precision return the precision of this Fs 643 func (f *Fs) Precision() time.Duration { 644 return time.Second 645 } 646 647 // Copy src to this remote using server side copy operations. 648 // 649 // This is stored with the remote path given 650 // 651 // It returns the destination Object and a possible error 652 // 653 // Will only be called if src.Fs().Name() == f.Name() 654 // 655 // If it isn't possible then return fs.ErrorCantCopy 656 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 657 srcObj, ok := src.(*Object) 658 if !ok { 659 fs.Debugf(src, "Can't copy - not same remote type") 660 return nil, fs.ErrorCantCopy 661 } 662 err := srcObj.readMetaData(ctx) 663 if err != nil { 664 return nil, err 665 } 666 667 srcPath := srcObj.fs.rootSlash() + srcObj.remote 668 dstPath := f.rootSlash() + remote 669 if strings.ToLower(srcPath) == strings.ToLower(dstPath) { 670 return nil, errors.Errorf("can't copy %q -> %q as are same name when lowercase", srcPath, dstPath) 671 } 672 673 // Create temporary object 674 dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 675 if err != nil { 676 return nil, err 677 } 678 679 // Copy the object 680 opts := rest.Opts{ 681 Method: "POST", 682 Path: "/files/" + srcObj.id + "/copy", 683 Parameters: fieldsValue(), 684 } 685 replacedLeaf := replaceReservedChars(leaf) 686 copyFile := api.CopyFile{ 687 Name: replacedLeaf, 688 Parent: api.Parent{ 689 ID: directoryID, 690 }, 691 } 692 var resp *http.Response 693 var info *api.Item 694 err = f.pacer.Call(func() (bool, error) { 695 resp, err = f.srv.CallJSON(&opts, ©File, &info) 696 return shouldRetry(resp, err) 697 }) 698 if err != nil { 699 return nil, err 700 } 701 err = dstObj.setMetaData(info) 702 if err != nil { 703 return nil, err 704 } 705 return dstObj, nil 706 } 707 708 // Purge deletes all the files and the container 709 // 710 // Optional interface: Only implement this if you have a way of 711 // deleting all the files quicker than just running Remove() on the 712 // result of List() 713 func (f *Fs) Purge(ctx context.Context) error { 714 return f.purgeCheck(ctx, "", false) 715 } 716 717 // move a file or folder 718 func (f *Fs) move(endpoint, id, leaf, directoryID string) (info *api.Item, err error) { 719 // Move the object 720 opts := rest.Opts{ 721 Method: "PUT", 722 Path: endpoint + id, 723 Parameters: fieldsValue(), 724 } 725 move := api.UpdateFileMove{ 726 Name: replaceReservedChars(leaf), 727 Parent: api.Parent{ 728 ID: directoryID, 729 }, 730 } 731 var resp *http.Response 732 err = f.pacer.Call(func() (bool, error) { 733 resp, err = f.srv.CallJSON(&opts, &move, &info) 734 return shouldRetry(resp, err) 735 }) 736 if err != nil { 737 return nil, err 738 } 739 return info, nil 740 } 741 742 // Move src to this remote using server side move operations. 743 // 744 // This is stored with the remote path given 745 // 746 // It returns the destination Object and a possible error 747 // 748 // Will only be called if src.Fs().Name() == f.Name() 749 // 750 // If it isn't possible then return fs.ErrorCantMove 751 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 752 srcObj, ok := src.(*Object) 753 if !ok { 754 fs.Debugf(src, "Can't move - not same remote type") 755 return nil, fs.ErrorCantMove 756 } 757 758 // Create temporary object 759 dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 760 if err != nil { 761 return nil, err 762 } 763 764 // Do the move 765 info, err := f.move("/files/", srcObj.id, leaf, directoryID) 766 if err != nil { 767 return nil, err 768 } 769 770 err = dstObj.setMetaData(info) 771 if err != nil { 772 return nil, err 773 } 774 return dstObj, nil 775 } 776 777 // DirMove moves src, srcRemote to this remote at dstRemote 778 // using server side move operations. 779 // 780 // Will only be called if src.Fs().Name() == f.Name() 781 // 782 // If it isn't possible then return fs.ErrorCantDirMove 783 // 784 // If destination exists then return fs.ErrorDirExists 785 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 786 srcFs, ok := src.(*Fs) 787 if !ok { 788 fs.Debugf(srcFs, "Can't move directory - not same remote type") 789 return fs.ErrorCantDirMove 790 } 791 srcPath := path.Join(srcFs.root, srcRemote) 792 dstPath := path.Join(f.root, dstRemote) 793 794 // Refuse to move to or from the root 795 if srcPath == "" || dstPath == "" { 796 fs.Debugf(src, "DirMove error: Can't move root") 797 return errors.New("can't move root directory") 798 } 799 800 // find the root src directory 801 err := srcFs.dirCache.FindRoot(ctx, false) 802 if err != nil { 803 return err 804 } 805 806 // find the root dst directory 807 if dstRemote != "" { 808 err = f.dirCache.FindRoot(ctx, true) 809 if err != nil { 810 return err 811 } 812 } else { 813 if f.dirCache.FoundRoot() { 814 return fs.ErrorDirExists 815 } 816 } 817 818 // Find ID of dst parent, creating subdirs if necessary 819 var leaf, directoryID string 820 findPath := dstRemote 821 if dstRemote == "" { 822 findPath = f.root 823 } 824 leaf, directoryID, err = f.dirCache.FindPath(ctx, findPath, true) 825 if err != nil { 826 return err 827 } 828 829 // Check destination does not exist 830 if dstRemote != "" { 831 _, err = f.dirCache.FindDir(ctx, dstRemote, false) 832 if err == fs.ErrorDirNotFound { 833 // OK 834 } else if err != nil { 835 return err 836 } else { 837 return fs.ErrorDirExists 838 } 839 } 840 841 // Find ID of src 842 srcID, err := srcFs.dirCache.FindDir(ctx, srcRemote, false) 843 if err != nil { 844 return err 845 } 846 847 // Do the move 848 _, err = f.move("/folders/", srcID, leaf, directoryID) 849 if err != nil { 850 return err 851 } 852 srcFs.dirCache.FlushDir(srcRemote) 853 return nil 854 } 855 856 // PublicLink adds a "readable by anyone with link" permission on the given file or folder. 857 func (f *Fs) PublicLink(ctx context.Context, remote string) (string, error) { 858 id, err := f.dirCache.FindDir(ctx, remote, false) 859 var opts rest.Opts 860 if err == nil { 861 fs.Debugf(f, "attempting to share directory '%s'", remote) 862 863 opts = rest.Opts{ 864 Method: "PUT", 865 Path: "/folders/" + id, 866 Parameters: fieldsValue(), 867 } 868 } else { 869 fs.Debugf(f, "attempting to share single file '%s'", remote) 870 o, err := f.NewObject(ctx, remote) 871 if err != nil { 872 return "", err 873 } 874 875 if o.(*Object).publicLink != "" { 876 return o.(*Object).publicLink, nil 877 } 878 879 opts = rest.Opts{ 880 Method: "PUT", 881 Path: "/files/" + o.(*Object).id, 882 Parameters: fieldsValue(), 883 } 884 } 885 886 shareLink := api.CreateSharedLink{} 887 var info api.Item 888 var resp *http.Response 889 err = f.pacer.Call(func() (bool, error) { 890 resp, err = f.srv.CallJSON(&opts, &shareLink, &info) 891 return shouldRetry(resp, err) 892 }) 893 return info.SharedLink.URL, err 894 } 895 896 // DirCacheFlush resets the directory cache - used in testing as an 897 // optional interface 898 func (f *Fs) DirCacheFlush() { 899 f.dirCache.ResetRoot() 900 } 901 902 // Hashes returns the supported hash sets. 903 func (f *Fs) Hashes() hash.Set { 904 return hash.Set(hash.SHA1) 905 } 906 907 // ------------------------------------------------------------ 908 909 // Fs returns the parent Fs 910 func (o *Object) Fs() fs.Info { 911 return o.fs 912 } 913 914 // Return a string version 915 func (o *Object) String() string { 916 if o == nil { 917 return "<nil>" 918 } 919 return o.remote 920 } 921 922 // Remote returns the remote path 923 func (o *Object) Remote() string { 924 return o.remote 925 } 926 927 // srvPath returns a path for use in server 928 func (o *Object) srvPath() string { 929 return replaceReservedChars(o.fs.rootSlash() + o.remote) 930 } 931 932 // Hash returns the SHA-1 of an object returning a lowercase hex string 933 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 934 if t != hash.SHA1 { 935 return "", hash.ErrUnsupported 936 } 937 return o.sha1, nil 938 } 939 940 // Size returns the size of an object in bytes 941 func (o *Object) Size() int64 { 942 err := o.readMetaData(context.TODO()) 943 if err != nil { 944 fs.Logf(o, "Failed to read metadata: %v", err) 945 return 0 946 } 947 return o.size 948 } 949 950 // setMetaData sets the metadata from info 951 func (o *Object) setMetaData(info *api.Item) (err error) { 952 if info.Type != api.ItemTypeFile { 953 return errors.Wrapf(fs.ErrorNotAFile, "%q is %q", o.remote, info.Type) 954 } 955 o.hasMetaData = true 956 o.size = int64(info.Size) 957 o.sha1 = info.SHA1 958 o.modTime = info.ModTime() 959 o.id = info.ID 960 o.publicLink = info.SharedLink.URL 961 return nil 962 } 963 964 // readMetaData gets the metadata if it hasn't already been fetched 965 // 966 // it also sets the info 967 func (o *Object) readMetaData(ctx context.Context) (err error) { 968 if o.hasMetaData { 969 return nil 970 } 971 info, err := o.fs.readMetaDataForPath(ctx, o.remote) 972 if err != nil { 973 if apiErr, ok := err.(*api.Error); ok { 974 if apiErr.Code == "not_found" || apiErr.Code == "trashed" { 975 return fs.ErrorObjectNotFound 976 } 977 } 978 return err 979 } 980 return o.setMetaData(info) 981 } 982 983 // ModTime returns the modification time of the object 984 // 985 // 986 // It attempts to read the objects mtime and if that isn't present the 987 // LastModified returned in the http headers 988 func (o *Object) ModTime(ctx context.Context) time.Time { 989 err := o.readMetaData(ctx) 990 if err != nil { 991 fs.Logf(o, "Failed to read metadata: %v", err) 992 return time.Now() 993 } 994 return o.modTime 995 } 996 997 // setModTime sets the modification time of the local fs object 998 func (o *Object) setModTime(ctx context.Context, modTime time.Time) (*api.Item, error) { 999 opts := rest.Opts{ 1000 Method: "PUT", 1001 Path: "/files/" + o.id, 1002 Parameters: fieldsValue(), 1003 } 1004 update := api.UpdateFileModTime{ 1005 ContentModifiedAt: api.Time(modTime), 1006 } 1007 var info *api.Item 1008 err := o.fs.pacer.Call(func() (bool, error) { 1009 resp, err := o.fs.srv.CallJSON(&opts, &update, &info) 1010 return shouldRetry(resp, err) 1011 }) 1012 return info, err 1013 } 1014 1015 // SetModTime sets the modification time of the local fs object 1016 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 1017 info, err := o.setModTime(ctx, modTime) 1018 if err != nil { 1019 return err 1020 } 1021 return o.setMetaData(info) 1022 } 1023 1024 // Storable returns a boolean showing whether this object storable 1025 func (o *Object) Storable() bool { 1026 return true 1027 } 1028 1029 // Open an object for read 1030 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 1031 if o.id == "" { 1032 return nil, errors.New("can't download - no id") 1033 } 1034 fs.FixRangeOption(options, o.size) 1035 var resp *http.Response 1036 opts := rest.Opts{ 1037 Method: "GET", 1038 Path: "/files/" + o.id + "/content", 1039 Options: options, 1040 } 1041 err = o.fs.pacer.Call(func() (bool, error) { 1042 resp, err = o.fs.srv.Call(&opts) 1043 return shouldRetry(resp, err) 1044 }) 1045 if err != nil { 1046 return nil, err 1047 } 1048 return resp.Body, err 1049 } 1050 1051 // upload does a single non-multipart upload 1052 // 1053 // This is recommended for less than 50 MB of content 1054 func (o *Object) upload(in io.Reader, leaf, directoryID string, modTime time.Time) (err error) { 1055 upload := api.UploadFile{ 1056 Name: replaceReservedChars(leaf), 1057 ContentModifiedAt: api.Time(modTime), 1058 ContentCreatedAt: api.Time(modTime), 1059 Parent: api.Parent{ 1060 ID: directoryID, 1061 }, 1062 } 1063 1064 var resp *http.Response 1065 var result api.FolderItems 1066 opts := rest.Opts{ 1067 Method: "POST", 1068 Body: in, 1069 MultipartMetadataName: "attributes", 1070 MultipartContentName: "contents", 1071 MultipartFileName: upload.Name, 1072 RootURL: uploadURL, 1073 } 1074 // If object has an ID then it is existing so create a new version 1075 if o.id != "" { 1076 opts.Path = "/files/" + o.id + "/content" 1077 } else { 1078 opts.Path = "/files/content" 1079 } 1080 err = o.fs.pacer.CallNoRetry(func() (bool, error) { 1081 resp, err = o.fs.srv.CallJSON(&opts, &upload, &result) 1082 return shouldRetry(resp, err) 1083 }) 1084 if err != nil { 1085 return err 1086 } 1087 if result.TotalCount != 1 || len(result.Entries) != 1 { 1088 return errors.Errorf("failed to upload %v - not sure why", o) 1089 } 1090 return o.setMetaData(&result.Entries[0]) 1091 } 1092 1093 // Update the object with the contents of the io.Reader, modTime and size 1094 // 1095 // If existing is set then it updates the object rather than creating a new one 1096 // 1097 // The new object may have been created if an error is returned 1098 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 1099 o.fs.tokenRenewer.Start() 1100 defer o.fs.tokenRenewer.Stop() 1101 1102 size := src.Size() 1103 modTime := src.ModTime(ctx) 1104 remote := o.Remote() 1105 1106 // Create the directory for the object if it doesn't exist 1107 leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, remote, true) 1108 if err != nil { 1109 return err 1110 } 1111 1112 // Upload with simple or multipart 1113 if size <= int64(o.fs.opt.UploadCutoff) { 1114 err = o.upload(in, leaf, directoryID, modTime) 1115 } else { 1116 err = o.uploadMultipart(in, leaf, directoryID, size, modTime) 1117 } 1118 return err 1119 } 1120 1121 // Remove an object 1122 func (o *Object) Remove(ctx context.Context) error { 1123 return o.fs.deleteObject(o.id) 1124 } 1125 1126 // ID returns the ID of the Object if known, or "" if not 1127 func (o *Object) ID() string { 1128 return o.id 1129 } 1130 1131 // Check the interfaces are satisfied 1132 var ( 1133 _ fs.Fs = (*Fs)(nil) 1134 _ fs.Purger = (*Fs)(nil) 1135 _ fs.PutStreamer = (*Fs)(nil) 1136 _ fs.Copier = (*Fs)(nil) 1137 _ fs.Mover = (*Fs)(nil) 1138 _ fs.DirMover = (*Fs)(nil) 1139 _ fs.DirCacheFlusher = (*Fs)(nil) 1140 _ fs.PublicLinker = (*Fs)(nil) 1141 _ fs.Object = (*Object)(nil) 1142 _ fs.IDer = (*Object)(nil) 1143 )