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