github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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) 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 // rootSlash returns root with a slash on if it is empty, otherwise empty string 319 func (f *Fs) rootSlash() string { 320 if f.root == "" { 321 return f.root 322 } 323 return f.root + "/" 324 } 325 326 // Return an Object from a path 327 // 328 // If it can't be found it returns the error fs.ErrorObjectNotFound. 329 func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.Item) (fs.Object, error) { 330 o := &Object{ 331 fs: f, 332 remote: remote, 333 } 334 var err error 335 if info != nil { 336 // Set info 337 err = o.setMetaData(info) 338 } else { 339 err = o.readMetaData(ctx) // reads info and meta, returning an error 340 } 341 if err != nil { 342 return nil, err 343 } 344 return o, nil 345 } 346 347 // NewObject finds the Object at remote. If it can't be found 348 // it returns the error fs.ErrorObjectNotFound. 349 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 350 return f.newObjectWithInfo(ctx, remote, nil) 351 } 352 353 // FindLeaf finds a directory of name leaf in the folder with ID pathID 354 func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) { 355 // Find the leaf in pathID 356 found, err = f.listAll(ctx, pathID, true, false, func(item *api.Item) bool { 357 if item.Name == leaf { 358 pathIDOut = item.ID 359 return true 360 } 361 return false 362 }) 363 return pathIDOut, found, err 364 } 365 366 // CreateDir makes a directory with pathID as parent and name leaf 367 func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { 368 // fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, leaf) 369 var resp *http.Response 370 var info api.FolderCreateResponse 371 opts := rest.Opts{ 372 Method: "POST", 373 Path: "/folder/create", 374 Parameters: f.baseParams(), 375 MultipartParams: url.Values{ 376 "name": {f.opt.Enc.FromStandardName(leaf)}, 377 "parent_id": {pathID}, 378 }, 379 } 380 err = f.pacer.Call(func() (bool, error) { 381 resp, err = f.srv.CallJSON(ctx, &opts, nil, &info) 382 return shouldRetry(resp, err) 383 }) 384 if err != nil { 385 //fmt.Printf("...Error %v\n", err) 386 return "", errors.Wrap(err, "CreateDir http") 387 } 388 if err = info.AsErr(); err != nil { 389 return "", errors.Wrap(err, "CreateDir") 390 } 391 // fmt.Printf("...Id %q\n", *info.Id) 392 return info.ID, nil 393 } 394 395 // list the objects into the function supplied 396 // 397 // If directories is set it only sends directories 398 // User function to process a File item from listAll 399 // 400 // Should return true to finish processing 401 type listAllFn func(*api.Item) bool 402 403 // Lists the directory required calling the user function on each item found 404 // 405 // If the user fn ever returns true then it early exits with found = true 406 func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) { 407 opts := rest.Opts{ 408 Method: "GET", 409 Path: "/folder/list", 410 Parameters: f.baseParams(), 411 } 412 opts.Parameters.Set("id", dirID) 413 opts.Parameters.Set("includebreadcrumbs", "false") 414 415 var result api.FolderListResponse 416 var resp *http.Response 417 err = f.pacer.Call(func() (bool, error) { 418 resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) 419 return shouldRetry(resp, err) 420 }) 421 if err != nil { 422 return found, errors.Wrap(err, "couldn't list files") 423 } 424 if err = result.AsErr(); err != nil { 425 return found, errors.Wrap(err, "error while listing") 426 } 427 for i := range result.Content { 428 item := &result.Content[i] 429 if item.Type == api.ItemTypeFolder { 430 if filesOnly { 431 continue 432 } 433 } else if item.Type == api.ItemTypeFile { 434 if directoriesOnly { 435 continue 436 } 437 } else { 438 fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type) 439 continue 440 } 441 item.Name = f.opt.Enc.ToStandardName(item.Name) 442 if fn(item) { 443 found = true 444 break 445 } 446 } 447 448 return 449 } 450 451 // List the objects and directories in dir into entries. The 452 // entries can be returned in any order but should be for a 453 // complete directory. 454 // 455 // dir should be "" to list the root, and should not have 456 // trailing slashes. 457 // 458 // This should return ErrDirNotFound if the directory isn't 459 // found. 460 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 461 err = f.dirCache.FindRoot(ctx, false) 462 if err != nil { 463 return nil, err 464 } 465 directoryID, err := f.dirCache.FindDir(ctx, dir, false) 466 if err != nil { 467 return nil, err 468 } 469 var iErr error 470 _, err = f.listAll(ctx, directoryID, false, false, func(info *api.Item) bool { 471 remote := path.Join(dir, info.Name) 472 if info.Type == api.ItemTypeFolder { 473 // cache the directory ID for later lookups 474 f.dirCache.Put(remote, info.ID) 475 d := fs.NewDir(remote, time.Unix(info.CreatedAt, 0)).SetID(info.ID) 476 entries = append(entries, d) 477 } else if info.Type == api.ItemTypeFile { 478 o, err := f.newObjectWithInfo(ctx, remote, info) 479 if err != nil { 480 iErr = err 481 return true 482 } 483 entries = append(entries, o) 484 } 485 return false 486 }) 487 if err != nil { 488 return nil, err 489 } 490 if iErr != nil { 491 return nil, iErr 492 } 493 return entries, nil 494 } 495 496 // Creates from the parameters passed in a half finished Object which 497 // must have setMetaData called on it 498 // 499 // Returns the object, leaf, directoryID and error 500 // 501 // Used to create new objects 502 func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) { 503 // Create the directory for the object if it doesn't exist 504 leaf, directoryID, err = f.dirCache.FindRootAndPath(ctx, remote, true) 505 if err != nil { 506 return 507 } 508 // Temporary Object under construction 509 o = &Object{ 510 fs: f, 511 remote: remote, 512 } 513 return o, leaf, directoryID, nil 514 } 515 516 // Put the object 517 // 518 // Copy the reader in to the new object which is returned 519 // 520 // The new object may have been created if an error is returned 521 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 522 existingObj, err := f.newObjectWithInfo(ctx, src.Remote(), nil) 523 switch err { 524 case nil: 525 return existingObj, existingObj.Update(ctx, in, src, options...) 526 case fs.ErrorObjectNotFound: 527 // Not found so create it 528 return f.PutUnchecked(ctx, in, src) 529 default: 530 return nil, err 531 } 532 } 533 534 // PutUnchecked the object into the container 535 // 536 // This will produce an error if the object already exists 537 // 538 // Copy the reader in to the new object which is returned 539 // 540 // The new object may have been created if an error is returned 541 func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 542 remote := src.Remote() 543 size := src.Size() 544 modTime := src.ModTime(ctx) 545 546 o, _, _, err := f.createObject(ctx, remote, modTime, size) 547 if err != nil { 548 return nil, err 549 } 550 return o, o.Update(ctx, in, src, options...) 551 } 552 553 // Mkdir creates the container if it doesn't exist 554 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 555 err := f.dirCache.FindRoot(ctx, true) 556 if err != nil { 557 return err 558 } 559 if dir != "" { 560 _, err = f.dirCache.FindDir(ctx, dir, true) 561 } 562 return err 563 } 564 565 // purgeCheck removes the root directory, if check is set then it 566 // refuses to do so if it has anything in 567 func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error { 568 root := path.Join(f.root, dir) 569 if root == "" { 570 return errors.New("can't purge root directory") 571 } 572 dc := f.dirCache 573 err := dc.FindRoot(ctx, false) 574 if err != nil { 575 return err 576 } 577 rootID, err := dc.FindDir(ctx, dir, false) 578 if err != nil { 579 return err 580 } 581 582 // need to check if empty as it will delete recursively by default 583 if check { 584 found, err := f.listAll(ctx, rootID, false, false, func(item *api.Item) bool { 585 return true 586 }) 587 if err != nil { 588 return errors.Wrap(err, "purgeCheck") 589 } 590 if found { 591 return fs.ErrorDirectoryNotEmpty 592 } 593 } 594 595 opts := rest.Opts{ 596 Method: "POST", 597 Path: "/folder/delete", 598 MultipartParams: url.Values{ 599 "id": {rootID}, 600 }, 601 Parameters: f.baseParams(), 602 } 603 var resp *http.Response 604 var result api.Response 605 err = f.pacer.Call(func() (bool, error) { 606 resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) 607 return shouldRetry(resp, err) 608 }) 609 if err != nil { 610 return errors.Wrap(err, "rmdir failed") 611 } 612 if err = result.AsErr(); err != nil { 613 return errors.Wrap(err, "rmdir") 614 } 615 f.dirCache.FlushDir(dir) 616 if err != nil { 617 return err 618 } 619 return nil 620 } 621 622 // Rmdir deletes the root folder 623 // 624 // Returns an error if it isn't empty 625 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 626 return f.purgeCheck(ctx, dir, true) 627 } 628 629 // Precision return the precision of this Fs 630 func (f *Fs) Precision() time.Duration { 631 return fs.ModTimeNotSupported 632 } 633 634 // Purge deletes all the files and the container 635 // 636 // Optional interface: Only implement this if you have a way of 637 // deleting all the files quicker than just running Remove() on the 638 // result of List() 639 func (f *Fs) Purge(ctx context.Context) error { 640 return f.purgeCheck(ctx, "", false) 641 } 642 643 // move a file or folder 644 // 645 // This is complicated by the fact that there is an API to move files 646 // between directories and a separate one to rename them. We try to 647 // call the minimum number of API calls. 648 func (f *Fs) move(ctx context.Context, isFile bool, id, oldLeaf, newLeaf, oldDirectoryID, newDirectoryID string) (err error) { 649 newLeaf = f.opt.Enc.FromStandardName(newLeaf) 650 oldLeaf = f.opt.Enc.FromStandardName(oldLeaf) 651 doRenameLeaf := oldLeaf != newLeaf 652 doMove := oldDirectoryID != newDirectoryID 653 654 // Now rename the leaf to a temporary name if we are moving to 655 // another directory to make sure we don't overwrite something 656 // in the destination directory by accident 657 if doRenameLeaf && doMove { 658 tmpLeaf := newLeaf + "." + random.String(8) 659 err = f.renameLeaf(ctx, isFile, id, tmpLeaf) 660 if err != nil { 661 return errors.Wrap(err, "Move rename leaf") 662 } 663 } 664 665 // Move the object to a new directory (with the existing name) 666 // if required 667 if doMove { 668 opts := rest.Opts{ 669 Method: "POST", 670 Path: "/folder/paste", 671 Parameters: f.baseParams(), 672 MultipartParams: url.Values{ 673 "id": {newDirectoryID}, 674 }, 675 } 676 if isFile { 677 opts.MultipartParams.Set("files[]", id) 678 } else { 679 opts.MultipartParams.Set("folders[]", id) 680 } 681 //replacedLeaf := enc.FromStandardName(leaf) 682 var resp *http.Response 683 var result api.Response 684 err = f.pacer.Call(func() (bool, error) { 685 resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) 686 return shouldRetry(resp, err) 687 }) 688 if err != nil { 689 return errors.Wrap(err, "Move http") 690 } 691 if err = result.AsErr(); err != nil { 692 return errors.Wrap(err, "Move") 693 } 694 } 695 696 // Rename the leaf to its final name if required 697 if doRenameLeaf { 698 err = f.renameLeaf(ctx, isFile, id, newLeaf) 699 if err != nil { 700 return errors.Wrap(err, "Move rename leaf") 701 } 702 } 703 704 return nil 705 } 706 707 // Move src to this remote using server side move operations. 708 // 709 // This is stored with the remote path given 710 // 711 // It returns the destination Object and a possible error 712 // 713 // Will only be called if src.Fs().Name() == f.Name() 714 // 715 // If it isn't possible then return fs.ErrorCantMove 716 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 717 srcObj, ok := src.(*Object) 718 if !ok { 719 fs.Debugf(src, "Can't move - not same remote type") 720 return nil, fs.ErrorCantMove 721 } 722 723 // Create temporary object 724 dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 725 if err != nil { 726 return nil, err 727 } 728 729 // Do the move 730 err = f.move(ctx, true, srcObj.id, path.Base(srcObj.remote), leaf, srcObj.parentID, directoryID) 731 if err != nil { 732 return nil, err 733 } 734 735 err = dstObj.readMetaData(ctx) 736 if err != nil { 737 return nil, err 738 } 739 return dstObj, nil 740 } 741 742 // DirMove moves src, srcRemote to this remote at dstRemote 743 // using server side move operations. 744 // 745 // Will only be called if src.Fs().Name() == f.Name() 746 // 747 // If it isn't possible then return fs.ErrorCantDirMove 748 // 749 // If destination exists then return fs.ErrorDirExists 750 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 751 srcFs, ok := src.(*Fs) 752 if !ok { 753 fs.Debugf(srcFs, "Can't move directory - not same remote type") 754 return fs.ErrorCantDirMove 755 } 756 srcPath := path.Join(srcFs.root, srcRemote) 757 dstPath := path.Join(f.root, dstRemote) 758 759 // Refuse to move to or from the root 760 if srcPath == "" || dstPath == "" { 761 fs.Debugf(src, "DirMove error: Can't move root") 762 return errors.New("can't move root directory") 763 } 764 765 // find the root src directory 766 err := srcFs.dirCache.FindRoot(ctx, false) 767 if err != nil { 768 return err 769 } 770 771 // find the root dst directory 772 if dstRemote != "" { 773 err = f.dirCache.FindRoot(ctx, true) 774 if err != nil { 775 return err 776 } 777 } else { 778 if f.dirCache.FoundRoot() { 779 return fs.ErrorDirExists 780 } 781 } 782 783 // Find ID of dst parent, creating subdirs if necessary 784 var leaf, directoryID string 785 findPath := dstRemote 786 if dstRemote == "" { 787 findPath = f.root 788 } 789 leaf, directoryID, err = f.dirCache.FindPath(ctx, findPath, true) 790 if err != nil { 791 return err 792 } 793 794 // Check destination does not exist 795 if dstRemote != "" { 796 _, err = f.dirCache.FindDir(ctx, dstRemote, false) 797 if err == fs.ErrorDirNotFound { 798 // OK 799 } else if err != nil { 800 return err 801 } else { 802 return fs.ErrorDirExists 803 } 804 } 805 806 // Find ID of src 807 srcID, err := srcFs.dirCache.FindDir(ctx, srcRemote, false) 808 if err != nil { 809 return err 810 } 811 812 // Find ID of src parent, not creating subdirs 813 var srcLeaf, srcDirectoryID string 814 findPath = srcRemote 815 if srcRemote == "" { 816 findPath = srcFs.root 817 } 818 srcLeaf, srcDirectoryID, err = srcFs.dirCache.FindPath(ctx, findPath, false) 819 if err != nil { 820 return err 821 } 822 823 // Do the move 824 err = f.move(ctx, false, srcID, srcLeaf, leaf, srcDirectoryID, directoryID) 825 if err != nil { 826 return err 827 } 828 srcFs.dirCache.FlushDir(srcRemote) 829 return nil 830 } 831 832 // PublicLink adds a "readable by anyone with link" permission on the given file or folder. 833 func (f *Fs) PublicLink(ctx context.Context, remote string) (string, error) { 834 _, err := f.dirCache.FindDir(ctx, remote, false) 835 if err == nil { 836 return "", fs.ErrorCantShareDirectories 837 } 838 o, err := f.NewObject(ctx, remote) 839 if err != nil { 840 return "", err 841 } 842 return o.(*Object).url, nil 843 } 844 845 // About gets quota information 846 func (f *Fs) About(ctx context.Context) (usage *fs.Usage, err error) { 847 var resp *http.Response 848 var info api.AccountInfoResponse 849 opts := rest.Opts{ 850 Method: "POST", 851 Path: "/account/info", 852 Parameters: f.baseParams(), 853 } 854 err = f.pacer.Call(func() (bool, error) { 855 resp, err = f.srv.CallJSON(ctx, &opts, nil, &info) 856 return shouldRetry(resp, err) 857 }) 858 if err != nil { 859 return nil, errors.Wrap(err, "CreateDir http") 860 } 861 if err = info.AsErr(); err != nil { 862 return nil, errors.Wrap(err, "CreateDir") 863 } 864 usage = &fs.Usage{ 865 Used: fs.NewUsageValue(int64(info.SpaceUsed)), 866 } 867 return usage, nil 868 } 869 870 // DirCacheFlush resets the directory cache - used in testing as an 871 // optional interface 872 func (f *Fs) DirCacheFlush() { 873 f.dirCache.ResetRoot() 874 } 875 876 // Hashes returns the supported hash sets. 877 func (f *Fs) Hashes() hash.Set { 878 return hash.Set(hash.None) 879 } 880 881 // ------------------------------------------------------------ 882 883 // Fs returns the parent Fs 884 func (o *Object) Fs() fs.Info { 885 return o.fs 886 } 887 888 // Return a string version 889 func (o *Object) String() string { 890 if o == nil { 891 return "<nil>" 892 } 893 return o.remote 894 } 895 896 // Remote returns the remote path 897 func (o *Object) Remote() string { 898 return o.remote 899 } 900 901 // srvPath returns a path for use in server 902 func (o *Object) srvPath() string { 903 return o.fs.opt.Enc.FromStandardPath(o.fs.rootSlash() + o.remote) 904 } 905 906 // Hash returns the SHA-1 of an object returning a lowercase hex string 907 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 908 return "", hash.ErrUnsupported 909 } 910 911 // Size returns the size of an object in bytes 912 func (o *Object) Size() int64 { 913 err := o.readMetaData(context.TODO()) 914 if err != nil { 915 fs.Logf(o, "Failed to read metadata: %v", err) 916 return 0 917 } 918 return o.size 919 } 920 921 // setMetaData sets the metadata from info 922 func (o *Object) setMetaData(info *api.Item) (err error) { 923 if info.Type != "file" { 924 return errors.Wrapf(fs.ErrorNotAFile, "%q is %q", o.remote, info.Type) 925 } 926 o.hasMetaData = true 927 o.size = info.Size 928 o.modTime = time.Unix(info.CreatedAt, 0) 929 o.id = info.ID 930 o.mimeType = info.MimeType 931 o.url = info.Link 932 return nil 933 } 934 935 // readMetaData gets the metadata if it hasn't already been fetched 936 // 937 // it also sets the info 938 func (o *Object) readMetaData(ctx context.Context) (err error) { 939 if o.hasMetaData { 940 return nil 941 } 942 info, err := o.fs.readMetaDataForPath(ctx, o.remote, false, true) 943 if err != nil { 944 return err 945 } 946 return o.setMetaData(info) 947 } 948 949 // ModTime returns the modification time of the object 950 // 951 // 952 // It attempts to read the objects mtime and if that isn't present the 953 // LastModified returned in the http headers 954 func (o *Object) ModTime(ctx context.Context) time.Time { 955 err := o.readMetaData(ctx) 956 if err != nil { 957 fs.Logf(o, "Failed to read metadata: %v", err) 958 return time.Now() 959 } 960 return o.modTime 961 } 962 963 // SetModTime sets the modification time of the local fs object 964 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 965 return fs.ErrorCantSetModTime 966 } 967 968 // Storable returns a boolean showing whether this object storable 969 func (o *Object) Storable() bool { 970 return true 971 } 972 973 // Open an object for read 974 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 975 if o.url == "" { 976 return nil, errors.New("can't download - no URL") 977 } 978 fs.FixRangeOption(options, o.size) 979 var resp *http.Response 980 opts := rest.Opts{ 981 Path: "", 982 RootURL: o.url, 983 Method: "GET", 984 Options: options, 985 } 986 err = o.fs.pacer.Call(func() (bool, error) { 987 resp, err = o.fs.srv.Call(ctx, &opts) 988 return shouldRetry(resp, err) 989 }) 990 if err != nil { 991 return nil, err 992 } 993 return resp.Body, err 994 } 995 996 // metaHash returns a rough hash of metadata to detect if object has been updated 997 func (o *Object) metaHash() string { 998 if !o.hasMetaData { 999 return "" 1000 } 1001 return fmt.Sprintf("remote=%q, size=%d, modTime=%v, id=%q, mimeType=%q", o.remote, o.size, o.modTime, o.id, o.mimeType) 1002 } 1003 1004 // Update the object with the contents of the io.Reader, modTime and size 1005 // 1006 // If existing is set then it updates the object rather than creating a new one 1007 // 1008 // The new object may have been created if an error is returned 1009 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 1010 remote := o.Remote() 1011 size := src.Size() 1012 1013 // Create the directory for the object if it doesn't exist 1014 leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, remote, true) 1015 if err != nil { 1016 return err 1017 } 1018 leaf = o.fs.opt.Enc.FromStandardName(leaf) 1019 1020 var resp *http.Response 1021 var info api.FolderUploadinfoResponse 1022 opts := rest.Opts{ 1023 Method: "POST", 1024 Path: "/folder/uploadinfo", 1025 Parameters: o.fs.baseParams(), 1026 MultipartParams: url.Values{ 1027 "id": {directoryID}, 1028 }, 1029 } 1030 err = o.fs.pacer.Call(func() (bool, error) { 1031 resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &info) 1032 if err != nil { 1033 return shouldRetry(resp, err) 1034 } 1035 // Just check the download URL resolves - sometimes 1036 // the URLs returned by premiumize.me don't resolve so 1037 // this needs a retry. 1038 var u *url.URL 1039 u, err = url.Parse(info.URL) 1040 if err != nil { 1041 return true, errors.Wrap(err, "failed to parse download URL") 1042 } 1043 _, err = net.LookupIP(u.Hostname()) 1044 if err != nil { 1045 return true, errors.Wrap(err, "failed to resolve download URL") 1046 } 1047 return false, nil 1048 }) 1049 if err != nil { 1050 return errors.Wrap(err, "upload get URL http") 1051 } 1052 if err = info.AsErr(); err != nil { 1053 return errors.Wrap(err, "upload get URL") 1054 } 1055 1056 // if file exists then rename it out the way otherwise uploads can fail 1057 uploaded := false 1058 var oldID = o.id 1059 if o.hasMetaData { 1060 newLeaf := leaf + "." + random.String(8) 1061 fs.Debugf(o, "Moving old file out the way to %q", newLeaf) 1062 err = o.fs.renameLeaf(ctx, true, oldID, newLeaf) 1063 if err != nil { 1064 return errors.Wrap(err, "upload rename old file") 1065 } 1066 defer func() { 1067 // on failed upload rename old file back 1068 if !uploaded { 1069 fs.Debugf(o, "Renaming old file back (from %q to %q) since upload failed", leaf, newLeaf) 1070 newErr := o.fs.renameLeaf(ctx, true, oldID, leaf) 1071 if newErr != nil && err == nil { 1072 err = errors.Wrap(newErr, "upload renaming old file back") 1073 } 1074 } 1075 }() 1076 } 1077 1078 opts = rest.Opts{ 1079 Method: "POST", 1080 RootURL: info.URL, 1081 Body: in, 1082 MultipartParams: url.Values{ 1083 "token": {info.Token}, 1084 }, 1085 MultipartContentName: "file", // ..name of the parameter which is the attached file 1086 MultipartFileName: leaf, // ..name of the file for the attached file 1087 ContentLength: &size, 1088 } 1089 var result api.Response 1090 err = o.fs.pacer.CallNoRetry(func() (bool, error) { 1091 resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &result) 1092 return shouldRetry(resp, err) 1093 }) 1094 if err != nil { 1095 return errors.Wrap(err, "upload file http") 1096 } 1097 if err = result.AsErr(); err != nil { 1098 return errors.Wrap(err, "upload file") 1099 } 1100 1101 // on successful upload, remove old file if it exists 1102 uploaded = true 1103 if o.hasMetaData { 1104 fs.Debugf(o, "Removing old file") 1105 err := o.fs.remove(ctx, oldID) 1106 if err != nil { 1107 return errors.Wrap(err, "upload remove old file") 1108 } 1109 } 1110 1111 o.hasMetaData = false 1112 return o.readMetaData(ctx) 1113 } 1114 1115 // Rename the leaf of a file or directory in a directory 1116 func (f *Fs) renameLeaf(ctx context.Context, isFile bool, id string, newLeaf string) (err error) { 1117 opts := rest.Opts{ 1118 Method: "POST", 1119 MultipartParams: url.Values{ 1120 "id": {id}, 1121 "name": {newLeaf}, 1122 }, 1123 Parameters: f.baseParams(), 1124 } 1125 if isFile { 1126 opts.Path = "/item/rename" 1127 } else { 1128 opts.Path = "/folder/rename" 1129 } 1130 var resp *http.Response 1131 var result api.Response 1132 err = f.pacer.Call(func() (bool, error) { 1133 resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) 1134 return shouldRetry(resp, err) 1135 }) 1136 if err != nil { 1137 return errors.Wrap(err, "rename http") 1138 } 1139 if err = result.AsErr(); err != nil { 1140 return errors.Wrap(err, "rename") 1141 } 1142 return nil 1143 } 1144 1145 // Remove an object by ID 1146 func (f *Fs) remove(ctx context.Context, id string) (err error) { 1147 opts := rest.Opts{ 1148 Method: "POST", 1149 Path: "/item/delete", 1150 MultipartParams: url.Values{ 1151 "id": {id}, 1152 }, 1153 Parameters: f.baseParams(), 1154 } 1155 var resp *http.Response 1156 var result api.Response 1157 err = f.pacer.Call(func() (bool, error) { 1158 resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) 1159 return shouldRetry(resp, err) 1160 }) 1161 if err != nil { 1162 return errors.Wrap(err, "remove http") 1163 } 1164 if err = result.AsErr(); err != nil { 1165 return errors.Wrap(err, "remove") 1166 } 1167 return nil 1168 } 1169 1170 // Remove an object 1171 func (o *Object) Remove(ctx context.Context) error { 1172 err := o.readMetaData(ctx) 1173 if err != nil { 1174 return errors.Wrap(err, "Remove: Failed to read metadata") 1175 } 1176 return o.fs.remove(ctx, o.id) 1177 } 1178 1179 // MimeType of an Object if known, "" otherwise 1180 func (o *Object) MimeType(ctx context.Context) string { 1181 return o.mimeType 1182 } 1183 1184 // ID returns the ID of the Object if known, or "" if not 1185 func (o *Object) ID() string { 1186 return o.id 1187 } 1188 1189 // Check the interfaces are satisfied 1190 var ( 1191 _ fs.Fs = (*Fs)(nil) 1192 _ fs.Purger = (*Fs)(nil) 1193 _ fs.Mover = (*Fs)(nil) 1194 _ fs.DirMover = (*Fs)(nil) 1195 _ fs.DirCacheFlusher = (*Fs)(nil) 1196 _ fs.Abouter = (*Fs)(nil) 1197 _ fs.PublicLinker = (*Fs)(nil) 1198 _ fs.Object = (*Object)(nil) 1199 _ fs.MimeTyper = (*Object)(nil) 1200 _ fs.IDer = (*Object)(nil) 1201 )