github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/seafile/seafile.go (about) 1 package seafile 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "path" 10 "strconv" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/coreos/go-semver/semver" 16 "github.com/pkg/errors" 17 "github.com/rclone/rclone/backend/seafile/api" 18 "github.com/rclone/rclone/fs" 19 "github.com/rclone/rclone/fs/config" 20 "github.com/rclone/rclone/fs/config/configmap" 21 "github.com/rclone/rclone/fs/config/configstruct" 22 "github.com/rclone/rclone/fs/config/obscure" 23 "github.com/rclone/rclone/fs/fserrors" 24 "github.com/rclone/rclone/fs/fshttp" 25 "github.com/rclone/rclone/fs/hash" 26 "github.com/rclone/rclone/lib/bucket" 27 "github.com/rclone/rclone/lib/cache" 28 "github.com/rclone/rclone/lib/encoder" 29 "github.com/rclone/rclone/lib/pacer" 30 "github.com/rclone/rclone/lib/random" 31 "github.com/rclone/rclone/lib/rest" 32 ) 33 34 const ( 35 librariesCacheKey = "all" 36 retryAfterHeader = "Retry-After" 37 configURL = "url" 38 configUser = "user" 39 configPassword = "pass" 40 config2FA = "2fa" 41 configLibrary = "library" 42 configLibraryKey = "library_key" 43 configCreateLibrary = "create_library" 44 configAuthToken = "auth_token" 45 ) 46 47 // This is global to all instances of fs 48 // (copying from a seafile remote to another remote would create 2 fs) 49 var ( 50 rangeDownloadNotice sync.Once // Display the notice only once 51 createLibraryMutex sync.Mutex // Mutex to protect library creation 52 ) 53 54 // Register with Fs 55 func init() { 56 fs.Register(&fs.RegInfo{ 57 Name: "seafile", 58 Description: "seafile", 59 NewFs: NewFs, 60 Config: Config, 61 Options: []fs.Option{{ 62 Name: configURL, 63 Help: "URL of seafile host to connect to", 64 Required: true, 65 Examples: []fs.OptionExample{{ 66 Value: "https://cloud.seafile.com/", 67 Help: "Connect to cloud.seafile.com", 68 }}, 69 }, { 70 Name: configUser, 71 Help: "User name (usually email address)", 72 Required: true, 73 }, { 74 // Password is not required, it will be left blank for 2FA 75 Name: configPassword, 76 Help: "Password", 77 IsPassword: true, 78 }, { 79 Name: config2FA, 80 Help: "Two-factor authentication ('true' if the account has 2FA enabled)", 81 Default: false, 82 }, { 83 Name: configLibrary, 84 Help: "Name of the library. Leave blank to access all non-encrypted libraries.", 85 }, { 86 Name: configLibraryKey, 87 Help: "Library password (for encrypted libraries only). Leave blank if you pass it through the command line.", 88 IsPassword: true, 89 }, { 90 Name: configCreateLibrary, 91 Help: "Should rclone create a library if it doesn't exist", 92 Advanced: true, 93 Default: false, 94 }, { 95 // Keep the authentication token after entering the 2FA code 96 Name: configAuthToken, 97 Help: "Authentication token", 98 Hide: fs.OptionHideBoth, 99 }, { 100 Name: config.ConfigEncoding, 101 Help: config.ConfigEncodingHelp, 102 Advanced: true, 103 Default: (encoder.EncodeZero | 104 encoder.EncodeCtl | 105 encoder.EncodeSlash | 106 encoder.EncodeBackSlash | 107 encoder.EncodeDoubleQuote | 108 encoder.EncodeInvalidUtf8), 109 }}, 110 }) 111 } 112 113 // Options defines the configuration for this backend 114 type Options struct { 115 URL string `config:"url"` 116 User string `config:"user"` 117 Password string `config:"pass"` 118 Is2FA bool `config:"2fa"` 119 AuthToken string `config:"auth_token"` 120 LibraryName string `config:"library"` 121 LibraryKey string `config:"library_key"` 122 CreateLibrary bool `config:"create_library"` 123 Enc encoder.MultiEncoder `config:"encoding"` 124 } 125 126 // Fs represents a remote seafile 127 type Fs struct { 128 name string // name of this remote 129 root string // the path we are working on 130 libraryName string // current library 131 encrypted bool // Is this an encrypted library 132 rootDirectory string // directory part of root (if any) 133 opt Options // parsed options 134 libraries *cache.Cache // Keep a cache of libraries 135 librariesMutex sync.Mutex // Mutex to protect getLibraryID 136 features *fs.Features // optional features 137 endpoint *url.URL // URL of the host 138 endpointURL string // endpoint as a string 139 srv *rest.Client // the connection to the one drive server 140 pacer *fs.Pacer // pacer for API calls 141 authMu sync.Mutex // Mutex to protect library decryption 142 createDirMutex sync.Mutex // Protect creation of directories 143 useOldDirectoryAPI bool // Use the old API v2 if seafile < 7 144 moveDirNotAvailable bool // Version < 7.0 don't have an API to move a directory 145 } 146 147 // ------------------------------------------------------------ 148 149 // NewFs constructs an Fs from the path, container:path 150 func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) { 151 // Parse config into Options struct 152 opt := new(Options) 153 err := configstruct.Set(m, opt) 154 if err != nil { 155 return nil, err 156 } 157 root = strings.Trim(root, "/") 158 isLibraryRooted := opt.LibraryName != "" 159 var libraryName, rootDirectory string 160 if isLibraryRooted { 161 libraryName = opt.LibraryName 162 rootDirectory = root 163 } else { 164 libraryName, rootDirectory = bucket.Split(root) 165 } 166 167 if !strings.HasSuffix(opt.URL, "/") { 168 opt.URL += "/" 169 } 170 if opt.Password != "" { 171 var err error 172 opt.Password, err = obscure.Reveal(opt.Password) 173 if err != nil { 174 return nil, errors.Wrap(err, "couldn't decrypt user password") 175 } 176 } 177 if opt.LibraryKey != "" { 178 var err error 179 opt.LibraryKey, err = obscure.Reveal(opt.LibraryKey) 180 if err != nil { 181 return nil, errors.Wrap(err, "couldn't decrypt library password") 182 } 183 } 184 185 // Parse the endpoint 186 u, err := url.Parse(opt.URL) 187 if err != nil { 188 return nil, err 189 } 190 191 f := &Fs{ 192 name: name, 193 root: root, 194 libraryName: libraryName, 195 rootDirectory: rootDirectory, 196 libraries: cache.New(), 197 opt: *opt, 198 endpoint: u, 199 endpointURL: u.String(), 200 srv: rest.NewClient(fshttp.NewClient(fs.Config)).SetRoot(u.String()), 201 pacer: getPacer(opt.URL), 202 } 203 f.features = (&fs.Features{ 204 CanHaveEmptyDirectories: true, 205 BucketBased: opt.LibraryName == "", 206 }).Fill(f) 207 208 ctx := context.Background() 209 serverInfo, err := f.getServerInfo(ctx) 210 if err != nil { 211 return nil, err 212 } 213 fs.Debugf(nil, "Seafile server version %s", serverInfo.Version) 214 215 // We don't support lower than seafile v6.0 (version 6.0 is already more than 3 years old) 216 serverVersion := semver.New(serverInfo.Version) 217 if serverVersion.Major < 6 { 218 return nil, errors.New("unsupported Seafile server (version < 6.0)") 219 } 220 if serverVersion.Major < 7 { 221 // Seafile 6 does not support recursive listing 222 f.useOldDirectoryAPI = true 223 f.features.ListR = nil 224 // It also does no support moving directories 225 f.moveDirNotAvailable = true 226 } 227 228 // Take the authentication token from the configuration first 229 token := f.opt.AuthToken 230 if token == "" { 231 // If not available, send the user/password instead 232 token, err = f.authorizeAccount(ctx) 233 if err != nil { 234 return nil, err 235 } 236 } 237 f.setAuthorizationToken(token) 238 239 if f.libraryName != "" { 240 // Check if the library exists 241 exists, err := f.libraryExists(ctx, f.libraryName) 242 if err != nil { 243 return f, err 244 } 245 if !exists { 246 if f.opt.CreateLibrary { 247 err := f.mkLibrary(ctx, f.libraryName, "") 248 if err != nil { 249 return f, err 250 } 251 } else { 252 return f, fmt.Errorf("library '%s' was not found, and the option to create it is not activated (advanced option)", f.libraryName) 253 } 254 } 255 libraryID, err := f.getLibraryID(ctx, f.libraryName) 256 if err != nil { 257 return f, err 258 } 259 f.encrypted, err = f.isEncrypted(ctx, libraryID) 260 if err != nil { 261 return f, err 262 } 263 if f.encrypted { 264 // If we're inside an encrypted library, let's decrypt it now 265 err = f.authorizeLibrary(ctx, libraryID) 266 if err != nil { 267 return f, err 268 } 269 // And remove the public link feature 270 f.features.PublicLink = nil 271 } 272 } else { 273 // Deactivate the cleaner feature since there's no library selected 274 f.features.CleanUp = nil 275 } 276 277 if f.rootDirectory != "" { 278 // Check to see if the root is an existing file 279 remote := path.Base(rootDirectory) 280 f.rootDirectory = path.Dir(rootDirectory) 281 if f.rootDirectory == "." { 282 f.rootDirectory = "" 283 } 284 _, err := f.NewObject(ctx, remote) 285 if err != nil { 286 if errors.Cause(err) == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile { 287 // File doesn't exist so return the original f 288 f.rootDirectory = rootDirectory 289 return f, nil 290 } 291 return f, err 292 } 293 // return an error with an fs which points to the parent 294 return f, fs.ErrorIsFile 295 } 296 return f, nil 297 } 298 299 // Config callback for 2FA 300 func Config(name string, m configmap.Mapper) { 301 serverURL, ok := m.Get(configURL) 302 if !ok || serverURL == "" { 303 // If there's no server URL, it means we're trying an operation at the backend level, like a "rclone authorize seafile" 304 fmt.Print("\nOperation not supported on this remote.\nIf you need a 2FA code on your account, use the command:\n\nrclone config reconnect <remote name>:\n\n") 305 return 306 } 307 308 // Stop if we are running non-interactive config 309 if fs.Config.AutoConfirm { 310 return 311 } 312 313 u, err := url.Parse(serverURL) 314 if err != nil { 315 fs.Errorf(nil, "Invalid server URL %s", serverURL) 316 return 317 } 318 319 is2faEnabled, _ := m.Get(config2FA) 320 if is2faEnabled != "true" { 321 fmt.Println("Two-factor authentication is not enabled on this account.") 322 return 323 } 324 325 username, _ := m.Get(configUser) 326 if username == "" { 327 fs.Errorf(nil, "A username is required") 328 return 329 } 330 331 password, _ := m.Get(configPassword) 332 if password != "" { 333 password, _ = obscure.Reveal(password) 334 } 335 // Just make sure we do have a password 336 for password == "" { 337 fmt.Print("Two-factor authentication: please enter your password (it won't be saved in the configuration)\npassword> ") 338 password = config.ReadPassword() 339 } 340 341 // Create rest client for getAuthorizationToken 342 url := u.String() 343 if !strings.HasPrefix(url, "/") { 344 url += "/" 345 } 346 srv := rest.NewClient(fshttp.NewClient(fs.Config)).SetRoot(url) 347 348 // We loop asking for a 2FA code 349 for { 350 code := "" 351 for code == "" { 352 fmt.Print("Two-factor authentication: please enter your 2FA code\n2fa code> ") 353 code = config.ReadLine() 354 } 355 356 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 357 defer cancel() 358 359 fmt.Println("Authenticating...") 360 token, err := getAuthorizationToken(ctx, srv, username, password, code) 361 if err != nil { 362 fmt.Printf("Authentication failed: %v\n", err) 363 tryAgain := strings.ToLower(config.ReadNonEmptyLine("Do you want to try again (y/n)?")) 364 if tryAgain != "y" && tryAgain != "yes" { 365 // The user is giving up, we're done here 366 break 367 } 368 } 369 if token != "" { 370 fmt.Println("Success!") 371 // Let's save the token into the configuration 372 m.Set(configAuthToken, token) 373 // And delete any previous entry for password 374 m.Set(configPassword, "") 375 config.SaveConfig() 376 // And we're done here 377 break 378 } 379 } 380 } 381 382 // sets the AuthorizationToken up 383 func (f *Fs) setAuthorizationToken(token string) { 384 f.srv.SetHeader("Authorization", "Token "+token) 385 } 386 387 // authorizeAccount gets the auth token. 388 func (f *Fs) authorizeAccount(ctx context.Context) (string, error) { 389 f.authMu.Lock() 390 defer f.authMu.Unlock() 391 392 token, err := f.getAuthorizationToken(ctx) 393 if err != nil { 394 return "", err 395 } 396 return token, nil 397 } 398 399 // retryErrorCodes is a slice of error codes that we will retry 400 var retryErrorCodes = []int{ 401 408, // Request Timeout 402 429, // Rate exceeded. 403 500, // Get occasional 500 Internal Server Error 404 503, // Service Unavailable 405 504, // Gateway Time-out 406 520, // Operation failed (We get them sometimes when running tests in parallel) 407 } 408 409 // shouldRetry returns a boolean as to whether this resp and err 410 // deserve to be retried. It returns the err as a convenience 411 func (f *Fs) shouldRetry(resp *http.Response, err error) (bool, error) { 412 // For 429 errors look at the Retry-After: header and 413 // set the retry appropriately, starting with a minimum of 1 414 // second if it isn't set. 415 if resp != nil && (resp.StatusCode == 429) { 416 var retryAfter = 1 417 retryAfterString := resp.Header.Get(retryAfterHeader) 418 if retryAfterString != "" { 419 var err error 420 retryAfter, err = strconv.Atoi(retryAfterString) 421 if err != nil { 422 fs.Errorf(f, "Malformed %s header %q: %v", retryAfterHeader, retryAfterString, err) 423 } 424 } 425 return true, pacer.RetryAfterError(err, time.Duration(retryAfter)*time.Second) 426 } 427 return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err 428 } 429 430 func (f *Fs) shouldRetryUpload(ctx context.Context, resp *http.Response, err error) (bool, error) { 431 if err != nil || (resp != nil && resp.StatusCode > 400) { 432 return true, err 433 } 434 return false, nil 435 } 436 437 // Name of the remote (as passed into NewFs) 438 func (f *Fs) Name() string { 439 return f.name 440 } 441 442 // Root of the remote (as passed into NewFs) 443 func (f *Fs) Root() string { 444 return f.root 445 } 446 447 // String converts this Fs to a string 448 func (f *Fs) String() string { 449 if f.libraryName == "" { 450 return fmt.Sprintf("seafile root") 451 } 452 library := "library" 453 if f.encrypted { 454 library = "encrypted " + library 455 } 456 if f.rootDirectory == "" { 457 return fmt.Sprintf("seafile %s '%s'", library, f.libraryName) 458 } 459 return fmt.Sprintf("seafile %s '%s' path '%s'", library, f.libraryName, f.rootDirectory) 460 } 461 462 // Precision of the ModTimes in this Fs 463 func (f *Fs) Precision() time.Duration { 464 // The API doesn't support setting the modified time 465 return fs.ModTimeNotSupported 466 } 467 468 // Hashes returns the supported hash sets. 469 func (f *Fs) Hashes() hash.Set { 470 return hash.Set(hash.None) 471 } 472 473 // Features returns the optional features of this Fs 474 func (f *Fs) Features() *fs.Features { 475 return f.features 476 } 477 478 // List the objects and directories in dir into entries. The 479 // entries can be returned in any order but should be for a 480 // complete directory. 481 // 482 // dir should be "" to list the root, and should not have 483 // trailing slashes. 484 // 485 // This should return fs.ErrorDirNotFound if the directory isn't 486 // found. 487 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 488 if dir == "" && f.libraryName == "" { 489 return f.listLibraries(ctx) 490 } 491 return f.listDir(ctx, dir, false) 492 } 493 494 // NewObject finds the Object at remote. If it can't be found 495 // it returns the error fs.ErrorObjectNotFound. 496 func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { 497 libraryName, filePath := f.splitPath(remote) 498 libraryID, err := f.getLibraryID(ctx, libraryName) 499 if err != nil { 500 return nil, err 501 } 502 err = f.authorizeLibrary(ctx, libraryID) 503 if err != nil { 504 return nil, err 505 } 506 507 fileDetails, err := f.getFileDetails(ctx, libraryID, filePath) 508 if err != nil { 509 return nil, err 510 } 511 512 modTime, err := time.Parse(time.RFC3339, fileDetails.Modified) 513 if err != nil { 514 fs.LogPrintf(fs.LogLevelWarning, fileDetails.Modified, "Cannot parse datetime") 515 } 516 517 o := &Object{ 518 fs: f, 519 libraryID: libraryID, 520 id: fileDetails.ID, 521 remote: remote, 522 pathInLibrary: filePath, 523 modTime: modTime, 524 size: fileDetails.Size, 525 } 526 return o, nil 527 } 528 529 // Put in to the remote path with the modTime given of the given size 530 // 531 // When called from outside an Fs by rclone, src.Size() will always be >= 0. 532 // But for unknown-sized objects (indicated by src.Size() == -1), Put should either 533 // return an error or upload it properly (rather than e.g. calling panic). 534 // 535 // May create the object even if it returns an error - if so 536 // will return the object and the error, otherwise will return 537 // nil and the error 538 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 539 object := f.newObject(ctx, src.Remote(), src.Size(), src.ModTime(ctx)) 540 // Check if we need to create a new library at that point 541 if object.libraryID == "" { 542 library, _ := f.splitPath(object.remote) 543 err := f.Mkdir(ctx, library) 544 if err != nil { 545 return object, err 546 } 547 libraryID, err := f.getLibraryID(ctx, library) 548 if err != nil { 549 return object, err 550 } 551 object.libraryID = libraryID 552 } 553 err := object.Update(ctx, in, src, options...) 554 if err != nil { 555 return object, err 556 } 557 return object, nil 558 } 559 560 // PutStream uploads to the remote path with the modTime given but of indeterminate size 561 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 562 return f.Put(ctx, in, src, options...) 563 } 564 565 // Mkdir makes the directory or library 566 // 567 // Shouldn't return an error if it already exists 568 func (f *Fs) Mkdir(ctx context.Context, dir string) error { 569 libraryName, folder := f.splitPath(dir) 570 if strings.HasPrefix(dir, libraryName) { 571 err := f.mkLibrary(ctx, libraryName, "") 572 if err != nil { 573 return err 574 } 575 if folder == "" { 576 // No directory to create after the library 577 return nil 578 } 579 } 580 err := f.mkDir(ctx, dir) 581 if err != nil { 582 return err 583 } 584 return nil 585 } 586 587 // Rmdir removes the directory or library if empty 588 // 589 // Return an error if it doesn't exist or isn't empty 590 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 591 libraryName, dirPath := f.splitPath(dir) 592 libraryID, err := f.getLibraryID(ctx, libraryName) 593 if err != nil { 594 return err 595 } 596 597 directoryEntries, err := f.getDirectoryEntries(ctx, libraryID, dirPath, false) 598 if err != nil { 599 return err 600 } 601 if len(directoryEntries) > 0 { 602 return fs.ErrorDirectoryNotEmpty 603 } 604 if dirPath == "" || dirPath == "/" { 605 return f.deleteLibrary(ctx, libraryID) 606 } 607 return f.deleteDir(ctx, libraryID, dirPath) 608 } 609 610 // ==================== Optional Interface fs.ListRer ==================== 611 612 // ListR lists the objects and directories of the Fs starting 613 // from dir recursively into out. 614 // 615 // dir should be "" to start from the root, and should not 616 // have trailing slashes. 617 // 618 // This should return ErrDirNotFound if the directory isn't 619 // found. 620 // 621 // It should call callback for each tranche of entries read. 622 // These need not be returned in any particular order. If 623 // callback returns an error then the listing will stop 624 // immediately. 625 func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) error { 626 var err error 627 628 if dir == "" && f.libraryName == "" { 629 libraries, err := f.listLibraries(ctx) 630 if err != nil { 631 return err 632 } 633 // Send the library list as folders 634 err = callback(libraries) 635 if err != nil { 636 return err 637 } 638 639 // Then list each library 640 for _, library := range libraries { 641 err = f.listDirCallback(ctx, library.Remote(), callback) 642 if err != nil { 643 return err 644 } 645 } 646 return nil 647 } 648 err = f.listDirCallback(ctx, dir, callback) 649 if err != nil { 650 return err 651 } 652 return nil 653 } 654 655 // ==================== Optional Interface fs.Copier ==================== 656 657 // Copy src to this remote using server side copy operations. 658 // 659 // This is stored with the remote path given 660 // 661 // It returns the destination Object and a possible error 662 // 663 // If it isn't possible then return fs.ErrorCantCopy 664 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 665 srcObj, ok := src.(*Object) 666 if !ok { 667 return nil, fs.ErrorCantCopy 668 } 669 srcLibraryName, srcPath := srcObj.fs.splitPath(src.Remote()) 670 srcLibraryID, err := srcObj.fs.getLibraryID(ctx, srcLibraryName) 671 if err != nil { 672 return nil, err 673 } 674 dstLibraryName, dstPath := f.splitPath(remote) 675 dstLibraryID, err := f.getLibraryID(ctx, dstLibraryName) 676 if err != nil { 677 return nil, err 678 } 679 680 // Seafile does not accept a file name as a destination, only a path. 681 // The destination filename will be the same as the original, or with (1) added in case it was already existing 682 dstDir, dstFilename := path.Split(dstPath) 683 684 // We have to make sure the destination path exists on the server or it's going to bomb out with an obscure error message 685 err = f.mkMultiDir(ctx, dstLibraryID, dstDir) 686 if err != nil { 687 return nil, err 688 } 689 690 op, err := f.copyFile(ctx, srcLibraryID, srcPath, dstLibraryID, dstDir) 691 if err != nil { 692 return nil, err 693 } 694 695 if op.Name != dstFilename { 696 // Destination was existing, so we need to move the file back into place 697 err = f.adjustDestination(ctx, dstLibraryID, op.Name, dstPath, dstDir, dstFilename) 698 if err != nil { 699 return nil, err 700 } 701 } 702 // Create a new object from the result 703 return f.NewObject(ctx, remote) 704 } 705 706 // ==================== Optional Interface fs.Mover ==================== 707 708 // Move src to this remote using server side move operations. 709 // 710 // This is stored with the remote path given 711 // 712 // It returns the destination Object and a possible error 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 return nil, fs.ErrorCantMove 719 } 720 721 srcLibraryName, srcPath := srcObj.fs.splitPath(src.Remote()) 722 srcLibraryID, err := srcObj.fs.getLibraryID(ctx, srcLibraryName) 723 if err != nil { 724 return nil, err 725 } 726 dstLibraryName, dstPath := f.splitPath(remote) 727 dstLibraryID, err := f.getLibraryID(ctx, dstLibraryName) 728 if err != nil { 729 return nil, err 730 } 731 732 // anchor both source and destination paths from the root so we can compare them 733 srcPath = path.Join("/", srcPath) 734 dstPath = path.Join("/", dstPath) 735 736 srcDir := path.Dir(srcPath) 737 dstDir, dstFilename := path.Split(dstPath) 738 739 if srcLibraryID == dstLibraryID && srcDir == dstDir { 740 // It's only a simple case of renaming the file 741 _, err := f.renameFile(ctx, srcLibraryID, srcPath, dstFilename) 742 if err != nil { 743 return nil, err 744 } 745 return f.NewObject(ctx, remote) 746 } 747 748 // We have to make sure the destination path exists on the server 749 err = f.mkMultiDir(ctx, dstLibraryID, dstDir) 750 if err != nil { 751 return nil, err 752 } 753 754 // Seafile does not accept a file name as a destination, only a path. 755 // The destination filename will be the same as the original, or with (1) added in case it already exists 756 op, err := f.moveFile(ctx, srcLibraryID, srcPath, dstLibraryID, dstDir) 757 if err != nil { 758 return nil, err 759 } 760 761 if op.Name != dstFilename { 762 // Destination was existing, so we need to move the file back into place 763 err = f.adjustDestination(ctx, dstLibraryID, op.Name, dstPath, dstDir, dstFilename) 764 if err != nil { 765 return nil, err 766 } 767 } 768 769 // Create a new object from the result 770 return f.NewObject(ctx, remote) 771 } 772 773 // adjustDestination rename the file 774 func (f *Fs) adjustDestination(ctx context.Context, libraryID, srcFilename, dstPath, dstDir, dstFilename string) error { 775 // Seafile seems to be acting strangely if the renamed file already exists (some cache issue maybe?) 776 // It's better to delete the destination if it already exists 777 fileDetail, err := f.getFileDetails(ctx, libraryID, dstPath) 778 if err != nil && err != fs.ErrorObjectNotFound { 779 return err 780 } 781 if fileDetail != nil { 782 err = f.deleteFile(ctx, libraryID, dstPath) 783 if err != nil { 784 return err 785 } 786 } 787 _, err = f.renameFile(ctx, libraryID, path.Join(dstDir, srcFilename), dstFilename) 788 if err != nil { 789 return err 790 } 791 792 return nil 793 } 794 795 // ==================== Optional Interface fs.DirMover ==================== 796 797 // DirMove moves src, srcRemote to this remote at dstRemote 798 // using server side move operations. 799 // 800 // Will only be called if src.Fs().Name() == f.Name() 801 // 802 // If it isn't possible then return fs.ErrorCantDirMove 803 // 804 // If destination exists then return fs.ErrorDirExists 805 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 806 807 // Cast into a seafile Fs 808 srcFs, ok := src.(*Fs) 809 if !ok { 810 return fs.ErrorCantDirMove 811 } 812 813 srcLibraryName, srcPath := srcFs.splitPath(srcRemote) 814 srcLibraryID, err := srcFs.getLibraryID(ctx, srcLibraryName) 815 if err != nil { 816 return err 817 } 818 dstLibraryName, dstPath := f.splitPath(dstRemote) 819 dstLibraryID, err := f.getLibraryID(ctx, dstLibraryName) 820 if err != nil { 821 return err 822 } 823 824 srcDir := path.Dir(srcPath) 825 dstDir, dstName := path.Split(dstPath) 826 827 // anchor both source and destination to the root so we can compare them 828 srcDir = path.Join("/", srcDir) 829 dstDir = path.Join("/", dstDir) 830 831 // The destination should not exist 832 entries, err := f.getDirectoryEntries(ctx, dstLibraryID, dstDir, false) 833 if err != nil && err != fs.ErrorDirNotFound { 834 return err 835 } 836 if err == nil { 837 for _, entry := range entries { 838 if entry.Name == dstName { 839 // Destination exists 840 return fs.ErrorDirExists 841 } 842 } 843 } 844 if srcLibraryID == dstLibraryID && srcDir == dstDir { 845 // It's only renaming 846 err = srcFs.renameDir(ctx, dstLibraryID, srcPath, dstName) 847 if err != nil { 848 return err 849 } 850 return nil 851 } 852 853 // Seafile < 7 does not support moving directories 854 if f.moveDirNotAvailable { 855 return fs.ErrorCantDirMove 856 } 857 858 // Make sure the destination path exists 859 err = f.mkMultiDir(ctx, dstLibraryID, dstDir) 860 if err != nil { 861 return err 862 } 863 864 // If the destination already exists, seafile will add a " (n)" to the name. 865 // Sadly this API call will not return the new given name like the move file version does 866 // So the trick is to rename the directory to something random before moving it 867 // After the move we rename the random name back to the expected one 868 // Hopefully there won't be anything with the same name existing at destination ;) 869 tempName := ".rclone-move-" + random.String(32) 870 871 // 1- rename source 872 err = srcFs.renameDir(ctx, srcLibraryID, srcPath, tempName) 873 if err != nil { 874 return errors.Wrap(err, "Cannot rename source directory to a temporary name") 875 } 876 877 // 2- move source to destination 878 err = f.moveDir(ctx, srcLibraryID, srcDir, tempName, dstLibraryID, dstDir) 879 if err != nil { 880 // Doh! Let's rename the source back to its original name 881 _ = srcFs.renameDir(ctx, srcLibraryID, path.Join(srcDir, tempName), path.Base(srcPath)) 882 return err 883 } 884 885 // 3- rename destination back to source name 886 err = f.renameDir(ctx, dstLibraryID, path.Join(dstDir, tempName), dstName) 887 if err != nil { 888 return errors.Wrap(err, "Cannot rename temporary directory to destination name") 889 } 890 891 return nil 892 } 893 894 // ==================== Optional Interface fs.Purger ==================== 895 896 // Purge all files in the root and the root directory 897 // 898 // Implement this if you have a way of deleting all the files 899 // quicker than just running Remove() on the result of List() 900 // 901 // Return an error if it doesn't exist 902 func (f *Fs) Purge(ctx context.Context) error { 903 if f.libraryName == "" { 904 return errors.New("Cannot delete from the root of the server. Please select a library") 905 } 906 libraryID, err := f.getLibraryID(ctx, f.libraryName) 907 if err != nil { 908 return err 909 } 910 if f.rootDirectory == "" { 911 // Delete library 912 err = f.deleteLibrary(ctx, libraryID) 913 if err != nil { 914 return err 915 } 916 return nil 917 } 918 err = f.deleteDir(ctx, libraryID, f.rootDirectory) 919 if err != nil { 920 return err 921 } 922 return nil 923 } 924 925 // ==================== Optional Interface fs.CleanUpper ==================== 926 927 // CleanUp the trash in the Fs 928 func (f *Fs) CleanUp(ctx context.Context) error { 929 if f.libraryName == "" { 930 return errors.New("Cannot clean up at the root of the seafile server: please select a library to clean up") 931 } 932 libraryID, err := f.getLibraryID(ctx, f.libraryName) 933 if err != nil { 934 return err 935 } 936 return f.emptyLibraryTrash(ctx, libraryID) 937 } 938 939 // ==================== Optional Interface fs.Abouter ==================== 940 941 // About gets quota information 942 func (f *Fs) About(ctx context.Context) (usage *fs.Usage, err error) { 943 accountInfo, err := f.getUserAccountInfo(ctx) 944 if err != nil { 945 return nil, err 946 } 947 948 usage = &fs.Usage{ 949 Used: fs.NewUsageValue(accountInfo.Usage), // bytes in use 950 } 951 if accountInfo.Total > 0 { 952 usage.Total = fs.NewUsageValue(accountInfo.Total) // quota of bytes that can be used 953 usage.Free = fs.NewUsageValue(accountInfo.Total - accountInfo.Usage) // bytes which can be uploaded before reaching the quota 954 } 955 return usage, nil 956 } 957 958 // ==================== Optional Interface fs.UserInfoer ==================== 959 960 // UserInfo returns info about the connected user 961 func (f *Fs) UserInfo(ctx context.Context) (map[string]string, error) { 962 accountInfo, err := f.getUserAccountInfo(ctx) 963 if err != nil { 964 return nil, err 965 } 966 return map[string]string{ 967 "Name": accountInfo.Name, 968 "Email": accountInfo.Email, 969 }, nil 970 } 971 972 // ==================== Optional Interface fs.PublicLinker ==================== 973 974 // PublicLink generates a public link to the remote path (usually readable by anyone) 975 func (f *Fs) PublicLink(ctx context.Context, remote string) (string, error) { 976 libraryName, filePath := f.splitPath(remote) 977 if libraryName == "" { 978 // We cannot share the whole seafile server, we need at least a library 979 return "", errors.New("Cannot share the root of the seafile server. Please select a library to share") 980 } 981 libraryID, err := f.getLibraryID(ctx, libraryName) 982 if err != nil { 983 return "", err 984 } 985 986 // List existing links first 987 shareLinks, err := f.listShareLinks(ctx, libraryID, filePath) 988 if err != nil { 989 return "", err 990 } 991 if shareLinks != nil && len(shareLinks) > 0 { 992 for _, shareLink := range shareLinks { 993 if shareLink.IsExpired == false { 994 return shareLink.Link, nil 995 } 996 } 997 } 998 // No link was found 999 shareLink, err := f.createShareLink(ctx, libraryID, filePath) 1000 if err != nil { 1001 return "", err 1002 } 1003 if shareLink.IsExpired { 1004 return "", nil 1005 } 1006 return shareLink.Link, nil 1007 } 1008 1009 func (f *Fs) listLibraries(ctx context.Context) (entries fs.DirEntries, err error) { 1010 libraries, err := f.getCachedLibraries(ctx) 1011 if err != nil { 1012 return nil, errors.New("cannot load libraries") 1013 } 1014 1015 for _, library := range libraries { 1016 d := fs.NewDir(library.Name, time.Unix(library.Modified, 0)) 1017 d.SetSize(int64(library.Size)) 1018 entries = append(entries, d) 1019 } 1020 1021 return entries, nil 1022 } 1023 1024 func (f *Fs) libraryExists(ctx context.Context, libraryName string) (bool, error) { 1025 libraries, err := f.getCachedLibraries(ctx) 1026 if err != nil { 1027 return false, err 1028 } 1029 1030 for _, library := range libraries { 1031 if library.Name == libraryName { 1032 return true, nil 1033 } 1034 } 1035 return false, nil 1036 } 1037 1038 func (f *Fs) getLibraryID(ctx context.Context, name string) (string, error) { 1039 libraries, err := f.getCachedLibraries(ctx) 1040 if err != nil { 1041 return "", err 1042 } 1043 1044 for _, library := range libraries { 1045 if library.Name == name { 1046 return library.ID, nil 1047 } 1048 } 1049 return "", fmt.Errorf("cannot find library '%s'", name) 1050 } 1051 1052 func (f *Fs) isLibraryInCache(libraryName string) bool { 1053 f.librariesMutex.Lock() 1054 defer f.librariesMutex.Unlock() 1055 1056 if f.libraries == nil { 1057 return false 1058 } 1059 value, found := f.libraries.GetMaybe(librariesCacheKey) 1060 if found == false { 1061 return false 1062 } 1063 libraries := value.([]api.Library) 1064 for _, library := range libraries { 1065 if library.Name == libraryName { 1066 return true 1067 } 1068 } 1069 return false 1070 } 1071 1072 func (f *Fs) isEncrypted(ctx context.Context, libraryID string) (bool, error) { 1073 libraries, err := f.getCachedLibraries(ctx) 1074 if err != nil { 1075 return false, err 1076 } 1077 1078 for _, library := range libraries { 1079 if library.ID == libraryID { 1080 return library.Encrypted, nil 1081 } 1082 } 1083 return false, fmt.Errorf("cannot find library ID %s", libraryID) 1084 } 1085 1086 func (f *Fs) authorizeLibrary(ctx context.Context, libraryID string) error { 1087 if libraryID == "" { 1088 return errors.New("a library ID is needed") 1089 } 1090 if f.opt.LibraryKey == "" { 1091 // We have no password to send 1092 return nil 1093 } 1094 encrypted, err := f.isEncrypted(ctx, libraryID) 1095 if err != nil { 1096 return err 1097 } 1098 if encrypted { 1099 fs.Debugf(nil, "Decrypting library %s", libraryID) 1100 f.authMu.Lock() 1101 defer f.authMu.Unlock() 1102 err := f.decryptLibrary(ctx, libraryID, f.opt.LibraryKey) 1103 if err != nil { 1104 return err 1105 } 1106 } 1107 return nil 1108 } 1109 1110 func (f *Fs) mkLibrary(ctx context.Context, libraryName, password string) error { 1111 // lock specific to library creation 1112 // we cannot reuse the same lock as we will dead-lock ourself if the libraries are not in cache 1113 createLibraryMutex.Lock() 1114 defer createLibraryMutex.Unlock() 1115 1116 if libraryName == "" { 1117 return errors.New("a library name is needed") 1118 } 1119 1120 // It's quite likely that multiple go routines are going to try creating the same library 1121 // at the start of a sync/copy. After releasing the mutex the calls waiting would try to create 1122 // the same library again. So we'd better check the library exists first 1123 if f.isLibraryInCache(libraryName) { 1124 return nil 1125 } 1126 1127 fs.Debugf(nil, "%s: Create library '%s'", f.Name(), libraryName) 1128 f.librariesMutex.Lock() 1129 defer f.librariesMutex.Unlock() 1130 1131 library, err := f.createLibrary(ctx, libraryName, password) 1132 if err != nil { 1133 return err 1134 } 1135 // Stores the library details into the cache 1136 value, found := f.libraries.GetMaybe(librariesCacheKey) 1137 if found == false { 1138 // Don't update the cache at that point 1139 return nil 1140 } 1141 libraries := value.([]api.Library) 1142 libraries = append(libraries, api.Library{ 1143 ID: library.ID, 1144 Name: library.Name, 1145 }) 1146 f.libraries.Put(librariesCacheKey, libraries) 1147 return nil 1148 } 1149 1150 // splitPath returns the library name and the full path inside the library 1151 func (f *Fs) splitPath(dir string) (library, folder string) { 1152 library = f.libraryName 1153 folder = dir 1154 if library == "" { 1155 // The first part of the path is the library 1156 library, folder = bucket.Split(dir) 1157 } else if f.rootDirectory != "" { 1158 // Adds the root folder to the path to get a full path 1159 folder = path.Join(f.rootDirectory, folder) 1160 } 1161 return 1162 } 1163 1164 func (f *Fs) listDir(ctx context.Context, dir string, recursive bool) (entries fs.DirEntries, err error) { 1165 libraryName, dirPath := f.splitPath(dir) 1166 libraryID, err := f.getLibraryID(ctx, libraryName) 1167 if err != nil { 1168 return nil, err 1169 } 1170 1171 directoryEntries, err := f.getDirectoryEntries(ctx, libraryID, dirPath, recursive) 1172 if err != nil { 1173 return nil, err 1174 } 1175 1176 return f.buildDirEntries(dir, libraryID, dirPath, directoryEntries, recursive), nil 1177 } 1178 1179 // listDirCallback is calling listDir with the recursive option and is sending the result to the callback 1180 func (f *Fs) listDirCallback(ctx context.Context, dir string, callback fs.ListRCallback) error { 1181 entries, err := f.listDir(ctx, dir, true) 1182 if err != nil { 1183 return err 1184 } 1185 err = callback(entries) 1186 if err != nil { 1187 return err 1188 } 1189 return nil 1190 } 1191 1192 func (f *Fs) buildDirEntries(parentPath, libraryID, parentPathInLibrary string, directoryEntries []api.DirEntry, recursive bool) (entries fs.DirEntries) { 1193 for _, entry := range directoryEntries { 1194 var filePath, filePathInLibrary string 1195 if recursive { 1196 // In recursive mode, paths are built from DirEntry (+ a starting point) 1197 entryPath := strings.TrimPrefix(entry.Path, "/") 1198 // If we're listing from some path inside the library (not the root) 1199 // there's already a path in parameter, which will also be included in the entry path 1200 entryPath = strings.TrimPrefix(entryPath, parentPathInLibrary) 1201 entryPath = strings.TrimPrefix(entryPath, "/") 1202 1203 filePath = path.Join(parentPath, entryPath, entry.Name) 1204 filePathInLibrary = path.Join(parentPathInLibrary, entryPath, entry.Name) 1205 } else { 1206 // In non-recursive mode, paths are build from the parameters 1207 filePath = path.Join(parentPath, entry.Name) 1208 filePathInLibrary = path.Join(parentPathInLibrary, entry.Name) 1209 } 1210 if entry.Type == api.FileTypeDir { 1211 d := fs. 1212 NewDir(filePath, time.Unix(entry.Modified, 0)). 1213 SetSize(entry.Size). 1214 SetID(entry.ID) 1215 entries = append(entries, d) 1216 } else if entry.Type == api.FileTypeFile { 1217 object := &Object{ 1218 fs: f, 1219 id: entry.ID, 1220 remote: filePath, 1221 pathInLibrary: filePathInLibrary, 1222 size: entry.Size, 1223 modTime: time.Unix(entry.Modified, 0), 1224 libraryID: libraryID, 1225 } 1226 entries = append(entries, object) 1227 } 1228 } 1229 return entries 1230 } 1231 1232 func (f *Fs) mkDir(ctx context.Context, dir string) error { 1233 library, fullPath := f.splitPath(dir) 1234 libraryID, err := f.getLibraryID(ctx, library) 1235 if err != nil { 1236 return err 1237 } 1238 return f.mkMultiDir(ctx, libraryID, fullPath) 1239 } 1240 1241 func (f *Fs) mkMultiDir(ctx context.Context, libraryID, dir string) error { 1242 // rebuild the path one by one 1243 currentPath := "" 1244 for _, singleDir := range splitPath(dir) { 1245 currentPath = path.Join(currentPath, singleDir) 1246 err := f.mkSingleDir(ctx, libraryID, currentPath) 1247 if err != nil { 1248 return err 1249 } 1250 } 1251 return nil 1252 } 1253 1254 func (f *Fs) mkSingleDir(ctx context.Context, libraryID, dir string) error { 1255 f.createDirMutex.Lock() 1256 defer f.createDirMutex.Unlock() 1257 1258 dirDetails, err := f.getDirectoryDetails(ctx, libraryID, dir) 1259 if err == nil && dirDetails != nil { 1260 // Don't fail if the directory exists 1261 return nil 1262 } 1263 if err == fs.ErrorDirNotFound { 1264 err = f.createDir(ctx, libraryID, dir) 1265 if err != nil { 1266 return err 1267 } 1268 return nil 1269 } 1270 return err 1271 } 1272 1273 func (f *Fs) getDirectoryEntries(ctx context.Context, libraryID, folder string, recursive bool) ([]api.DirEntry, error) { 1274 if f.useOldDirectoryAPI { 1275 return f.getDirectoryEntriesAPIv2(ctx, libraryID, folder) 1276 } 1277 return f.getDirectoryEntriesAPIv21(ctx, libraryID, folder, recursive) 1278 } 1279 1280 // splitPath creates a slice of paths 1281 func splitPath(tree string) (paths []string) { 1282 tree, leaf := path.Split(path.Clean(tree)) 1283 for leaf != "" && leaf != "." { 1284 paths = append([]string{leaf}, paths...) 1285 tree, leaf = path.Split(path.Clean(tree)) 1286 } 1287 return 1288 } 1289 1290 func (f *Fs) getCachedLibraries(ctx context.Context) ([]api.Library, error) { 1291 f.librariesMutex.Lock() 1292 defer f.librariesMutex.Unlock() 1293 1294 libraries, err := f.libraries.Get(librariesCacheKey, func(key string) (value interface{}, ok bool, error error) { 1295 // Load the libraries if not present in the cache 1296 libraries, err := f.getLibraries(ctx) 1297 if err != nil { 1298 return nil, false, err 1299 } 1300 return libraries, true, nil 1301 }) 1302 if err != nil { 1303 return nil, err 1304 } 1305 // Type assertion 1306 return libraries.([]api.Library), nil 1307 } 1308 1309 func (f *Fs) newObject(ctx context.Context, remote string, size int64, modTime time.Time) *Object { 1310 libraryName, remotePath := f.splitPath(remote) 1311 libraryID, _ := f.getLibraryID(ctx, libraryName) // If error it means the library does not exist (yet) 1312 1313 object := &Object{ 1314 fs: f, 1315 remote: remote, 1316 libraryID: libraryID, 1317 pathInLibrary: remotePath, 1318 size: size, 1319 modTime: modTime, 1320 } 1321 return object 1322 } 1323 1324 // Check the interfaces are satisfied 1325 var ( 1326 _ fs.Fs = &Fs{} 1327 _ fs.Abouter = &Fs{} 1328 _ fs.CleanUpper = &Fs{} 1329 _ fs.Copier = &Fs{} 1330 _ fs.Mover = &Fs{} 1331 _ fs.DirMover = &Fs{} 1332 _ fs.ListRer = &Fs{} 1333 _ fs.Purger = &Fs{} 1334 _ fs.PutStreamer = &Fs{} 1335 _ fs.PublicLinker = &Fs{} 1336 _ fs.UserInfoer = &Fs{} 1337 _ fs.Object = &Object{} 1338 _ fs.IDer = &Object{} 1339 )