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  )