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