github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/uptobox/uptobox.go (about) 1 // Package uptobox provides an interface to the Uptobox storage system. 2 package uptobox 3 4 import ( 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "path" 13 "regexp" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/rclone/rclone/backend/uptobox/api" 19 "github.com/rclone/rclone/fs" 20 "github.com/rclone/rclone/fs/config" 21 "github.com/rclone/rclone/fs/config/configmap" 22 "github.com/rclone/rclone/fs/config/configstruct" 23 "github.com/rclone/rclone/fs/fserrors" 24 "github.com/rclone/rclone/fs/fshttp" 25 "github.com/rclone/rclone/fs/hash" 26 "github.com/rclone/rclone/lib/encoder" 27 "github.com/rclone/rclone/lib/pacer" 28 "github.com/rclone/rclone/lib/random" 29 "github.com/rclone/rclone/lib/rest" 30 ) 31 32 const ( 33 apiBaseURL = "https://uptobox.com/api" 34 minSleep = 400 * time.Millisecond // api is extremely rate limited now 35 maxSleep = 5 * time.Second 36 decayConstant = 2 // bigger for slower decay, exponential 37 attackConstant = 0 // start with max sleep 38 ) 39 40 func init() { 41 fs.Register(&fs.RegInfo{ 42 Name: "uptobox", 43 Description: "Uptobox", 44 NewFs: NewFs, 45 Options: []fs.Option{{ 46 Help: "Your access token.\n\nGet it from https://uptobox.com/my_account.", 47 Name: "access_token", 48 Sensitive: true, 49 }, { 50 Help: "Set to make uploaded files private", 51 Name: "private", 52 Advanced: true, 53 Default: false, 54 }, { 55 Name: config.ConfigEncoding, 56 Help: config.ConfigEncodingHelp, 57 Advanced: true, 58 // maxFileLength = 255 59 Default: (encoder.Display | 60 encoder.EncodeBackQuote | 61 encoder.EncodeDoubleQuote | 62 encoder.EncodeLtGt | 63 encoder.EncodeLeftSpace | 64 encoder.EncodeInvalidUtf8), 65 }}, 66 }) 67 } 68 69 // Options defines the configuration for this backend 70 type Options struct { 71 AccessToken string `config:"access_token"` 72 Private bool `config:"private"` 73 Enc encoder.MultiEncoder `config:"encoding"` 74 } 75 76 // Fs is the interface a cloud storage system must provide 77 type Fs struct { 78 root string 79 name string 80 opt Options 81 features *fs.Features 82 srv *rest.Client 83 pacer *fs.Pacer 84 IDRegexp *regexp.Regexp 85 public string // "0" to make objects private 86 } 87 88 // Object represents an Uptobox object 89 type Object struct { 90 fs *Fs // what this object is part of 91 remote string // The remote path 92 hasMetaData bool // whether info below has been set 93 size int64 // Bytes in the object 94 // modTime time.Time // Modified time of the object 95 code string 96 } 97 98 // Name of the remote (as passed into NewFs) 99 func (f *Fs) Name() string { 100 return f.name 101 } 102 103 // Root of the remote (as passed into NewFs) 104 func (f *Fs) Root() string { 105 return f.root 106 } 107 108 // String returns a description of the FS 109 func (f *Fs) String() string { 110 return fmt.Sprintf("Uptobox root '%s'", f.root) 111 } 112 113 // Precision of the ModTimes in this Fs 114 func (f *Fs) Precision() time.Duration { 115 return fs.ModTimeNotSupported 116 } 117 118 // Hashes returns the supported hash types of the filesystem 119 func (f *Fs) Hashes() hash.Set { 120 return hash.Set(hash.None) 121 } 122 123 // Features returns the optional features of this Fs 124 func (f *Fs) Features() *fs.Features { 125 return f.features 126 } 127 128 // retryErrorCodes is a slice of error codes that we will retry 129 var retryErrorCodes = []int{ 130 429, // Too Many Requests. 131 500, // Internal Server Error 132 502, // Bad Gateway 133 503, // Service Unavailable 134 504, // Gateway Timeout 135 509, // Bandwidth Limit Exceeded 136 } 137 138 // shouldRetry returns a boolean as to whether this resp and err 139 // deserve to be retried. It returns the err as a convenience 140 func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { 141 if fserrors.ContextError(ctx, &err) { 142 return false, err 143 } 144 return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 145 } 146 147 // dirPath returns an escaped file path (f.root, file) 148 func (f *Fs) dirPath(file string) string { 149 //return path.Join(f.diskRoot, file) 150 if file == "" || file == "." { 151 return "//" + f.root 152 } 153 return "//" + path.Join(f.root, file) 154 } 155 156 // returns the full path based on root and the last element 157 func (f *Fs) splitPathFull(pth string) (string, string) { 158 fullPath := strings.Trim(path.Join(f.root, pth), "/") 159 160 i := len(fullPath) - 1 161 for i >= 0 && fullPath[i] != '/' { 162 i-- 163 } 164 165 if i < 0 { 166 return "//" + fullPath[:i+1], fullPath[i+1:] 167 } 168 169 // do not include the / at the split 170 return "//" + fullPath[:i], fullPath[i+1:] 171 } 172 173 // splitPath is modified splitPath version that doesn't include the separator 174 // in the base path 175 func (f *Fs) splitPath(pth string) (string, string) { 176 // chop of any leading or trailing '/' 177 pth = strings.Trim(pth, "/") 178 179 i := len(pth) - 1 180 for i >= 0 && pth[i] != '/' { 181 i-- 182 } 183 184 if i < 0 { 185 return pth[:i+1], pth[i+1:] 186 } 187 return pth[:i], pth[i+1:] 188 } 189 190 // NewFs makes a new Fs object from the path 191 // 192 // The path is of the form remote:path 193 // 194 // Remotes are looked up in the config file. If the remote isn't 195 // found then NotFoundInConfigFile will be returned. 196 // 197 // On Windows avoid single character remote names as they can be mixed 198 // up with drive letters. 199 func NewFs(ctx context.Context, name string, root string, config configmap.Mapper) (fs.Fs, error) { 200 opt := new(Options) 201 err := configstruct.Set(config, opt) 202 if err != nil { 203 return nil, err 204 } 205 206 f := &Fs{ 207 name: name, 208 root: root, 209 opt: *opt, 210 pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant), pacer.AttackConstant(attackConstant))), 211 } 212 if root == "/" || root == "." { 213 f.root = "" 214 } else { 215 f.root = root 216 } 217 f.features = (&fs.Features{ 218 DuplicateFiles: true, 219 CanHaveEmptyDirectories: true, 220 ReadMimeType: false, 221 }).Fill(ctx, f) 222 if f.opt.Private { 223 f.public = "0" 224 } 225 226 client := fshttp.NewClient(ctx) 227 f.srv = rest.NewClient(client).SetRoot(apiBaseURL) 228 f.IDRegexp = regexp.MustCompile(`^https://uptobox\.com/([a-zA-Z0-9]+)`) 229 230 _, err = f.readMetaDataForPath(ctx, f.dirPath(""), &api.MetadataRequestOptions{Limit: 10}) 231 if err != nil { 232 if _, ok := err.(api.Error); !ok { 233 return nil, err 234 } 235 // assume it's a file than 236 oldRoot := f.root 237 rootDir, file := f.splitPath(root) 238 f.root = rootDir 239 _, err = f.NewObject(ctx, file) 240 if err == nil { 241 return f, fs.ErrorIsFile 242 } 243 f.root = oldRoot 244 } 245 246 return f, nil 247 } 248 249 func (f *Fs) decodeError(resp *http.Response, response interface{}) (err error) { 250 defer fs.CheckClose(resp.Body, &err) 251 252 body, err := io.ReadAll(resp.Body) 253 if err != nil { 254 return err 255 } 256 // try to unmarshal into correct structure 257 err = json.Unmarshal(body, response) 258 if err == nil { 259 return nil 260 } 261 // try to unmarshal into Error 262 var apiErr api.Error 263 err = json.Unmarshal(body, &apiErr) 264 if err != nil { 265 return err 266 } 267 return apiErr 268 } 269 270 func (f *Fs) readMetaDataForPath(ctx context.Context, path string, options *api.MetadataRequestOptions) (*api.ReadMetadataResponse, error) { 271 opts := rest.Opts{ 272 Method: "GET", 273 Path: "/user/files", 274 Parameters: url.Values{ 275 "token": []string{f.opt.AccessToken}, 276 "path": []string{f.opt.Enc.FromStandardPath(path)}, 277 "limit": []string{strconv.FormatUint(options.Limit, 10)}, 278 }, 279 } 280 281 if options.Offset != 0 { 282 opts.Parameters.Set("offset", strconv.FormatUint(options.Offset, 10)) 283 } 284 285 var err error 286 var info api.ReadMetadataResponse 287 var resp *http.Response 288 err = f.pacer.Call(func() (bool, error) { 289 resp, err = f.srv.Call(ctx, &opts) 290 return shouldRetry(ctx, resp, err) 291 }) 292 if err != nil { 293 return nil, err 294 } 295 296 err = f.decodeError(resp, &info) 297 if err != nil { 298 return nil, err 299 } 300 301 if info.StatusCode != 0 { 302 return nil, errors.New(info.Message) 303 } 304 305 return &info, nil 306 } 307 308 // List the objects and directories in dir into entries. The 309 // entries can be returned in any order but should be for a 310 // complete directory. 311 // 312 // dir should be "" to list the root, and should not have 313 // trailing slashes. 314 // 315 // This should return ErrDirNotFound if the directory isn't 316 // found. 317 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 318 root := f.dirPath(dir) 319 320 var limit uint64 = 100 // max number of objects per request - 100 seems to be the maximum the api accepts 321 var page uint64 = 1 322 var offset uint64 // for the next page of requests 323 324 for { 325 opts := &api.MetadataRequestOptions{ 326 Limit: limit, 327 Offset: offset, 328 } 329 330 info, err := f.readMetaDataForPath(ctx, root, opts) 331 if err != nil { 332 if apiErr, ok := err.(api.Error); ok { 333 // might indicate other errors but we can probably assume not found here 334 if apiErr.StatusCode == 1 { 335 return nil, fs.ErrorDirNotFound 336 } 337 } 338 return nil, err 339 } 340 341 for _, item := range info.Data.Files { 342 remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name)) 343 o, err := f.newObjectWithInfo(ctx, remote, &item) 344 if err != nil { 345 continue 346 } 347 entries = append(entries, o) 348 } 349 350 // folders are always listed entirely on every page grr. 351 if page == 1 { 352 for _, item := range info.Data.Folders { 353 remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name)) 354 d := fs.NewDir(remote, time.Time{}).SetID(strconv.FormatUint(item.FolderID, 10)) 355 entries = append(entries, d) 356 } 357 } 358 359 //offset for the next page of items 360 page++ 361 offset += limit 362 //check if we reached end of list 363 if page > uint64(info.Data.PageCount) { 364 break 365 } 366 } 367 return entries, nil 368 } 369 370 // Return an Object from a path 371 // 372 // If it can't be found it returns the error fs.ErrorObjectNotFound. 373 func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.FileInfo) (fs.Object, error) { 374 o := &Object{ 375 fs: f, 376 remote: remote, 377 size: info.Size, 378 code: info.Code, 379 hasMetaData: true, 380 } 381 return o, nil 382 } 383 384 // NewObject finds the Object at remote. If it can't be found it 385 // returns the error fs.ErrorObjectNotFound. 386 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 387 // no way to directly access an object by path so we have to list the parent dir 388 entries, err := f.List(ctx, path.Dir(remote)) 389 if err != nil { 390 // need to change error type 391 // if the parent dir doesn't exist the object doesn't exist either 392 if err == fs.ErrorDirNotFound { 393 return nil, fs.ErrorObjectNotFound 394 } 395 return nil, err 396 } 397 for _, entry := range entries { 398 if o, ok := entry.(fs.Object); ok { 399 if o.Remote() == remote { 400 return o, nil 401 } 402 } 403 } 404 return nil, fs.ErrorObjectNotFound 405 } 406 407 func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, filename string, uploadURL string, options ...fs.OpenOption) (*api.UploadResponse, error) { 408 opts := rest.Opts{ 409 Method: "POST", 410 RootURL: "https:" + uploadURL, 411 Body: in, 412 ContentLength: &size, 413 Options: options, 414 MultipartContentName: "files", 415 MultipartFileName: filename, 416 } 417 418 var err error 419 var resp *http.Response 420 var ul api.UploadResponse 421 err = f.pacer.CallNoRetry(func() (bool, error) { 422 resp, err = f.srv.CallJSON(ctx, &opts, nil, &ul) 423 return shouldRetry(ctx, resp, err) 424 }) 425 if err != nil { 426 return nil, fmt.Errorf("couldn't upload file: %w", err) 427 } 428 return &ul, nil 429 } 430 431 // dstPath starts from root and includes // 432 func (f *Fs) move(ctx context.Context, dstPath string, fileID string) (err error) { 433 meta, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 10}) 434 if err != nil { 435 return err 436 } 437 438 opts := rest.Opts{ 439 Method: "PATCH", 440 Path: "/user/files", 441 } 442 mv := api.CopyMoveFileRequest{ 443 Token: f.opt.AccessToken, 444 FileCodes: fileID, 445 DestinationFolderID: meta.Data.CurrentFolder.FolderID, 446 Action: "move", 447 } 448 449 var resp *http.Response 450 var info api.UpdateResponse 451 err = f.pacer.Call(func() (bool, error) { 452 resp, err = f.srv.CallJSON(ctx, &opts, &mv, &info) 453 return shouldRetry(ctx, resp, err) 454 }) 455 if err != nil { 456 return fmt.Errorf("couldn't move file: %w", err) 457 } 458 if info.StatusCode != 0 { 459 return fmt.Errorf("move: api error: %d - %s", info.StatusCode, info.Message) 460 } 461 return err 462 } 463 464 // updateFileInformation set's various file attributes most importantly it's name 465 func (f *Fs) updateFileInformation(ctx context.Context, update *api.UpdateFileInformation) (err error) { 466 opts := rest.Opts{ 467 Method: "PATCH", 468 Path: "/user/files", 469 } 470 471 var resp *http.Response 472 var info api.UpdateResponse 473 err = f.pacer.Call(func() (bool, error) { 474 resp, err = f.srv.CallJSON(ctx, &opts, update, &info) 475 return shouldRetry(ctx, resp, err) 476 }) 477 if err != nil { 478 return fmt.Errorf("couldn't update file info: %w", err) 479 } 480 if info.StatusCode != 0 { 481 return fmt.Errorf("updateFileInfo: api error: %d - %s", info.StatusCode, info.Message) 482 } 483 return err 484 } 485 486 func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size int64, options ...fs.OpenOption) error { 487 if size > int64(200e9) { // max size 200GB 488 return errors.New("file too big, can't upload") 489 } else if size == 0 { 490 return fs.ErrorCantUploadEmptyFiles 491 } 492 // yes it does take 4 requests if we're uploading to root and 6+ if we're uploading to any subdir :( 493 494 // create upload request 495 opts := rest.Opts{ 496 Method: "GET", 497 Path: "/upload", 498 } 499 token := api.Token{ 500 Token: f.opt.AccessToken, 501 } 502 var info api.UploadInfo 503 err := f.pacer.Call(func() (bool, error) { 504 resp, err := f.srv.CallJSON(ctx, &opts, &token, &info) 505 return shouldRetry(ctx, resp, err) 506 }) 507 if err != nil { 508 return err 509 } 510 if info.StatusCode != 0 { 511 return fmt.Errorf("putUnchecked api error: %d - %s", info.StatusCode, info.Message) 512 } 513 // we need to have a safe name for the upload to work 514 tmpName := "rcloneTemp" + random.String(8) 515 upload, err := f.uploadFile(ctx, in, size, tmpName, info.Data.UploadLink, options...) 516 if err != nil { 517 return err 518 } 519 if len(upload.Files) != 1 { 520 return errors.New("upload unexpected response") 521 } 522 match := f.IDRegexp.FindStringSubmatch(upload.Files[0].URL) 523 524 // move file to destination folder 525 base, leaf := f.splitPath(remote) 526 fullBase := f.dirPath(base) 527 528 if fullBase != "//" { 529 // make all the parent folders 530 err = f.Mkdir(ctx, base) 531 if err != nil { 532 // this might need some more error handling. if any of the following requests fail 533 // we'll leave an orphaned temporary file floating around somewhere 534 // they rarely fail though 535 return err 536 } 537 538 err = f.move(ctx, fullBase, match[1]) 539 if err != nil { 540 return err 541 } 542 } 543 544 // rename file to final name 545 err = f.updateFileInformation(ctx, &api.UpdateFileInformation{ 546 Token: f.opt.AccessToken, 547 FileCode: match[1], 548 NewName: f.opt.Enc.FromStandardName(leaf), 549 Public: f.public, 550 }) 551 if err != nil { 552 return err 553 } 554 555 return nil 556 } 557 558 // Put in to the remote path with the modTime given of the given size 559 // 560 // When called from outside an Fs by rclone, src.Size() will always be >= 0. 561 // But for unknown-sized objects (indicated by src.Size() == -1), Put should either 562 // return an error or upload it properly (rather than e.g. calling panic). 563 // 564 // May create the object even if it returns an error - if so 565 // will return the object and the error, otherwise will return 566 // nil and the error 567 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 568 existingObj, err := f.NewObject(ctx, src.Remote()) 569 switch err { 570 case nil: 571 return existingObj, existingObj.Update(ctx, in, src, options...) 572 case fs.ErrorObjectNotFound: 573 // Not found so create it 574 return f.PutUnchecked(ctx, in, src, options...) 575 default: 576 return nil, err 577 } 578 } 579 580 // PutUnchecked uploads the object 581 // 582 // This will create a duplicate if we upload a new file without 583 // checking to see if there is one already - use Put() for that. 584 func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 585 err := f.putUnchecked(ctx, in, src.Remote(), src.Size(), options...) 586 if err != nil { 587 return nil, err 588 } 589 return f.NewObject(ctx, src.Remote()) 590 } 591 592 // CreateDir dir creates a directory with the given parent path 593 // base starts from root and may or may not include // 594 func (f *Fs) CreateDir(ctx context.Context, base string, leaf string) (err error) { 595 base = "//" + strings.Trim(base, "/") 596 597 var resp *http.Response 598 var apiErr api.Error 599 opts := rest.Opts{ 600 Method: "PUT", 601 Path: "/user/files", 602 } 603 mkdir := api.CreateFolderRequest{ 604 Name: f.opt.Enc.FromStandardName(leaf), 605 Path: f.opt.Enc.FromStandardPath(base), 606 Token: f.opt.AccessToken, 607 } 608 err = f.pacer.Call(func() (bool, error) { 609 resp, err = f.srv.CallJSON(ctx, &opts, &mkdir, &apiErr) 610 return shouldRetry(ctx, resp, err) 611 }) 612 if err != nil { 613 return err 614 } 615 // checking if the dir exists beforehand would be slower so we'll just ignore the error here 616 if apiErr.StatusCode != 0 && !strings.Contains(apiErr.Data, "already exists") { 617 return apiErr 618 } 619 return nil 620 } 621 622 func (f *Fs) mkDirs(ctx context.Context, path string) (err error) { 623 // chop of any leading or trailing slashes 624 dirs := strings.Split(path, "/") 625 var base = "" 626 for _, element := range dirs { 627 // create every dir one by one 628 if element != "" { 629 err = f.CreateDir(ctx, base, element) 630 if err != nil { 631 return err 632 } 633 base += "/" + element 634 } 635 } 636 return nil 637 } 638 639 // Mkdir makes the directory (container, bucket) 640 // 641 // Shouldn't return an error if it already exists 642 func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) { 643 if dir == "" || dir == "." { 644 return f.mkDirs(ctx, f.root) 645 } 646 return f.mkDirs(ctx, path.Join(f.root, dir)) 647 } 648 649 // may or may not delete folders with contents? 650 func (f *Fs) purge(ctx context.Context, folderID uint64) (err error) { 651 var resp *http.Response 652 var apiErr api.Error 653 opts := rest.Opts{ 654 Method: "DELETE", 655 Path: "/user/files", 656 } 657 rm := api.DeleteFolderRequest{ 658 FolderID: folderID, 659 Token: f.opt.AccessToken, 660 } 661 err = f.pacer.Call(func() (bool, error) { 662 resp, err = f.srv.CallJSON(ctx, &opts, &rm, &apiErr) 663 return shouldRetry(ctx, resp, err) 664 }) 665 if err != nil { 666 return err 667 } 668 if apiErr.StatusCode != 0 { 669 return apiErr 670 } 671 return nil 672 } 673 674 // Rmdir removes the directory (container, bucket) if empty 675 // 676 // Return an error if it doesn't exist or isn't empty 677 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 678 info, err := f.readMetaDataForPath(ctx, f.dirPath(dir), &api.MetadataRequestOptions{Limit: 10}) 679 if err != nil { 680 return err 681 } 682 if len(info.Data.Folders) > 0 || len(info.Data.Files) > 0 { 683 return fs.ErrorDirectoryNotEmpty 684 } 685 686 return f.purge(ctx, info.Data.CurrentFolder.FolderID) 687 } 688 689 // Move src to this remote using server side move operations. 690 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 691 srcObj, ok := src.(*Object) 692 if !ok { 693 fs.Debugf(src, "Can't move - not same remote type") 694 return nil, fs.ErrorCantMove 695 } 696 697 srcBase, srcLeaf := srcObj.fs.splitPathFull(src.Remote()) 698 dstBase, dstLeaf := f.splitPathFull(remote) 699 700 needRename := srcLeaf != dstLeaf 701 needMove := srcBase != dstBase 702 703 // do the move if required 704 if needMove { 705 err := f.mkDirs(ctx, strings.Trim(dstBase, "/")) 706 if err != nil { 707 return nil, fmt.Errorf("move: failed to make destination dirs: %w", err) 708 } 709 710 err = f.move(ctx, dstBase, srcObj.code) 711 if err != nil { 712 return nil, err 713 } 714 } 715 716 // rename to final name if we need to 717 if needRename { 718 err := f.updateFileInformation(ctx, &api.UpdateFileInformation{ 719 Token: f.opt.AccessToken, 720 FileCode: srcObj.code, 721 NewName: f.opt.Enc.FromStandardName(dstLeaf), 722 Public: f.public, 723 }) 724 if err != nil { 725 return nil, fmt.Errorf("move: failed final rename: %w", err) 726 } 727 } 728 729 // copy the old object and apply the changes 730 newObj := *srcObj 731 newObj.remote = remote 732 newObj.fs = f 733 return &newObj, nil 734 } 735 736 // renameDir renames a directory 737 func (f *Fs) renameDir(ctx context.Context, folderID uint64, newName string) (err error) { 738 var resp *http.Response 739 var apiErr api.Error 740 opts := rest.Opts{ 741 Method: "PATCH", 742 Path: "/user/files", 743 } 744 rename := api.RenameFolderRequest{ 745 Token: f.opt.AccessToken, 746 FolderID: folderID, 747 NewName: newName, 748 } 749 err = f.pacer.Call(func() (bool, error) { 750 resp, err = f.srv.CallJSON(ctx, &opts, &rename, &apiErr) 751 return shouldRetry(ctx, resp, err) 752 }) 753 if err != nil { 754 return err 755 } 756 if apiErr.StatusCode != 0 { 757 return apiErr 758 } 759 return nil 760 } 761 762 // DirMove moves src, srcRemote to this remote at dstRemote 763 // using server-side move operations. 764 // 765 // Will only be called if src.Fs().Name() == f.Name() 766 // 767 // If it isn't possible then return fs.ErrorCantDirMove 768 // 769 // If destination exists then return fs.ErrorDirExists 770 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 771 srcFs, ok := src.(*Fs) 772 if !ok { 773 fs.Debugf(srcFs, "Can't move directory - not same remote type") 774 return fs.ErrorCantDirMove 775 } 776 777 // find out source 778 srcPath := srcFs.dirPath(srcRemote) 779 srcInfo, err := f.readMetaDataForPath(ctx, srcPath, &api.MetadataRequestOptions{Limit: 1}) 780 if err != nil { 781 return fmt.Errorf("dirmove: source not found: %w", err) 782 } 783 // check if the destination already exists 784 dstPath := f.dirPath(dstRemote) 785 _, err = f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 1}) 786 if err == nil { 787 return fs.ErrorDirExists 788 } 789 790 // make the destination parent path 791 dstBase, dstName := f.splitPathFull(dstRemote) 792 err = f.mkDirs(ctx, strings.Trim(dstBase, "/")) 793 if err != nil { 794 return fmt.Errorf("dirmove: failed to create dirs: %w", err) 795 } 796 797 // find the destination parent dir 798 dstInfo, err := f.readMetaDataForPath(ctx, dstBase, &api.MetadataRequestOptions{Limit: 1}) 799 if err != nil { 800 return fmt.Errorf("dirmove: failed to read destination: %w", err) 801 } 802 srcBase, srcName := srcFs.splitPathFull(srcRemote) 803 804 needRename := srcName != dstName 805 needMove := srcBase != dstBase 806 807 // if we have to rename we'll have to use a temporary name since 808 // there could already be a directory with the same name as the src directory 809 if needRename { 810 // rename to a temporary name 811 tmpName := "rcloneTemp" + random.String(8) 812 err = f.renameDir(ctx, srcInfo.Data.CurrentFolder.FolderID, tmpName) 813 if err != nil { 814 return fmt.Errorf("dirmove: failed initial rename: %w", err) 815 } 816 } 817 818 // do the move 819 if needMove { 820 opts := rest.Opts{ 821 Method: "PATCH", 822 Path: "/user/files", 823 } 824 move := api.MoveFolderRequest{ 825 Token: f.opt.AccessToken, 826 FolderID: srcInfo.Data.CurrentFolder.FolderID, 827 DestinationFolderID: dstInfo.Data.CurrentFolder.FolderID, 828 Action: "move", 829 } 830 var resp *http.Response 831 var apiErr api.Error 832 err = f.pacer.Call(func() (bool, error) { 833 resp, err = f.srv.CallJSON(ctx, &opts, &move, &apiErr) 834 return shouldRetry(ctx, resp, err) 835 }) 836 if err != nil { 837 return fmt.Errorf("dirmove: failed to move: %w", err) 838 } 839 if apiErr.StatusCode != 0 { 840 return apiErr 841 } 842 } 843 844 // rename to final name 845 if needRename { 846 err = f.renameDir(ctx, srcInfo.Data.CurrentFolder.FolderID, dstName) 847 if err != nil { 848 return fmt.Errorf("dirmove: failed final rename: %w", err) 849 } 850 } 851 return nil 852 } 853 854 func (f *Fs) copy(ctx context.Context, dstPath string, fileID string) (err error) { 855 meta, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 10}) 856 if err != nil { 857 return err 858 } 859 860 opts := rest.Opts{ 861 Method: "PATCH", 862 Path: "/user/files", 863 } 864 cp := api.CopyMoveFileRequest{ 865 Token: f.opt.AccessToken, 866 FileCodes: fileID, 867 DestinationFolderID: meta.Data.CurrentFolder.FolderID, 868 Action: "copy", 869 } 870 871 var resp *http.Response 872 var info api.UpdateResponse 873 err = f.pacer.Call(func() (bool, error) { 874 resp, err = f.srv.CallJSON(ctx, &opts, &cp, &info) 875 return shouldRetry(ctx, resp, err) 876 }) 877 if err != nil { 878 return fmt.Errorf("couldn't copy file: %w", err) 879 } 880 if info.StatusCode != 0 { 881 return fmt.Errorf("copy: api error: %d - %s", info.StatusCode, info.Message) 882 } 883 return err 884 } 885 886 // Copy src to this remote using server side move operations. 887 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 888 srcObj, ok := src.(*Object) 889 if !ok { 890 fs.Debugf(src, "Can't copy - not same remote type") 891 return nil, fs.ErrorCantMove 892 } 893 894 _, srcLeaf := f.splitPath(src.Remote()) 895 dstBase, dstLeaf := f.splitPath(remote) 896 897 needRename := srcLeaf != dstLeaf 898 899 err := f.mkDirs(ctx, path.Join(f.root, dstBase)) 900 if err != nil { 901 return nil, fmt.Errorf("copy: failed to make destination dirs: %w", err) 902 } 903 904 err = f.copy(ctx, f.dirPath(dstBase), srcObj.code) 905 if err != nil { 906 return nil, err 907 } 908 909 newObj, err := f.NewObject(ctx, path.Join(dstBase, srcLeaf)) 910 if err != nil { 911 return nil, fmt.Errorf("copy: couldn't find copied object: %w", err) 912 } 913 914 if needRename { 915 err := f.updateFileInformation(ctx, &api.UpdateFileInformation{ 916 Token: f.opt.AccessToken, 917 FileCode: newObj.(*Object).code, 918 NewName: f.opt.Enc.FromStandardName(dstLeaf), 919 Public: f.public, 920 }) 921 if err != nil { 922 return nil, fmt.Errorf("copy: failed final rename: %w", err) 923 } 924 newObj.(*Object).remote = remote 925 } 926 927 return newObj, nil 928 } 929 930 // ------------------------------------------------------------ 931 932 // Fs returns the parent Fs 933 func (o *Object) Fs() fs.Info { 934 return o.fs 935 } 936 937 // Return a string version 938 func (o *Object) String() string { 939 if o == nil { 940 return "<nil>" 941 } 942 return o.remote 943 } 944 945 // Remote returns the remote path 946 func (o *Object) Remote() string { 947 return o.remote 948 } 949 950 // ModTime returns the modification time of the object 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 ci := fs.GetConfig(ctx) 956 return time.Time(ci.DefaultTime) 957 } 958 959 // Size returns the size of an object in bytes 960 func (o *Object) Size() int64 { 961 return o.size 962 } 963 964 // Hash returns the Md5sum of an object returning a lowercase hex string 965 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 966 return "", hash.ErrUnsupported 967 } 968 969 // ID returns the ID of the Object if known, or "" if not 970 func (o *Object) ID() string { 971 return o.code 972 } 973 974 // Storable returns whether this object is storable 975 func (o *Object) Storable() bool { 976 return true 977 } 978 979 // SetModTime sets the modification time of the local fs object 980 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 981 return fs.ErrorCantSetModTime 982 } 983 984 // Open an object for read 985 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 986 opts := rest.Opts{ 987 Method: "GET", 988 Path: "/link", 989 Parameters: url.Values{ 990 "token": []string{o.fs.opt.AccessToken}, 991 "file_code": []string{o.code}, 992 }, 993 } 994 var dl api.Download 995 var resp *http.Response 996 err = o.fs.pacer.Call(func() (bool, error) { 997 resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl) 998 return shouldRetry(ctx, resp, err) 999 }) 1000 if err != nil { 1001 return nil, fmt.Errorf("open: failed to get download link: %w", err) 1002 } 1003 1004 fs.FixRangeOption(options, o.size) 1005 opts = rest.Opts{ 1006 Method: "GET", 1007 RootURL: dl.Data.DownloadLink, 1008 Options: options, 1009 } 1010 1011 err = o.fs.pacer.Call(func() (bool, error) { 1012 resp, err = o.fs.srv.Call(ctx, &opts) 1013 return shouldRetry(ctx, resp, err) 1014 }) 1015 1016 if err != nil { 1017 return nil, err 1018 } 1019 return resp.Body, err 1020 } 1021 1022 // Update the already existing object 1023 // 1024 // Copy the reader into the object updating modTime and size. 1025 // 1026 // The new object may have been created if an error is returned 1027 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { 1028 if src.Size() < 0 { 1029 return errors.New("refusing to update with unknown size") 1030 } 1031 1032 // upload with new size but old name 1033 err := o.fs.putUnchecked(ctx, in, o.Remote(), src.Size(), options...) 1034 if err != nil { 1035 return err 1036 } 1037 1038 // delete duplicate object after successful upload 1039 err = o.Remove(ctx) 1040 if err != nil { 1041 return fmt.Errorf("failed to remove old version: %w", err) 1042 } 1043 1044 // Fetch new object after deleting the duplicate 1045 info, err := o.fs.NewObject(ctx, o.Remote()) 1046 if err != nil { 1047 return err 1048 } 1049 1050 // Replace guts of old object with new one 1051 *o = *info.(*Object) 1052 1053 return nil 1054 } 1055 1056 // Remove an object 1057 func (o *Object) Remove(ctx context.Context) error { 1058 opts := rest.Opts{ 1059 Method: "DELETE", 1060 Path: "/user/files", 1061 } 1062 delete := api.RemoveFileRequest{ 1063 Token: o.fs.opt.AccessToken, 1064 FileCodes: o.code, 1065 } 1066 var info api.UpdateResponse 1067 err := o.fs.pacer.Call(func() (bool, error) { 1068 resp, err := o.fs.srv.CallJSON(ctx, &opts, &delete, &info) 1069 return shouldRetry(ctx, resp, err) 1070 }) 1071 if err != nil { 1072 return err 1073 } 1074 if info.StatusCode != 0 { 1075 return fmt.Errorf("remove: api error: %d - %s", info.StatusCode, info.Message) 1076 } 1077 return nil 1078 } 1079 1080 // Check the interfaces are satisfied 1081 var ( 1082 _ fs.Fs = (*Fs)(nil) 1083 _ fs.Copier = (*Fs)(nil) 1084 _ fs.Mover = (*Fs)(nil) 1085 _ fs.DirMover = (*Fs)(nil) 1086 _ fs.Object = (*Object)(nil) 1087 )