github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/seafile/seafile.go (about)

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