github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/opendrive/opendrive.go (about) 1 package opendrive 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "path" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/ncw/rclone/fs" 15 "github.com/ncw/rclone/fs/config/configmap" 16 "github.com/ncw/rclone/fs/config/configstruct" 17 "github.com/ncw/rclone/fs/config/obscure" 18 "github.com/ncw/rclone/fs/fserrors" 19 "github.com/ncw/rclone/fs/fshttp" 20 "github.com/ncw/rclone/fs/hash" 21 "github.com/ncw/rclone/lib/dircache" 22 "github.com/ncw/rclone/lib/pacer" 23 "github.com/ncw/rclone/lib/readers" 24 "github.com/ncw/rclone/lib/rest" 25 "github.com/pkg/errors" 26 ) 27 28 const ( 29 defaultEndpoint = "https://dev.opendrive.com/api/v1" 30 minSleep = 10 * time.Millisecond 31 maxSleep = 5 * time.Minute 32 decayConstant = 1 // bigger for slower decay, exponential 33 ) 34 35 // Register with Fs 36 func init() { 37 fs.Register(&fs.RegInfo{ 38 Name: "opendrive", 39 Description: "OpenDrive", 40 NewFs: NewFs, 41 Options: []fs.Option{{ 42 Name: "username", 43 Help: "Username", 44 Required: true, 45 }, { 46 Name: "password", 47 Help: "Password.", 48 IsPassword: true, 49 Required: true, 50 }}, 51 }) 52 } 53 54 // Options defines the configuration for this backend 55 type Options struct { 56 UserName string `config:"username"` 57 Password string `config:"password"` 58 } 59 60 // Fs represents a remote server 61 type Fs struct { 62 name string // name of this remote 63 root string // the path we are working on 64 opt Options // parsed options 65 features *fs.Features // optional features 66 srv *rest.Client // the connection to the server 67 pacer *fs.Pacer // To pace and retry the API calls 68 session UserSessionInfo // contains the session data 69 dirCache *dircache.DirCache // Map of directory path to directory id 70 } 71 72 // Object describes an object 73 type Object struct { 74 fs *Fs // what this object is part of 75 remote string // The remote path 76 id string // ID of the file 77 modTime time.Time // The modified time of the object if known 78 md5 string // MD5 hash if known 79 size int64 // Size of the object 80 } 81 82 // parsePath parses an incoming 'url' 83 func parsePath(path string) (root string) { 84 root = strings.Trim(path, "/") 85 return 86 } 87 88 // ------------------------------------------------------------ 89 90 // Name of the remote (as passed into NewFs) 91 func (f *Fs) Name() string { 92 return f.name 93 } 94 95 // Root of the remote (as passed into NewFs) 96 func (f *Fs) Root() string { 97 return f.root 98 } 99 100 // String converts this Fs to a string 101 func (f *Fs) String() string { 102 return fmt.Sprintf("OpenDrive root '%s'", f.root) 103 } 104 105 // Features returns the optional features of this Fs 106 func (f *Fs) Features() *fs.Features { 107 return f.features 108 } 109 110 // Hashes returns the supported hash sets. 111 func (f *Fs) Hashes() hash.Set { 112 return hash.Set(hash.MD5) 113 } 114 115 // DirCacheFlush resets the directory cache - used in testing as an 116 // optional interface 117 func (f *Fs) DirCacheFlush() { 118 f.dirCache.ResetRoot() 119 } 120 121 // NewFs constructs an Fs from the path, bucket:path 122 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 123 ctx := context.Background() 124 // Parse config into Options struct 125 opt := new(Options) 126 err := configstruct.Set(m, opt) 127 if err != nil { 128 return nil, err 129 } 130 root = parsePath(root) 131 if opt.UserName == "" { 132 return nil, errors.New("username not found") 133 } 134 opt.Password, err = obscure.Reveal(opt.Password) 135 if err != nil { 136 return nil, errors.New("password could not revealed") 137 } 138 if opt.Password == "" { 139 return nil, errors.New("password not found") 140 } 141 142 f := &Fs{ 143 name: name, 144 root: root, 145 opt: *opt, 146 srv: rest.NewClient(fshttp.NewClient(fs.Config)).SetErrorHandler(errorHandler), 147 pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), 148 } 149 150 f.dirCache = dircache.New(root, "0", f) 151 152 // set the rootURL for the REST client 153 f.srv.SetRoot(defaultEndpoint) 154 155 // get sessionID 156 var resp *http.Response 157 err = f.pacer.Call(func() (bool, error) { 158 account := Account{Username: opt.UserName, Password: opt.Password} 159 160 opts := rest.Opts{ 161 Method: "POST", 162 Path: "/session/login.json", 163 } 164 resp, err = f.srv.CallJSON(&opts, &account, &f.session) 165 return f.shouldRetry(resp, err) 166 }) 167 if err != nil { 168 return nil, errors.Wrap(err, "failed to create session") 169 } 170 fs.Debugf(nil, "Starting OpenDrive session with ID: %s", f.session.SessionID) 171 172 f.features = (&fs.Features{ 173 CaseInsensitive: true, 174 CanHaveEmptyDirectories: true, 175 }).Fill(f) 176 177 // Find the current root 178 err = f.dirCache.FindRoot(ctx, false) 179 if err != nil { 180 // Assume it is a file 181 newRoot, remote := dircache.SplitPath(root) 182 tempF := *f 183 tempF.dirCache = dircache.New(newRoot, "0", &tempF) 184 tempF.root = newRoot 185 186 // Make new Fs which is the parent 187 err = tempF.dirCache.FindRoot(ctx, false) 188 if err != nil { 189 // No root so return old f 190 return f, nil 191 } 192 _, err := tempF.newObjectWithInfo(ctx, remote, nil) 193 if err != nil { 194 if err == fs.ErrorObjectNotFound { 195 // File doesn't exist so return old f 196 return f, nil 197 } 198 return nil, err 199 } 200 // XXX: update the old f here instead of returning tempF, since 201 // `features` were already filled with functions having *f as a receiver. 202 // See https://github.com/ncw/rclone/issues/2182 203 f.dirCache = tempF.dirCache 204 f.root = tempF.root 205 // return an error with an fs which points to the parent 206 return f, fs.ErrorIsFile 207 } 208 return f, nil 209 } 210 211 // rootSlash returns root with a slash on if it is empty, otherwise empty string 212 func (f *Fs) rootSlash() string { 213 if f.root == "" { 214 return f.root 215 } 216 return f.root + "/" 217 } 218 219 // errorHandler parses a non 2xx error response into an error 220 func errorHandler(resp *http.Response) error { 221 errResponse := new(Error) 222 err := rest.DecodeJSON(resp, &errResponse) 223 if err != nil { 224 fs.Debugf(nil, "Couldn't decode error response: %v", err) 225 } 226 if errResponse.Info.Code == 0 { 227 errResponse.Info.Code = resp.StatusCode 228 } 229 if errResponse.Info.Message == "" { 230 errResponse.Info.Message = "Unknown " + resp.Status 231 } 232 return errResponse 233 } 234 235 // Mkdir creates the folder if it doesn't exist 236 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 237 // fs.Debugf(nil, "Mkdir(\"%s\")", dir) 238 err := f.dirCache.FindRoot(ctx, true) 239 if err != nil { 240 return err 241 } 242 if dir != "" { 243 _, err = f.dirCache.FindDir(ctx, dir, true) 244 } 245 return err 246 } 247 248 // deleteObject removes an object by ID 249 func (f *Fs) deleteObject(id string) error { 250 return f.pacer.Call(func() (bool, error) { 251 removeDirData := removeFolder{SessionID: f.session.SessionID, FolderID: id} 252 opts := rest.Opts{ 253 Method: "POST", 254 NoResponse: true, 255 Path: "/folder/remove.json", 256 } 257 resp, err := f.srv.CallJSON(&opts, &removeDirData, nil) 258 return f.shouldRetry(resp, err) 259 }) 260 } 261 262 // purgeCheck remotes the root directory, if check is set then it 263 // refuses to do so if it has anything in 264 func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error { 265 root := path.Join(f.root, dir) 266 if root == "" { 267 return errors.New("can't purge root directory") 268 } 269 dc := f.dirCache 270 err := dc.FindRoot(ctx, false) 271 if err != nil { 272 return err 273 } 274 rootID, err := dc.FindDir(ctx, dir, false) 275 if err != nil { 276 return err 277 } 278 item, err := f.readMetaDataForFolderID(rootID) 279 if err != nil { 280 return err 281 } 282 if check && len(item.Files) != 0 { 283 return errors.New("folder not empty") 284 } 285 err = f.deleteObject(rootID) 286 if err != nil { 287 return err 288 } 289 f.dirCache.FlushDir(dir) 290 return nil 291 } 292 293 // Rmdir deletes the root folder 294 // 295 // Returns an error if it isn't empty 296 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 297 // fs.Debugf(nil, "Rmdir(\"%s\")", path.Join(f.root, dir)) 298 return f.purgeCheck(ctx, dir, true) 299 } 300 301 // Precision of the remote 302 func (f *Fs) Precision() time.Duration { 303 return time.Second 304 } 305 306 // Copy src to this remote using server side copy operations. 307 // 308 // This is stored with the remote path given 309 // 310 // It returns the destination Object and a possible error 311 // 312 // Will only be called if src.Fs().Name() == f.Name() 313 // 314 // If it isn't possible then return fs.ErrorCantCopy 315 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 316 // fs.Debugf(nil, "Copy(%v)", remote) 317 srcObj, ok := src.(*Object) 318 if !ok { 319 fs.Debugf(src, "Can't copy - not same remote type") 320 return nil, fs.ErrorCantCopy 321 } 322 err := srcObj.readMetaData(ctx) 323 if err != nil { 324 return nil, err 325 } 326 327 srcPath := srcObj.fs.rootSlash() + srcObj.remote 328 dstPath := f.rootSlash() + remote 329 if strings.ToLower(srcPath) == strings.ToLower(dstPath) { 330 return nil, errors.Errorf("Can't copy %q -> %q as are same name when lowercase", srcPath, dstPath) 331 } 332 333 // Create temporary object 334 dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 335 if err != nil { 336 return nil, err 337 } 338 // fs.Debugf(nil, "...%#v\n...%#v", remote, directoryID) 339 340 // Copy the object 341 var resp *http.Response 342 response := moveCopyFileResponse{} 343 err = f.pacer.Call(func() (bool, error) { 344 copyFileData := moveCopyFile{ 345 SessionID: f.session.SessionID, 346 SrcFileID: srcObj.id, 347 DstFolderID: directoryID, 348 Move: "false", 349 OverwriteIfExists: "true", 350 NewFileName: leaf, 351 } 352 opts := rest.Opts{ 353 Method: "POST", 354 Path: "/file/move_copy.json", 355 } 356 resp, err = f.srv.CallJSON(&opts, ©FileData, &response) 357 return f.shouldRetry(resp, err) 358 }) 359 if err != nil { 360 return nil, err 361 } 362 363 size, _ := strconv.ParseInt(response.Size, 10, 64) 364 dstObj.id = response.FileID 365 dstObj.size = size 366 367 return dstObj, nil 368 } 369 370 // Move src to this remote using server side move operations. 371 // 372 // This is stored with the remote path given 373 // 374 // It returns the destination Object and a possible error 375 // 376 // Will only be called if src.Fs().Name() == f.Name() 377 // 378 // If it isn't possible then return fs.ErrorCantMove 379 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 380 // fs.Debugf(nil, "Move(%v)", remote) 381 srcObj, ok := src.(*Object) 382 if !ok { 383 fs.Debugf(src, "Can't move - not same remote type") 384 return nil, fs.ErrorCantCopy 385 } 386 err := srcObj.readMetaData(ctx) 387 if err != nil { 388 return nil, err 389 } 390 391 // Create temporary object 392 dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size) 393 if err != nil { 394 return nil, err 395 } 396 397 // Copy the object 398 var resp *http.Response 399 response := moveCopyFileResponse{} 400 err = f.pacer.Call(func() (bool, error) { 401 copyFileData := moveCopyFile{ 402 SessionID: f.session.SessionID, 403 SrcFileID: srcObj.id, 404 DstFolderID: directoryID, 405 Move: "true", 406 OverwriteIfExists: "true", 407 NewFileName: leaf, 408 } 409 opts := rest.Opts{ 410 Method: "POST", 411 Path: "/file/move_copy.json", 412 } 413 resp, err = f.srv.CallJSON(&opts, ©FileData, &response) 414 return f.shouldRetry(resp, err) 415 }) 416 if err != nil { 417 return nil, err 418 } 419 420 size, _ := strconv.ParseInt(response.Size, 10, 64) 421 dstObj.id = response.FileID 422 dstObj.size = size 423 424 return dstObj, nil 425 } 426 427 // DirMove moves src, srcRemote to this remote at dstRemote 428 // using server side move operations. 429 // 430 // Will only be called if src.Fs().Name() == f.Name() 431 // 432 // If it isn't possible then return fs.ErrorCantDirMove 433 // 434 // If destination exists then return fs.ErrorDirExists 435 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) (err error) { 436 srcFs, ok := src.(*Fs) 437 if !ok { 438 fs.Debugf(srcFs, "Can't move directory - not same remote type") 439 return fs.ErrorCantDirMove 440 } 441 srcPath := path.Join(srcFs.root, srcRemote) 442 dstPath := path.Join(f.root, dstRemote) 443 444 // Refuse to move to or from the root 445 if srcPath == "" || dstPath == "" { 446 fs.Debugf(src, "DirMove error: Can't move root") 447 return errors.New("can't move root directory") 448 } 449 450 // find the root src directory 451 err = srcFs.dirCache.FindRoot(ctx, false) 452 if err != nil { 453 return err 454 } 455 456 // find the root dst directory 457 if dstRemote != "" { 458 err = f.dirCache.FindRoot(ctx, true) 459 if err != nil { 460 return err 461 } 462 } else { 463 if f.dirCache.FoundRoot() { 464 return fs.ErrorDirExists 465 } 466 } 467 468 // Find ID of dst parent, creating subdirs if necessary 469 var leaf, directoryID string 470 findPath := dstRemote 471 if dstRemote == "" { 472 findPath = f.root 473 } 474 leaf, directoryID, err = f.dirCache.FindPath(ctx, findPath, true) 475 if err != nil { 476 return err 477 } 478 479 // Check destination does not exist 480 if dstRemote != "" { 481 _, err = f.dirCache.FindDir(ctx, dstRemote, false) 482 if err == fs.ErrorDirNotFound { 483 // OK 484 } else if err != nil { 485 return err 486 } else { 487 return fs.ErrorDirExists 488 } 489 } 490 491 // Find ID of src 492 srcID, err := srcFs.dirCache.FindDir(ctx, srcRemote, false) 493 if err != nil { 494 return err 495 } 496 497 // Do the move 498 var resp *http.Response 499 response := moveCopyFolderResponse{} 500 err = f.pacer.Call(func() (bool, error) { 501 moveFolderData := moveCopyFolder{ 502 SessionID: f.session.SessionID, 503 FolderID: srcID, 504 DstFolderID: directoryID, 505 Move: "true", 506 NewFolderName: leaf, 507 } 508 opts := rest.Opts{ 509 Method: "POST", 510 Path: "/folder/move_copy.json", 511 } 512 resp, err = f.srv.CallJSON(&opts, &moveFolderData, &response) 513 return f.shouldRetry(resp, err) 514 }) 515 if err != nil { 516 fs.Debugf(src, "DirMove error %v", err) 517 return err 518 } 519 520 srcFs.dirCache.FlushDir(srcRemote) 521 return nil 522 } 523 524 // Purge deletes all the files and the container 525 // 526 // Optional interface: Only implement this if you have a way of 527 // deleting all the files quicker than just running Remove() on the 528 // result of List() 529 func (f *Fs) Purge(ctx context.Context) error { 530 return f.purgeCheck(ctx, "", false) 531 } 532 533 // Return an Object from a path 534 // 535 // If it can't be found it returns the error fs.ErrorObjectNotFound. 536 func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, file *File) (fs.Object, error) { 537 // fs.Debugf(nil, "newObjectWithInfo(%s, %v)", remote, file) 538 539 var o *Object 540 if nil != file { 541 o = &Object{ 542 fs: f, 543 remote: remote, 544 id: file.FileID, 545 modTime: time.Unix(file.DateModified, 0), 546 size: file.Size, 547 md5: file.FileHash, 548 } 549 } else { 550 o = &Object{ 551 fs: f, 552 remote: remote, 553 } 554 555 err := o.readMetaData(ctx) 556 if err != nil { 557 return nil, err 558 } 559 } 560 return o, nil 561 } 562 563 // NewObject finds the Object at remote. If it can't be found 564 // it returns the error fs.ErrorObjectNotFound. 565 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 566 // fs.Debugf(nil, "NewObject(\"%s\")", remote) 567 return f.newObjectWithInfo(ctx, remote, nil) 568 } 569 570 // Creates from the parameters passed in a half finished Object which 571 // must have setMetaData called on it 572 // 573 // Returns the object, leaf, directoryID and error 574 // 575 // Used to create new objects 576 func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) { 577 // Create the directory for the object if it doesn't exist 578 leaf, directoryID, err = f.dirCache.FindRootAndPath(ctx, remote, true) 579 if err != nil { 580 return nil, leaf, directoryID, err 581 } 582 // fs.Debugf(nil, "\n...leaf %#v\n...id %#v", leaf, directoryID) 583 // Temporary Object under construction 584 o = &Object{ 585 fs: f, 586 remote: remote, 587 } 588 return o, leaf, directoryID, nil 589 } 590 591 // readMetaDataForPath reads the metadata from the path 592 func (f *Fs) readMetaDataForFolderID(id string) (info *FolderList, err error) { 593 var resp *http.Response 594 opts := rest.Opts{ 595 Method: "GET", 596 Path: "/folder/list.json/" + f.session.SessionID + "/" + id, 597 } 598 err = f.pacer.Call(func() (bool, error) { 599 resp, err = f.srv.CallJSON(&opts, nil, &info) 600 return f.shouldRetry(resp, err) 601 }) 602 if err != nil { 603 return nil, err 604 } 605 if resp != nil { 606 } 607 608 return info, err 609 } 610 611 // Put the object into the bucket 612 // 613 // Copy the reader in to the new object which is returned 614 // 615 // The new object may have been created if an error is returned 616 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 617 remote := src.Remote() 618 size := src.Size() 619 modTime := src.ModTime(ctx) 620 621 // fs.Debugf(nil, "Put(%s)", remote) 622 623 o, leaf, directoryID, err := f.createObject(ctx, remote, modTime, size) 624 if err != nil { 625 return nil, err 626 } 627 628 if "" == o.id { 629 // Attempt to read ID, ignore error 630 // FIXME is this correct? 631 _ = o.readMetaData(ctx) 632 } 633 634 if "" == o.id { 635 // We need to create a ID for this file 636 var resp *http.Response 637 response := createFileResponse{} 638 err := o.fs.pacer.Call(func() (bool, error) { 639 createFileData := createFile{SessionID: o.fs.session.SessionID, FolderID: directoryID, Name: replaceReservedChars(leaf)} 640 opts := rest.Opts{ 641 Method: "POST", 642 Path: "/upload/create_file.json", 643 } 644 resp, err = o.fs.srv.CallJSON(&opts, &createFileData, &response) 645 return o.fs.shouldRetry(resp, err) 646 }) 647 if err != nil { 648 return nil, errors.Wrap(err, "failed to create file") 649 } 650 651 o.id = response.FileID 652 } 653 654 return o, o.Update(ctx, in, src, options...) 655 } 656 657 // retryErrorCodes is a slice of error codes that we will retry 658 var retryErrorCodes = []int{ 659 400, // Bad request (seen in "Next token is expired") 660 401, // Unauthorized (seen in "Token has expired") 661 408, // Request Timeout 662 423, // Locked - get this on folders sometimes 663 429, // Rate exceeded. 664 500, // Get occasional 500 Internal Server Error 665 502, // Bad Gateway when doing big listings 666 503, // Service Unavailable 667 504, // Gateway Time-out 668 } 669 670 // shouldRetry returns a boolean as to whether this resp and err 671 // deserve to be retried. It returns the err as a convenience 672 func (f *Fs) shouldRetry(resp *http.Response, err error) (bool, error) { 673 return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 674 } 675 676 // DirCacher methods 677 678 // CreateDir makes a directory with pathID as parent and name leaf 679 func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { 680 // fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, replaceReservedChars(leaf)) 681 var resp *http.Response 682 response := createFolderResponse{} 683 err = f.pacer.Call(func() (bool, error) { 684 createDirData := createFolder{ 685 SessionID: f.session.SessionID, 686 FolderName: replaceReservedChars(leaf), 687 FolderSubParent: pathID, 688 FolderIsPublic: 0, 689 FolderPublicUpl: 0, 690 FolderPublicDisplay: 0, 691 FolderPublicDnl: 0, 692 } 693 opts := rest.Opts{ 694 Method: "POST", 695 Path: "/folder.json", 696 } 697 resp, err = f.srv.CallJSON(&opts, &createDirData, &response) 698 return f.shouldRetry(resp, err) 699 }) 700 if err != nil { 701 return "", err 702 } 703 704 return response.FolderID, nil 705 } 706 707 // FindLeaf finds a directory of name leaf in the folder with ID pathID 708 func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) { 709 // fs.Debugf(nil, "FindLeaf(\"%s\", \"%s\")", pathID, leaf) 710 711 if pathID == "0" && leaf == "" { 712 // fs.Debugf(nil, "Found OpenDrive root") 713 // that's the root directory 714 return pathID, true, nil 715 } 716 717 // get the folderIDs 718 var resp *http.Response 719 folderList := FolderList{} 720 err = f.pacer.Call(func() (bool, error) { 721 opts := rest.Opts{ 722 Method: "GET", 723 Path: "/folder/list.json/" + f.session.SessionID + "/" + pathID, 724 } 725 resp, err = f.srv.CallJSON(&opts, nil, &folderList) 726 return f.shouldRetry(resp, err) 727 }) 728 if err != nil { 729 return "", false, errors.Wrap(err, "failed to get folder list") 730 } 731 732 for _, folder := range folderList.Folders { 733 folder.Name = restoreReservedChars(folder.Name) 734 // fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID) 735 736 if leaf == folder.Name { 737 // found 738 return folder.FolderID, true, nil 739 } 740 } 741 742 return "", false, nil 743 } 744 745 // List the objects and directories in dir into entries. The 746 // entries can be returned in any order but should be for a 747 // complete directory. 748 // 749 // dir should be "" to list the root, and should not have 750 // trailing slashes. 751 // 752 // This should return ErrDirNotFound if the directory isn't 753 // found. 754 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 755 // fs.Debugf(nil, "List(%v)", dir) 756 err = f.dirCache.FindRoot(ctx, false) 757 if err != nil { 758 return nil, err 759 } 760 directoryID, err := f.dirCache.FindDir(ctx, dir, false) 761 if err != nil { 762 return nil, err 763 } 764 765 var resp *http.Response 766 opts := rest.Opts{ 767 Method: "GET", 768 Path: "/folder/list.json/" + f.session.SessionID + "/" + directoryID, 769 } 770 folderList := FolderList{} 771 err = f.pacer.Call(func() (bool, error) { 772 resp, err = f.srv.CallJSON(&opts, nil, &folderList) 773 return f.shouldRetry(resp, err) 774 }) 775 if err != nil { 776 return nil, errors.Wrap(err, "failed to get folder list") 777 } 778 779 for _, folder := range folderList.Folders { 780 folder.Name = restoreReservedChars(folder.Name) 781 // fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID) 782 remote := path.Join(dir, folder.Name) 783 // cache the directory ID for later lookups 784 f.dirCache.Put(remote, folder.FolderID) 785 d := fs.NewDir(remote, time.Unix(folder.DateModified, 0)).SetID(folder.FolderID) 786 d.SetItems(int64(folder.ChildFolders)) 787 entries = append(entries, d) 788 } 789 790 for _, file := range folderList.Files { 791 file.Name = restoreReservedChars(file.Name) 792 // fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID) 793 remote := path.Join(dir, file.Name) 794 o, err := f.newObjectWithInfo(ctx, remote, &file) 795 if err != nil { 796 return nil, err 797 } 798 entries = append(entries, o) 799 } 800 801 return entries, nil 802 } 803 804 // ------------------------------------------------------------ 805 806 // Fs returns the parent Fs 807 func (o *Object) Fs() fs.Info { 808 return o.fs 809 } 810 811 // Return a string version 812 func (o *Object) String() string { 813 if o == nil { 814 return "<nil>" 815 } 816 return o.remote 817 } 818 819 // Remote returns the remote path 820 func (o *Object) Remote() string { 821 return o.remote 822 } 823 824 // Hash returns the Md5sum of an object returning a lowercase hex string 825 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 826 if t != hash.MD5 { 827 return "", hash.ErrUnsupported 828 } 829 return o.md5, nil 830 } 831 832 // Size returns the size of an object in bytes 833 func (o *Object) Size() int64 { 834 return o.size // Object is likely PENDING 835 } 836 837 // ModTime returns the modification time of the object 838 // 839 // 840 // It attempts to read the objects mtime and if that isn't present the 841 // LastModified returned in the http headers 842 func (o *Object) ModTime(ctx context.Context) time.Time { 843 return o.modTime 844 } 845 846 // SetModTime sets the modification time of the local fs object 847 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 848 // fs.Debugf(nil, "SetModTime(%v)", modTime.String()) 849 opts := rest.Opts{ 850 Method: "PUT", 851 NoResponse: true, 852 Path: "/file/filesettings.json", 853 } 854 update := modTimeFile{SessionID: o.fs.session.SessionID, FileID: o.id, FileModificationTime: strconv.FormatInt(modTime.Unix(), 10)} 855 err := o.fs.pacer.Call(func() (bool, error) { 856 resp, err := o.fs.srv.CallJSON(&opts, &update, nil) 857 return o.fs.shouldRetry(resp, err) 858 }) 859 860 o.modTime = modTime 861 862 return err 863 } 864 865 // Open an object for read 866 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) { 867 // fs.Debugf(nil, "Open(\"%v\")", o.remote) 868 fs.FixRangeOption(options, o.size) 869 opts := rest.Opts{ 870 Method: "GET", 871 Path: "/download/file.json/" + o.id + "?session_id=" + o.fs.session.SessionID, 872 Options: options, 873 } 874 var resp *http.Response 875 err = o.fs.pacer.Call(func() (bool, error) { 876 resp, err = o.fs.srv.Call(&opts) 877 return o.fs.shouldRetry(resp, err) 878 }) 879 if err != nil { 880 return nil, errors.Wrap(err, "failed to open file)") 881 } 882 883 return resp.Body, nil 884 } 885 886 // Remove an object 887 func (o *Object) Remove(ctx context.Context) error { 888 // fs.Debugf(nil, "Remove(\"%s\")", o.id) 889 return o.fs.pacer.Call(func() (bool, error) { 890 opts := rest.Opts{ 891 Method: "DELETE", 892 NoResponse: true, 893 Path: "/file.json/" + o.fs.session.SessionID + "/" + o.id, 894 } 895 resp, err := o.fs.srv.Call(&opts) 896 return o.fs.shouldRetry(resp, err) 897 }) 898 } 899 900 // Storable returns a boolean showing whether this object storable 901 func (o *Object) Storable() bool { 902 return true 903 } 904 905 // Update the object with the contents of the io.Reader, modTime and size 906 // 907 // The new object may have been created if an error is returned 908 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { 909 size := src.Size() 910 modTime := src.ModTime(ctx) 911 // fs.Debugf(nil, "Update(\"%s\", \"%s\")", o.id, o.remote) 912 913 // Open file for upload 914 var resp *http.Response 915 openResponse := openUploadResponse{} 916 err := o.fs.pacer.Call(func() (bool, error) { 917 openUploadData := openUpload{SessionID: o.fs.session.SessionID, FileID: o.id, Size: size} 918 // fs.Debugf(nil, "PreOpen: %#v", openUploadData) 919 opts := rest.Opts{ 920 Method: "POST", 921 Path: "/upload/open_file_upload.json", 922 } 923 resp, err := o.fs.srv.CallJSON(&opts, &openUploadData, &openResponse) 924 return o.fs.shouldRetry(resp, err) 925 }) 926 if err != nil { 927 return errors.Wrap(err, "failed to create file") 928 } 929 // resp.Body.Close() 930 // fs.Debugf(nil, "PostOpen: %#v", openResponse) 931 932 // 10 MB chunks size 933 chunkSize := int64(1024 * 1024 * 10) 934 buf := make([]byte, int(chunkSize)) 935 chunkOffset := int64(0) 936 remainingBytes := size 937 chunkCounter := 0 938 939 for remainingBytes > 0 { 940 currentChunkSize := chunkSize 941 if currentChunkSize > remainingBytes { 942 currentChunkSize = remainingBytes 943 } 944 remainingBytes -= currentChunkSize 945 fs.Debugf(o, "Uploading chunk %d, size=%d, remain=%d", chunkCounter, currentChunkSize, remainingBytes) 946 947 chunk := readers.NewRepeatableLimitReaderBuffer(in, buf, currentChunkSize) 948 var reply uploadFileChunkReply 949 err = o.fs.pacer.Call(func() (bool, error) { 950 // seek to the start in case this is a retry 951 if _, err = chunk.Seek(0, io.SeekStart); err != nil { 952 return false, err 953 } 954 opts := rest.Opts{ 955 Method: "POST", 956 Path: "/upload/upload_file_chunk.json", 957 Body: chunk, 958 MultipartParams: url.Values{ 959 "session_id": []string{o.fs.session.SessionID}, 960 "file_id": []string{o.id}, 961 "temp_location": []string{openResponse.TempLocation}, 962 "chunk_offset": []string{strconv.FormatInt(chunkOffset, 10)}, 963 "chunk_size": []string{strconv.FormatInt(currentChunkSize, 10)}, 964 }, 965 MultipartContentName: "file_data", // ..name of the parameter which is the attached file 966 MultipartFileName: o.remote, // ..name of the file for the attached file 967 968 } 969 resp, err = o.fs.srv.CallJSON(&opts, nil, &reply) 970 return o.fs.shouldRetry(resp, err) 971 }) 972 if err != nil { 973 return errors.Wrap(err, "failed to create file") 974 } 975 if reply.TotalWritten != currentChunkSize { 976 return errors.Errorf("failed to create file: incomplete write of %d/%d bytes", reply.TotalWritten, currentChunkSize) 977 } 978 979 chunkCounter++ 980 chunkOffset += currentChunkSize 981 } 982 983 // Close file for upload 984 closeResponse := closeUploadResponse{} 985 err = o.fs.pacer.Call(func() (bool, error) { 986 closeUploadData := closeUpload{SessionID: o.fs.session.SessionID, FileID: o.id, Size: size, TempLocation: openResponse.TempLocation} 987 // fs.Debugf(nil, "PreClose: %#v", closeUploadData) 988 opts := rest.Opts{ 989 Method: "POST", 990 Path: "/upload/close_file_upload.json", 991 } 992 resp, err = o.fs.srv.CallJSON(&opts, &closeUploadData, &closeResponse) 993 return o.fs.shouldRetry(resp, err) 994 }) 995 if err != nil { 996 return errors.Wrap(err, "failed to create file") 997 } 998 // fs.Debugf(nil, "PostClose: %#v", closeResponse) 999 1000 o.id = closeResponse.FileID 1001 o.size = closeResponse.Size 1002 1003 // Set the mod time now 1004 err = o.SetModTime(ctx, modTime) 1005 if err != nil { 1006 return err 1007 } 1008 1009 // Set permissions 1010 err = o.fs.pacer.Call(func() (bool, error) { 1011 update := permissions{SessionID: o.fs.session.SessionID, FileID: o.id, FileIsPublic: 0} 1012 // fs.Debugf(nil, "Permissions : %#v", update) 1013 opts := rest.Opts{ 1014 Method: "POST", 1015 NoResponse: true, 1016 Path: "/file/access.json", 1017 } 1018 resp, err = o.fs.srv.CallJSON(&opts, &update, nil) 1019 return o.fs.shouldRetry(resp, err) 1020 }) 1021 if err != nil { 1022 return err 1023 } 1024 1025 return o.readMetaData(ctx) 1026 } 1027 1028 func (o *Object) readMetaData(ctx context.Context) (err error) { 1029 leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, o.remote, false) 1030 if err != nil { 1031 if err == fs.ErrorDirNotFound { 1032 return fs.ErrorObjectNotFound 1033 } 1034 return err 1035 } 1036 var resp *http.Response 1037 folderList := FolderList{} 1038 err = o.fs.pacer.Call(func() (bool, error) { 1039 opts := rest.Opts{ 1040 Method: "GET", 1041 Path: "/folder/itembyname.json/" + o.fs.session.SessionID + "/" + directoryID + "?name=" + url.QueryEscape(replaceReservedChars(leaf)), 1042 } 1043 resp, err = o.fs.srv.CallJSON(&opts, nil, &folderList) 1044 return o.fs.shouldRetry(resp, err) 1045 }) 1046 if err != nil { 1047 return errors.Wrap(err, "failed to get folder list") 1048 } 1049 1050 if len(folderList.Files) == 0 { 1051 return fs.ErrorObjectNotFound 1052 } 1053 1054 leafFile := folderList.Files[0] 1055 o.id = leafFile.FileID 1056 o.modTime = time.Unix(leafFile.DateModified, 0) 1057 o.md5 = leafFile.FileHash 1058 o.size = leafFile.Size 1059 1060 return nil 1061 } 1062 1063 // ID returns the ID of the Object if known, or "" if not 1064 func (o *Object) ID() string { 1065 return o.id 1066 } 1067 1068 // Check the interfaces are satisfied 1069 var ( 1070 _ fs.Fs = (*Fs)(nil) 1071 _ fs.Purger = (*Fs)(nil) 1072 _ fs.Copier = (*Fs)(nil) 1073 _ fs.Mover = (*Fs)(nil) 1074 _ fs.DirMover = (*Fs)(nil) 1075 _ fs.DirCacheFlusher = (*Fs)(nil) 1076 _ fs.Object = (*Object)(nil) 1077 _ fs.IDer = (*Object)(nil) 1078 )