github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/yandex/yandex.go (about) 1 package yandex 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "log" 9 "net/http" 10 "net/url" 11 "path" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/ncw/rclone/backend/yandex/api" 17 "github.com/ncw/rclone/fs" 18 "github.com/ncw/rclone/fs/config" 19 "github.com/ncw/rclone/fs/config/configmap" 20 "github.com/ncw/rclone/fs/config/configstruct" 21 "github.com/ncw/rclone/fs/config/obscure" 22 "github.com/ncw/rclone/fs/fserrors" 23 "github.com/ncw/rclone/fs/hash" 24 "github.com/ncw/rclone/lib/oauthutil" 25 "github.com/ncw/rclone/lib/pacer" 26 "github.com/ncw/rclone/lib/readers" 27 "github.com/ncw/rclone/lib/rest" 28 "github.com/pkg/errors" 29 "golang.org/x/oauth2" 30 ) 31 32 //oAuth 33 const ( 34 rcloneClientID = "ac39b43b9eba4cae8ffb788c06d816a8" 35 rcloneEncryptedClientSecret = "EfyyNZ3YUEwXM5yAhi72G9YwKn2mkFrYwJNS7cY0TJAhFlX9K-uJFbGlpO-RYjrJ" 36 rootURL = "https://cloud-api.yandex.com/v1/disk" 37 minSleep = 10 * time.Millisecond 38 maxSleep = 2 * time.Second // may needs to be increased, testing needed 39 decayConstant = 2 // bigger for slower decay, exponential 40 ) 41 42 // Globals 43 var ( 44 // Description of how to auth for this app 45 oauthConfig = &oauth2.Config{ 46 Endpoint: oauth2.Endpoint{ 47 AuthURL: "https://oauth.yandex.com/authorize", //same as https://oauth.yandex.ru/authorize 48 TokenURL: "https://oauth.yandex.com/token", //same as https://oauth.yandex.ru/token 49 }, 50 ClientID: rcloneClientID, 51 ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret), 52 RedirectURL: oauthutil.RedirectURL, 53 } 54 ) 55 56 // Register with Fs 57 func init() { 58 fs.Register(&fs.RegInfo{ 59 Name: "yandex", 60 Description: "Yandex Disk", 61 NewFs: NewFs, 62 Config: func(name string, m configmap.Mapper) { 63 err := oauthutil.Config("yandex", name, m, oauthConfig) 64 if err != nil { 65 log.Fatalf("Failed to configure token: %v", err) 66 return 67 } 68 }, 69 Options: []fs.Option{{ 70 Name: config.ConfigClientID, 71 Help: "Yandex Client Id\nLeave blank normally.", 72 }, { 73 Name: config.ConfigClientSecret, 74 Help: "Yandex Client Secret\nLeave blank normally.", 75 }, { 76 Name: "unlink", 77 Help: "Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.", 78 Default: false, 79 Advanced: true, 80 }}, 81 }) 82 } 83 84 // Options defines the configuration for this backend 85 type Options struct { 86 Token string `config:"token"` 87 Unlink bool `config:"unlink"` 88 } 89 90 // Fs represents a remote yandex 91 type Fs struct { 92 name string 93 root string // root path 94 opt Options // parsed options 95 features *fs.Features // optional features 96 srv *rest.Client // the connection to the yandex server 97 pacer *fs.Pacer // pacer for API calls 98 diskRoot string // root path with "disk:/" container name 99 } 100 101 // Object describes a swift object 102 type Object struct { 103 fs *Fs // what this object is part of 104 remote string // The remote path 105 hasMetaData bool // whether info below has been set 106 md5sum string // The MD5Sum of the object 107 size int64 // Bytes in the object 108 modTime time.Time // Modified time of the object 109 mimeType string // Content type according to the server 110 111 } 112 113 // ------------------------------------------------------------ 114 115 // Name of the remote (as passed into NewFs) 116 func (f *Fs) Name() string { 117 return f.name 118 } 119 120 // Root of the remote (as passed into NewFs) 121 func (f *Fs) Root() string { 122 return f.root 123 } 124 125 // String converts this Fs to a string 126 func (f *Fs) String() string { 127 return fmt.Sprintf("Yandex %s", f.root) 128 } 129 130 // Precision return the precision of this Fs 131 func (f *Fs) Precision() time.Duration { 132 return time.Nanosecond 133 } 134 135 // Hashes returns the supported hash sets. 136 func (f *Fs) Hashes() hash.Set { 137 return hash.Set(hash.MD5) 138 } 139 140 // Features returns the optional features of this Fs 141 func (f *Fs) Features() *fs.Features { 142 return f.features 143 } 144 145 // retryErrorCodes is a slice of error codes that we will retry 146 var retryErrorCodes = []int{ 147 429, // Too Many Requests. 148 500, // Internal Server Error 149 502, // Bad Gateway 150 503, // Service Unavailable 151 504, // Gateway Timeout 152 509, // Bandwidth Limit Exceeded 153 } 154 155 // shouldRetry returns a boolean as to whether this resp and err 156 // deserve to be retried. It returns the err as a convenience 157 func shouldRetry(resp *http.Response, err error) (bool, error) { 158 return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 159 } 160 161 // errorHandler parses a non 2xx error response into an error 162 func errorHandler(resp *http.Response) error { 163 // Decode error response 164 errResponse := new(api.ErrorResponse) 165 err := rest.DecodeJSON(resp, &errResponse) 166 if err != nil { 167 fs.Debugf(nil, "Couldn't decode error response: %v", err) 168 } 169 if errResponse.Message == "" { 170 errResponse.Message = resp.Status 171 } 172 if errResponse.StatusCode == 0 { 173 errResponse.StatusCode = resp.StatusCode 174 } 175 return errResponse 176 } 177 178 // Sets root in f 179 func (f *Fs) setRoot(root string) { 180 //Set root path 181 f.root = strings.Trim(root, "/") 182 //Set disk root path. 183 //Adding "disk:" to root path as all paths on disk start with it 184 var diskRoot string 185 if f.root == "" { 186 diskRoot = "disk:/" 187 } else { 188 diskRoot = "disk:/" + f.root + "/" 189 } 190 f.diskRoot = diskRoot 191 } 192 193 // filePath returns a escaped file path (f.root, file) 194 func (f *Fs) filePath(file string) string { 195 return path.Join(f.diskRoot, file) 196 } 197 198 // dirPath returns a escaped file path (f.root, file) ending with '/' 199 func (f *Fs) dirPath(file string) string { 200 return path.Join(f.diskRoot, file) + "/" 201 } 202 203 func (f *Fs) readMetaDataForPath(path string, options *api.ResourceInfoRequestOptions) (*api.ResourceInfoResponse, error) { 204 opts := rest.Opts{ 205 Method: "GET", 206 Path: "/resources", 207 Parameters: url.Values{}, 208 } 209 210 opts.Parameters.Set("path", path) 211 212 if options.SortMode != nil { 213 opts.Parameters.Set("sort", options.SortMode.String()) 214 } 215 if options.Limit != 0 { 216 opts.Parameters.Set("limit", strconv.FormatUint(options.Limit, 10)) 217 } 218 if options.Offset != 0 { 219 opts.Parameters.Set("offset", strconv.FormatUint(options.Offset, 10)) 220 } 221 if options.Fields != nil { 222 opts.Parameters.Set("fields", strings.Join(options.Fields, ",")) 223 } 224 225 var err error 226 var info api.ResourceInfoResponse 227 var resp *http.Response 228 err = f.pacer.Call(func() (bool, error) { 229 resp, err = f.srv.CallJSON(&opts, nil, &info) 230 return shouldRetry(resp, err) 231 }) 232 233 if err != nil { 234 return nil, err 235 } 236 237 return &info, nil 238 } 239 240 // NewFs constructs an Fs from the path, container:path 241 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 242 // Parse config into Options struct 243 opt := new(Options) 244 err := configstruct.Set(m, opt) 245 if err != nil { 246 return nil, err 247 } 248 249 token, err := oauthutil.GetToken(name, m) 250 if err != nil { 251 log.Fatalf("Couldn't read OAuth token (this should never happen).") 252 } 253 if token.RefreshToken == "" { 254 log.Fatalf("Unable to get RefreshToken. If you are upgrading from older versions of rclone, please run `rclone config` and re-configure this backend.") 255 } 256 if token.TokenType != "OAuth" { 257 token.TokenType = "OAuth" 258 err = oauthutil.PutToken(name, m, token, false) 259 if err != nil { 260 log.Fatalf("Couldn't save OAuth token (this should never happen).") 261 } 262 log.Printf("Automatically upgraded OAuth config.") 263 } 264 oAuthClient, _, err := oauthutil.NewClient(name, m, oauthConfig) 265 if err != nil { 266 log.Fatalf("Failed to configure Yandex: %v", err) 267 } 268 269 f := &Fs{ 270 name: name, 271 opt: *opt, 272 srv: rest.NewClient(oAuthClient).SetRoot(rootURL), 273 pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), 274 } 275 f.setRoot(root) 276 f.features = (&fs.Features{ 277 ReadMimeType: true, 278 WriteMimeType: true, 279 CanHaveEmptyDirectories: true, 280 }).Fill(f) 281 f.srv.SetErrorHandler(errorHandler) 282 283 // Check to see if the object exists and is a file 284 //request object meta info 285 // Check to see if the object exists and is a file 286 //request object meta info 287 if info, err := f.readMetaDataForPath(f.diskRoot, &api.ResourceInfoRequestOptions{}); err != nil { 288 289 } else { 290 if info.ResourceType == "file" { 291 rootDir := path.Dir(root) 292 if rootDir == "." { 293 rootDir = "" 294 } 295 f.setRoot(rootDir) 296 // return an error with an fs which points to the parent 297 return f, fs.ErrorIsFile 298 } 299 } 300 return f, nil 301 } 302 303 // Convert a list item into a DirEntry 304 func (f *Fs) itemToDirEntry(remote string, object *api.ResourceInfoResponse) (fs.DirEntry, error) { 305 switch object.ResourceType { 306 case "dir": 307 t, err := time.Parse(time.RFC3339Nano, object.Modified) 308 if err != nil { 309 return nil, errors.Wrap(err, "error parsing time in directory item") 310 } 311 d := fs.NewDir(remote, t).SetSize(object.Size) 312 return d, nil 313 case "file": 314 o, err := f.newObjectWithInfo(remote, object) 315 if err != nil { 316 return nil, err 317 } 318 return o, nil 319 default: 320 fs.Debugf(f, "Unknown resource type %q", object.ResourceType) 321 } 322 return nil, nil 323 } 324 325 // List the objects and directories in dir into entries. The 326 // entries can be returned in any order but should be for a 327 // complete directory. 328 // 329 // dir should be "" to list the root, and should not have 330 // trailing slashes. 331 // 332 // This should return ErrDirNotFound if the directory isn't 333 // found. 334 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 335 root := f.dirPath(dir) 336 337 var limit uint64 = 1000 // max number of objects per request 338 var itemsCount uint64 // number of items per page in response 339 var offset uint64 // for the next page of requests 340 341 for { 342 opts := &api.ResourceInfoRequestOptions{ 343 Limit: limit, 344 Offset: offset, 345 } 346 info, err := f.readMetaDataForPath(root, opts) 347 348 if err != nil { 349 if apiErr, ok := err.(*api.ErrorResponse); ok { 350 // does not exist 351 if apiErr.ErrorName == "DiskNotFoundError" { 352 return nil, fs.ErrorDirNotFound 353 } 354 } 355 return nil, err 356 } 357 itemsCount = uint64(len(info.Embedded.Items)) 358 359 if info.ResourceType == "dir" { 360 //list all subdirs 361 for _, element := range info.Embedded.Items { 362 remote := path.Join(dir, element.Name) 363 entry, err := f.itemToDirEntry(remote, &element) 364 if err != nil { 365 return nil, err 366 } 367 if entry != nil { 368 entries = append(entries, entry) 369 } 370 } 371 } else if info.ResourceType == "file" { 372 return nil, fs.ErrorIsFile 373 } 374 375 //offset for the next page of items 376 offset += itemsCount 377 //check if we reached end of list 378 if itemsCount < limit { 379 break 380 } 381 } 382 383 return entries, nil 384 } 385 386 // Return an Object from a path 387 // 388 // If it can't be found it returns the error fs.ErrorObjectNotFound. 389 func (f *Fs) newObjectWithInfo(remote string, info *api.ResourceInfoResponse) (fs.Object, error) { 390 o := &Object{ 391 fs: f, 392 remote: remote, 393 } 394 var err error 395 if info != nil { 396 err = o.setMetaData(info) 397 } else { 398 err = o.readMetaData() 399 if apiErr, ok := err.(*api.ErrorResponse); ok { 400 // does not exist 401 if apiErr.ErrorName == "DiskNotFoundError" { 402 return nil, fs.ErrorObjectNotFound 403 } 404 } 405 } 406 if err != nil { 407 return nil, err 408 } 409 return o, nil 410 } 411 412 // NewObject finds the Object at remote. If it can't be found it 413 // returns the error fs.ErrorObjectNotFound. 414 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 415 return f.newObjectWithInfo(remote, nil) 416 } 417 418 // Creates from the parameters passed in a half finished Object which 419 // must have setMetaData called on it 420 // 421 // Used to create new objects 422 func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Object) { 423 // Temporary Object under construction 424 o = &Object{ 425 fs: f, 426 remote: remote, 427 size: size, 428 modTime: modTime, 429 } 430 return o 431 } 432 433 // Put the object 434 // 435 // Copy the reader in to the new object which is returned 436 // 437 // The new object may have been created if an error is returned 438 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 439 o := f.createObject(src.Remote(), src.ModTime(ctx), src.Size()) 440 return o, o.Update(ctx, in, src, options...) 441 } 442 443 // PutStream uploads to the remote path with the modTime given of indeterminate size 444 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 445 return f.Put(ctx, in, src, options...) 446 } 447 448 // CreateDir makes a directory 449 func (f *Fs) CreateDir(path string) (err error) { 450 //fmt.Printf("CreateDir: %s\n", path) 451 452 var resp *http.Response 453 opts := rest.Opts{ 454 Method: "PUT", 455 Path: "/resources", 456 Parameters: url.Values{}, 457 NoResponse: true, 458 } 459 460 opts.Parameters.Set("path", path) 461 462 err = f.pacer.Call(func() (bool, error) { 463 resp, err = f.srv.Call(&opts) 464 return shouldRetry(resp, err) 465 }) 466 if err != nil { 467 //fmt.Printf("CreateDir Error: %s\n", err.Error()) 468 return err 469 } 470 // fmt.Printf("...Id %q\n", *info.Id) 471 return nil 472 } 473 474 // This really needs improvement and especially proper error checking 475 // but Yandex does not publish a List of possible errors and when they're 476 // expected to occur. 477 func (f *Fs) mkDirs(path string) (err error) { 478 //trim filename from path 479 //dirString := strings.TrimSuffix(path, filepath.Base(path)) 480 //trim "disk:" from path 481 dirString := strings.TrimPrefix(path, "disk:") 482 if dirString == "" { 483 return nil 484 } 485 486 if err = f.CreateDir(dirString); err != nil { 487 if apiErr, ok := err.(*api.ErrorResponse); ok { 488 // allready exists 489 if apiErr.ErrorName != "DiskPathPointsToExistentDirectoryError" { 490 // 2 if it fails then create all directories in the path from root. 491 dirs := strings.Split(dirString, "/") //path separator 492 var mkdirpath = "/" //path separator / 493 for _, element := range dirs { 494 if element != "" { 495 mkdirpath += element + "/" //path separator / 496 if err = f.CreateDir(mkdirpath); err != nil { 497 // ignore errors while creating dirs 498 } 499 } 500 } 501 } 502 return nil 503 } 504 } 505 return err 506 } 507 508 func (f *Fs) mkParentDirs(resPath string) error { 509 // defer log.Trace(dirPath, "")("") 510 // chop off trailing / if it exists 511 if strings.HasSuffix(resPath, "/") { 512 resPath = resPath[:len(resPath)-1] 513 } 514 parent := path.Dir(resPath) 515 if parent == "." { 516 parent = "" 517 } 518 return f.mkDirs(parent) 519 } 520 521 // Mkdir creates the container if it doesn't exist 522 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 523 path := f.filePath(dir) 524 return f.mkDirs(path) 525 } 526 527 // waitForJob waits for the job with status in url to complete 528 func (f *Fs) waitForJob(location string) (err error) { 529 opts := rest.Opts{ 530 RootURL: location, 531 Method: "GET", 532 } 533 deadline := time.Now().Add(fs.Config.Timeout) 534 for time.Now().Before(deadline) { 535 var resp *http.Response 536 var body []byte 537 err = f.pacer.Call(func() (bool, error) { 538 resp, err = f.srv.Call(&opts) 539 if err != nil { 540 return fserrors.ShouldRetry(err), err 541 } 542 body, err = rest.ReadBody(resp) 543 return fserrors.ShouldRetry(err), err 544 }) 545 if err != nil { 546 return err 547 } 548 // Try to decode the body first as an api.AsyncOperationStatus 549 var status api.AsyncStatus 550 err = json.Unmarshal(body, &status) 551 if err != nil { 552 return errors.Wrapf(err, "async status result not JSON: %q", body) 553 } 554 555 switch status.Status { 556 case "failure": 557 return errors.Errorf("async operation returned %q", status.Status) 558 case "success": 559 return nil 560 } 561 562 time.Sleep(1 * time.Second) 563 } 564 return errors.Errorf("async operation didn't complete after %v", fs.Config.Timeout) 565 } 566 567 func (f *Fs) delete(path string, hardDelete bool) (err error) { 568 opts := rest.Opts{ 569 Method: "DELETE", 570 Path: "/resources", 571 Parameters: url.Values{}, 572 } 573 574 opts.Parameters.Set("path", path) 575 opts.Parameters.Set("permanently", strconv.FormatBool(hardDelete)) 576 577 var resp *http.Response 578 var body []byte 579 err = f.pacer.Call(func() (bool, error) { 580 resp, err = f.srv.Call(&opts) 581 if err != nil { 582 return fserrors.ShouldRetry(err), err 583 } 584 body, err = rest.ReadBody(resp) 585 return fserrors.ShouldRetry(err), err 586 }) 587 if err != nil { 588 return err 589 } 590 591 // if 202 Accepted it's an async operation we have to wait for it complete before retuning 592 if resp.StatusCode == 202 { 593 var info api.AsyncInfo 594 err = json.Unmarshal(body, &info) 595 if err != nil { 596 return errors.Wrapf(err, "async info result not JSON: %q", body) 597 } 598 return f.waitForJob(info.HRef) 599 } 600 return nil 601 } 602 603 // purgeCheck remotes the root directory, if check is set then it 604 // refuses to do so if it has anything in 605 func (f *Fs) purgeCheck(dir string, check bool) error { 606 root := f.filePath(dir) 607 if check { 608 //to comply with rclone logic we check if the directory is empty before delete. 609 //send request to get list of objects in this directory. 610 info, err := f.readMetaDataForPath(root, &api.ResourceInfoRequestOptions{}) 611 if err != nil { 612 return errors.Wrap(err, "rmdir failed") 613 } 614 if len(info.Embedded.Items) != 0 { 615 return fs.ErrorDirectoryNotEmpty 616 } 617 } 618 //delete directory 619 return f.delete(root, false) 620 } 621 622 // Rmdir deletes the container 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(dir, true) 627 } 628 629 // Purge deletes all the files and the container 630 // 631 // Optional interface: Only implement this if you have a way of 632 // deleting all the files quicker than just running Remove() on the 633 // result of List() 634 func (f *Fs) Purge(ctx context.Context) error { 635 return f.purgeCheck("", false) 636 } 637 638 // copyOrMoves copies or moves directories or files depending on the method parameter 639 func (f *Fs) copyOrMove(method, src, dst string, overwrite bool) (err error) { 640 opts := rest.Opts{ 641 Method: "POST", 642 Path: "/resources/" + method, 643 Parameters: url.Values{}, 644 } 645 646 opts.Parameters.Set("from", src) 647 opts.Parameters.Set("path", dst) 648 opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite)) 649 650 var resp *http.Response 651 var body []byte 652 err = f.pacer.Call(func() (bool, error) { 653 resp, err = f.srv.Call(&opts) 654 if err != nil { 655 return fserrors.ShouldRetry(err), err 656 } 657 body, err = rest.ReadBody(resp) 658 return fserrors.ShouldRetry(err), err 659 }) 660 if err != nil { 661 return err 662 } 663 664 // if 202 Accepted it's an async operation we have to wait for it complete before retuning 665 if resp.StatusCode == 202 { 666 var info api.AsyncInfo 667 err = json.Unmarshal(body, &info) 668 if err != nil { 669 return errors.Wrapf(err, "async info result not JSON: %q", body) 670 } 671 return f.waitForJob(info.HRef) 672 } 673 return nil 674 } 675 676 // Copy src to this remote using server side copy operations. 677 // 678 // This is stored with the remote path given 679 // 680 // It returns the destination Object and a possible error 681 // 682 // Will only be called if src.Fs().Name() == f.Name() 683 // 684 // If it isn't possible then return fs.ErrorCantCopy 685 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 686 srcObj, ok := src.(*Object) 687 if !ok { 688 fs.Debugf(src, "Can't copy - not same remote type") 689 return nil, fs.ErrorCantCopy 690 } 691 692 dstPath := f.filePath(remote) 693 err := f.mkParentDirs(dstPath) 694 if err != nil { 695 return nil, err 696 } 697 err = f.copyOrMove("copy", srcObj.filePath(), dstPath, false) 698 699 if err != nil { 700 return nil, errors.Wrap(err, "couldn't copy file") 701 } 702 703 return f.NewObject(ctx, remote) 704 } 705 706 // Move src to this remote using server side move operations. 707 // 708 // This is stored with the remote path given 709 // 710 // It returns the destination Object and a possible error 711 // 712 // Will only be called if src.Fs().Name() == f.Name() 713 // 714 // If it isn't possible then return fs.ErrorCantMove 715 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 716 srcObj, ok := src.(*Object) 717 if !ok { 718 fs.Debugf(src, "Can't move - not same remote type") 719 return nil, fs.ErrorCantMove 720 } 721 722 dstPath := f.filePath(remote) 723 err := f.mkParentDirs(dstPath) 724 if err != nil { 725 return nil, err 726 } 727 err = f.copyOrMove("move", srcObj.filePath(), dstPath, false) 728 729 if err != nil { 730 return nil, errors.Wrap(err, "couldn't move file") 731 } 732 733 return f.NewObject(ctx, remote) 734 } 735 736 // DirMove moves src, srcRemote to this remote at dstRemote 737 // using server side move operations. 738 // 739 // Will only be called if src.Fs().Name() == f.Name() 740 // 741 // If it isn't possible then return fs.ErrorCantDirMove 742 // 743 // If destination exists then return fs.ErrorDirExists 744 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 745 srcFs, ok := src.(*Fs) 746 if !ok { 747 fs.Debugf(srcFs, "Can't move directory - not same remote type") 748 return fs.ErrorCantDirMove 749 } 750 srcPath := path.Join(srcFs.diskRoot, srcRemote) 751 dstPath := f.dirPath(dstRemote) 752 753 //fmt.Printf("Move src: %s (FullPath: %s), dst: %s (FullPath: %s)\n", srcRemote, srcPath, dstRemote, dstPath) 754 755 // Refuse to move to or from the root 756 if srcPath == "disk:/" || dstPath == "disk:/" { 757 fs.Debugf(src, "DirMove error: Can't move root") 758 return errors.New("can't move root directory") 759 } 760 761 err := f.mkParentDirs(dstPath) 762 if err != nil { 763 return err 764 } 765 766 _, err = f.readMetaDataForPath(dstPath, &api.ResourceInfoRequestOptions{}) 767 if apiErr, ok := err.(*api.ErrorResponse); ok { 768 // does not exist 769 if apiErr.ErrorName == "DiskNotFoundError" { 770 // OK 771 } 772 } else if err != nil { 773 return err 774 } else { 775 return fs.ErrorDirExists 776 } 777 778 err = f.copyOrMove("move", srcPath, dstPath, false) 779 780 if err != nil { 781 return errors.Wrap(err, "couldn't move directory") 782 } 783 return nil 784 } 785 786 // PublicLink generates a public link to the remote path (usually readable by anyone) 787 func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err error) { 788 var path string 789 if f.opt.Unlink { 790 path = "/resources/unpublish" 791 } else { 792 path = "/resources/publish" 793 } 794 opts := rest.Opts{ 795 Method: "PUT", 796 Path: path, 797 Parameters: url.Values{}, 798 NoResponse: true, 799 } 800 801 opts.Parameters.Set("path", f.filePath(remote)) 802 803 var resp *http.Response 804 err = f.pacer.Call(func() (bool, error) { 805 resp, err = f.srv.Call(&opts) 806 return shouldRetry(resp, err) 807 }) 808 809 if apiErr, ok := err.(*api.ErrorResponse); ok { 810 // does not exist 811 if apiErr.ErrorName == "DiskNotFoundError" { 812 return "", fs.ErrorObjectNotFound 813 } 814 } 815 if err != nil { 816 if f.opt.Unlink { 817 return "", errors.Wrap(err, "couldn't remove public link") 818 } 819 return "", errors.Wrap(err, "couldn't create public link") 820 } 821 822 info, err := f.readMetaDataForPath(f.filePath(remote), &api.ResourceInfoRequestOptions{}) 823 if err != nil { 824 return "", err 825 } 826 827 if info.PublicURL == "" { 828 return "", errors.New("couldn't create public link - no link path received") 829 } 830 return info.PublicURL, nil 831 } 832 833 // CleanUp permanently deletes all trashed files/folders 834 func (f *Fs) CleanUp(ctx context.Context) (err error) { 835 var resp *http.Response 836 opts := rest.Opts{ 837 Method: "DELETE", 838 Path: "/trash/resources", 839 NoResponse: true, 840 } 841 842 err = f.pacer.Call(func() (bool, error) { 843 resp, err = f.srv.Call(&opts) 844 return shouldRetry(resp, err) 845 }) 846 return err 847 } 848 849 // About gets quota information 850 func (f *Fs) About(ctx context.Context) (*fs.Usage, error) { 851 opts := rest.Opts{ 852 Method: "GET", 853 Path: "/", 854 } 855 856 var resp *http.Response 857 var info api.DiskInfo 858 var err error 859 err = f.pacer.Call(func() (bool, error) { 860 resp, err = f.srv.CallJSON(&opts, nil, &info) 861 return shouldRetry(resp, err) 862 }) 863 864 if err != nil { 865 return nil, err 866 } 867 868 usage := &fs.Usage{ 869 Total: fs.NewUsageValue(info.TotalSpace), 870 Used: fs.NewUsageValue(info.UsedSpace), 871 Free: fs.NewUsageValue(info.TotalSpace - info.UsedSpace), 872 } 873 return usage, nil 874 } 875 876 // ------------------------------------------------------------ 877 878 // Fs returns the parent Fs 879 func (o *Object) Fs() fs.Info { 880 return o.fs 881 } 882 883 // Return a string version 884 func (o *Object) String() string { 885 if o == nil { 886 return "<nil>" 887 } 888 return o.remote 889 } 890 891 // Remote returns the remote path 892 func (o *Object) Remote() string { 893 return o.remote 894 } 895 896 // Returns the full remote path for the object 897 func (o *Object) filePath() string { 898 return o.fs.filePath(o.remote) 899 } 900 901 // setMetaData sets the fs data from a storage.Object 902 func (o *Object) setMetaData(info *api.ResourceInfoResponse) (err error) { 903 o.hasMetaData = true 904 o.size = info.Size 905 o.md5sum = info.Md5 906 o.mimeType = info.MimeType 907 908 var modTimeString string 909 modTimeObj, ok := info.CustomProperties["rclone_modified"] 910 if ok { 911 // read modTime from rclone_modified custom_property of object 912 modTimeString, ok = modTimeObj.(string) 913 } 914 if !ok { 915 // read modTime from Modified property of object as a fallback 916 modTimeString = info.Modified 917 } 918 t, err := time.Parse(time.RFC3339Nano, modTimeString) 919 if err != nil { 920 return errors.Wrapf(err, "failed to parse modtime from %q", modTimeString) 921 } 922 o.modTime = t 923 return nil 924 } 925 926 // readMetaData reads ands sets the new metadata for a storage.Object 927 func (o *Object) readMetaData() (err error) { 928 if o.hasMetaData { 929 return nil 930 } 931 info, err := o.fs.readMetaDataForPath(o.filePath(), &api.ResourceInfoRequestOptions{}) 932 if err != nil { 933 return err 934 } 935 if info.ResourceType != "file" { 936 return fs.ErrorNotAFile 937 } 938 return o.setMetaData(info) 939 } 940 941 // ModTime returns the modification time of the object 942 // 943 // It attempts to read the objects mtime and if that isn't present the 944 // LastModified returned in the http headers 945 func (o *Object) ModTime(ctx context.Context) time.Time { 946 err := o.readMetaData() 947 if err != nil { 948 fs.Logf(o, "Failed to read metadata: %v", err) 949 return time.Now() 950 } 951 return o.modTime 952 } 953 954 // Size returns the size of an object in bytes 955 func (o *Object) Size() int64 { 956 err := o.readMetaData() 957 if err != nil { 958 fs.Logf(o, "Failed to read metadata: %v", err) 959 return 0 960 } 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 if t != hash.MD5 { 967 return "", hash.ErrUnsupported 968 } 969 return o.md5sum, nil 970 } 971 972 // Storable returns whether this object is storable 973 func (o *Object) Storable() bool { 974 return true 975 } 976 977 func (o *Object) setCustomProperty(property string, value string) (err error) { 978 var resp *http.Response 979 opts := rest.Opts{ 980 Method: "PATCH", 981 Path: "/resources", 982 Parameters: url.Values{}, 983 NoResponse: true, 984 } 985 986 opts.Parameters.Set("path", o.filePath()) 987 rcm := map[string]interface{}{ 988 property: value, 989 } 990 cpr := api.CustomPropertyResponse{CustomProperties: rcm} 991 992 err = o.fs.pacer.Call(func() (bool, error) { 993 resp, err = o.fs.srv.CallJSON(&opts, &cpr, nil) 994 return shouldRetry(resp, err) 995 }) 996 return err 997 } 998 999 // SetModTime sets the modification time of the local fs object 1000 // 1001 // Commits the datastore 1002 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 1003 // set custom_property 'rclone_modified' of object to modTime 1004 err := o.setCustomProperty("rclone_modified", modTime.Format(time.RFC3339Nano)) 1005 if err != nil { 1006 return err 1007 } 1008 o.modTime = modTime 1009 return nil 1010 } 1011 1012 // Open an object for read 1013 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 1014 // prepare download 1015 var resp *http.Response 1016 var dl api.AsyncInfo 1017 opts := rest.Opts{ 1018 Method: "GET", 1019 Path: "/resources/download", 1020 Parameters: url.Values{}, 1021 } 1022 1023 opts.Parameters.Set("path", o.filePath()) 1024 1025 err = o.fs.pacer.Call(func() (bool, error) { 1026 resp, err = o.fs.srv.CallJSON(&opts, nil, &dl) 1027 return shouldRetry(resp, err) 1028 }) 1029 1030 if err != nil { 1031 return nil, err 1032 } 1033 1034 // perform the download 1035 opts = rest.Opts{ 1036 RootURL: dl.HRef, 1037 Method: "GET", 1038 Options: options, 1039 } 1040 err = o.fs.pacer.Call(func() (bool, error) { 1041 resp, err = o.fs.srv.Call(&opts) 1042 return shouldRetry(resp, err) 1043 }) 1044 if err != nil { 1045 return nil, err 1046 } 1047 return resp.Body, err 1048 } 1049 1050 func (o *Object) upload(in io.Reader, overwrite bool, mimeType string) (err error) { 1051 // prepare upload 1052 var resp *http.Response 1053 var ur api.AsyncInfo 1054 opts := rest.Opts{ 1055 Method: "GET", 1056 Path: "/resources/upload", 1057 Parameters: url.Values{}, 1058 } 1059 1060 opts.Parameters.Set("path", o.filePath()) 1061 opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite)) 1062 1063 err = o.fs.pacer.Call(func() (bool, error) { 1064 resp, err = o.fs.srv.CallJSON(&opts, nil, &ur) 1065 return shouldRetry(resp, err) 1066 }) 1067 1068 if err != nil { 1069 return err 1070 } 1071 1072 // perform the actual upload 1073 opts = rest.Opts{ 1074 RootURL: ur.HRef, 1075 Method: "PUT", 1076 ContentType: mimeType, 1077 Body: in, 1078 NoResponse: true, 1079 } 1080 1081 err = o.fs.pacer.Call(func() (bool, error) { 1082 resp, err = o.fs.srv.Call(&opts) 1083 return shouldRetry(resp, err) 1084 }) 1085 1086 return err 1087 } 1088 1089 // Update the already existing object 1090 // 1091 // Copy the reader into the object updating modTime and size 1092 // 1093 // The new object may have been created if an error is returned 1094 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { 1095 in1 := readers.NewCountingReader(in) 1096 modTime := src.ModTime(ctx) 1097 remote := o.filePath() 1098 1099 //create full path to file before upload. 1100 err := o.fs.mkParentDirs(remote) 1101 if err != nil { 1102 return err 1103 } 1104 1105 //upload file 1106 err = o.upload(in1, true, fs.MimeType(ctx, src)) 1107 if err != nil { 1108 return err 1109 } 1110 1111 //if file uploaded successfully then return metadata 1112 o.modTime = modTime 1113 o.md5sum = "" // according to unit tests after put the md5 is empty. 1114 o.size = int64(in1.BytesRead()) // better solution o.readMetaData() ? 1115 //and set modTime of uploaded file 1116 err = o.SetModTime(ctx, modTime) 1117 1118 return err 1119 } 1120 1121 // Remove an object 1122 func (o *Object) Remove(ctx context.Context) error { 1123 return o.fs.delete(o.filePath(), false) 1124 } 1125 1126 // MimeType of an Object if known, "" otherwise 1127 func (o *Object) MimeType(ctx context.Context) string { 1128 return o.mimeType 1129 } 1130 1131 // Check the interfaces are satisfied 1132 var ( 1133 _ fs.Fs = (*Fs)(nil) 1134 _ fs.Purger = (*Fs)(nil) 1135 _ fs.Copier = (*Fs)(nil) 1136 _ fs.Mover = (*Fs)(nil) 1137 _ fs.DirMover = (*Fs)(nil) 1138 _ fs.PublicLinker = (*Fs)(nil) 1139 _ fs.CleanUpper = (*Fs)(nil) 1140 _ fs.Abouter = (*Fs)(nil) 1141 _ fs.Object = (*Object)(nil) 1142 _ fs.MimeTyper = (*Object)(nil) 1143 )