github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/backend/box/box.go (about)

     1  // Package box provides an interface to the Box
     2  // object storage system.
     3  package box
     4  
     5  // FIXME Box only supports file names of 255 characters or less. Names
     6  // that will not be supported are those that contain non-printable
     7  // ascii, / or \, names with trailing spaces, and the special names
     8  // “.” and “..”.
     9  
    10  // FIXME box can copy a directory
    11  
    12  import (
    13  	"context"
    14  	"crypto/rsa"
    15  	"encoding/json"
    16  	"encoding/pem"
    17  	"fmt"
    18  	"io"
    19  	"io/ioutil"
    20  	"log"
    21  	"net/http"
    22  	"net/url"
    23  	"path"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/rclone/rclone/lib/encoder"
    29  	"github.com/rclone/rclone/lib/jwtutil"
    30  
    31  	"github.com/youmark/pkcs8"
    32  
    33  	"github.com/pkg/errors"
    34  	"github.com/rclone/rclone/backend/box/api"
    35  	"github.com/rclone/rclone/fs"
    36  	"github.com/rclone/rclone/fs/config"
    37  	"github.com/rclone/rclone/fs/config/configmap"
    38  	"github.com/rclone/rclone/fs/config/configstruct"
    39  	"github.com/rclone/rclone/fs/config/obscure"
    40  	"github.com/rclone/rclone/fs/fserrors"
    41  	"github.com/rclone/rclone/fs/fshttp"
    42  	"github.com/rclone/rclone/fs/hash"
    43  	"github.com/rclone/rclone/lib/dircache"
    44  	"github.com/rclone/rclone/lib/oauthutil"
    45  	"github.com/rclone/rclone/lib/pacer"
    46  	"github.com/rclone/rclone/lib/rest"
    47  	"golang.org/x/oauth2"
    48  	"golang.org/x/oauth2/jws"
    49  )
    50  
    51  const (
    52  	rcloneClientID              = "d0374ba6pgmaguie02ge15sv1mllndho"
    53  	rcloneEncryptedClientSecret = "sYbJYm99WB8jzeaLPU0OPDMJKIkZvD2qOn3SyEMfiJr03RdtDt3xcZEIudRhbIDL"
    54  	minSleep                    = 10 * time.Millisecond
    55  	maxSleep                    = 2 * time.Second
    56  	decayConstant               = 2 // bigger for slower decay, exponential
    57  	rootURL                     = "https://api.box.com/2.0"
    58  	uploadURL                   = "https://upload.box.com/api/2.0"
    59  	listChunks                  = 1000     // chunk size to read directory listings
    60  	minUploadCutoff             = 50000000 // upload cutoff can be no lower than this
    61  	defaultUploadCutoff         = 50 * 1024 * 1024
    62  	tokenURL                    = "https://api.box.com/oauth2/token"
    63  )
    64  
    65  // Globals
    66  var (
    67  	// Description of how to auth for this app
    68  	oauthConfig = &oauth2.Config{
    69  		Scopes: nil,
    70  		Endpoint: oauth2.Endpoint{
    71  			AuthURL:  "https://app.box.com/api/oauth2/authorize",
    72  			TokenURL: "https://app.box.com/api/oauth2/token",
    73  		},
    74  		ClientID:     rcloneClientID,
    75  		ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
    76  		RedirectURL:  oauthutil.RedirectURL,
    77  	}
    78  )
    79  
    80  // Register with Fs
    81  func init() {
    82  	fs.Register(&fs.RegInfo{
    83  		Name:        "box",
    84  		Description: "Box",
    85  		NewFs:       NewFs,
    86  		Config: func(name string, m configmap.Mapper) {
    87  			jsonFile, ok := m.Get("box_config_file")
    88  			boxSubType, boxSubTypeOk := m.Get("box_sub_type")
    89  			var err error
    90  			if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" {
    91  				err = refreshJWTToken(jsonFile, boxSubType, name, m)
    92  				if err != nil {
    93  					log.Fatalf("Failed to configure token with jwt authentication: %v", err)
    94  				}
    95  			} else {
    96  				err = oauthutil.Config("box", name, m, oauthConfig, nil)
    97  				if err != nil {
    98  					log.Fatalf("Failed to configure token with oauth authentication: %v", err)
    99  				}
   100  			}
   101  		},
   102  		Options: []fs.Option{{
   103  			Name: config.ConfigClientID,
   104  			Help: "Box App Client Id.\nLeave blank normally.",
   105  		}, {
   106  			Name: config.ConfigClientSecret,
   107  			Help: "Box App Client Secret\nLeave blank normally.",
   108  		}, {
   109  			Name:     "root_folder_id",
   110  			Help:     "Fill in for rclone to use a non root folder as its starting point.",
   111  			Default:  "0",
   112  			Advanced: true,
   113  		}, {
   114  			Name: "box_config_file",
   115  			Help: "Box App config.json location\nLeave blank normally.",
   116  		}, {
   117  			Name:    "box_sub_type",
   118  			Default: "user",
   119  			Examples: []fs.OptionExample{{
   120  				Value: "user",
   121  				Help:  "Rclone should act on behalf of a user",
   122  			}, {
   123  				Value: "enterprise",
   124  				Help:  "Rclone should act on behalf of a service account",
   125  			}},
   126  		}, {
   127  			Name:     "upload_cutoff",
   128  			Help:     "Cutoff for switching to multipart upload (>= 50MB).",
   129  			Default:  fs.SizeSuffix(defaultUploadCutoff),
   130  			Advanced: true,
   131  		}, {
   132  			Name:     "commit_retries",
   133  			Help:     "Max number of times to try committing a multipart file.",
   134  			Default:  100,
   135  			Advanced: true,
   136  		}, {
   137  			Name:     config.ConfigEncoding,
   138  			Help:     config.ConfigEncodingHelp,
   139  			Advanced: true,
   140  			// From https://developer.box.com/docs/error-codes#section-400-bad-request :
   141  			// > Box only supports file or folder names that are 255 characters or less.
   142  			// > File names containing non-printable ascii, "/" or "\", names with leading
   143  			// > or trailing spaces, and the special names “.” and “..” are also unsupported.
   144  			//
   145  			// Testing revealed names with leading spaces work fine.
   146  			// Also encode invalid UTF-8 bytes as json doesn't handle them properly.
   147  			Default: (encoder.Display |
   148  				encoder.EncodeBackSlash |
   149  				encoder.EncodeRightSpace |
   150  				encoder.EncodeInvalidUtf8),
   151  		}},
   152  	})
   153  }
   154  
   155  func refreshJWTToken(jsonFile string, boxSubType string, name string, m configmap.Mapper) error {
   156  	boxConfig, err := getBoxConfig(jsonFile)
   157  	if err != nil {
   158  		log.Fatalf("Failed to configure token: %v", err)
   159  	}
   160  	privateKey, err := getDecryptedPrivateKey(boxConfig)
   161  	if err != nil {
   162  		log.Fatalf("Failed to configure token: %v", err)
   163  	}
   164  	claims, err := getClaims(boxConfig, boxSubType)
   165  	if err != nil {
   166  		log.Fatalf("Failed to configure token: %v", err)
   167  	}
   168  	signingHeaders := getSigningHeaders(boxConfig)
   169  	queryParams := getQueryParams(boxConfig)
   170  	client := fshttp.NewClient(fs.Config)
   171  	err = jwtutil.Config("box", name, claims, signingHeaders, queryParams, privateKey, m, client)
   172  	return err
   173  }
   174  
   175  func getBoxConfig(configFile string) (boxConfig *api.ConfigJSON, err error) {
   176  	file, err := ioutil.ReadFile(configFile)
   177  	if err != nil {
   178  		return nil, errors.Wrap(err, "box: failed to read Box config")
   179  	}
   180  	err = json.Unmarshal(file, &boxConfig)
   181  	if err != nil {
   182  		return nil, errors.Wrap(err, "box: failed to parse Box config")
   183  	}
   184  	return boxConfig, nil
   185  }
   186  
   187  func getClaims(boxConfig *api.ConfigJSON, boxSubType string) (claims *jws.ClaimSet, err error) {
   188  	val, err := jwtutil.RandomHex(20)
   189  	if err != nil {
   190  		return nil, errors.Wrap(err, "box: failed to generate random string for jti")
   191  	}
   192  
   193  	claims = &jws.ClaimSet{
   194  		Iss: boxConfig.BoxAppSettings.ClientID,
   195  		Sub: boxConfig.EnterpriseID,
   196  		Aud: tokenURL,
   197  		Exp: time.Now().Add(time.Second * 45).Unix(),
   198  		PrivateClaims: map[string]interface{}{
   199  			"box_sub_type": boxSubType,
   200  			"aud":          tokenURL,
   201  			"jti":          val,
   202  		},
   203  	}
   204  
   205  	return claims, nil
   206  }
   207  
   208  func getSigningHeaders(boxConfig *api.ConfigJSON) *jws.Header {
   209  	signingHeaders := &jws.Header{
   210  		Algorithm: "RS256",
   211  		Typ:       "JWT",
   212  		KeyID:     boxConfig.BoxAppSettings.AppAuth.PublicKeyID,
   213  	}
   214  
   215  	return signingHeaders
   216  }
   217  
   218  func getQueryParams(boxConfig *api.ConfigJSON) map[string]string {
   219  	queryParams := map[string]string{
   220  		"client_id":     boxConfig.BoxAppSettings.ClientID,
   221  		"client_secret": boxConfig.BoxAppSettings.ClientSecret,
   222  	}
   223  
   224  	return queryParams
   225  }
   226  
   227  func getDecryptedPrivateKey(boxConfig *api.ConfigJSON) (key *rsa.PrivateKey, err error) {
   228  
   229  	block, rest := pem.Decode([]byte(boxConfig.BoxAppSettings.AppAuth.PrivateKey))
   230  	if len(rest) > 0 {
   231  		return nil, errors.Wrap(err, "box: extra data included in private key")
   232  	}
   233  
   234  	rsaKey, err := pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte(boxConfig.BoxAppSettings.AppAuth.Passphrase))
   235  	if err != nil {
   236  		return nil, errors.Wrap(err, "box: failed to decrypt private key")
   237  	}
   238  
   239  	return rsaKey.(*rsa.PrivateKey), nil
   240  }
   241  
   242  // Options defines the configuration for this backend
   243  type Options struct {
   244  	UploadCutoff  fs.SizeSuffix        `config:"upload_cutoff"`
   245  	CommitRetries int                  `config:"commit_retries"`
   246  	Enc           encoder.MultiEncoder `config:"encoding"`
   247  	RootFolderID  string               `config:"root_folder_id"`
   248  }
   249  
   250  // Fs represents a remote box
   251  type Fs struct {
   252  	name         string                // name of this remote
   253  	root         string                // the path we are working on
   254  	opt          Options               // parsed options
   255  	features     *fs.Features          // optional features
   256  	srv          *rest.Client          // the connection to the one drive server
   257  	dirCache     *dircache.DirCache    // Map of directory path to directory id
   258  	pacer        *fs.Pacer             // pacer for API calls
   259  	tokenRenewer *oauthutil.Renew      // renew the token on expiry
   260  	uploadToken  *pacer.TokenDispenser // control concurrency
   261  }
   262  
   263  // Object describes a box object
   264  //
   265  // Will definitely have info but maybe not meta
   266  type Object struct {
   267  	fs          *Fs       // what this object is part of
   268  	remote      string    // The remote path
   269  	hasMetaData bool      // whether info below has been set
   270  	size        int64     // size of the object
   271  	modTime     time.Time // modification time of the object
   272  	id          string    // ID of the object
   273  	publicLink  string    // Public Link for the object
   274  	sha1        string    // SHA-1 of the object content
   275  }
   276  
   277  // ------------------------------------------------------------
   278  
   279  // Name of the remote (as passed into NewFs)
   280  func (f *Fs) Name() string {
   281  	return f.name
   282  }
   283  
   284  // Root of the remote (as passed into NewFs)
   285  func (f *Fs) Root() string {
   286  	return f.root
   287  }
   288  
   289  // String converts this Fs to a string
   290  func (f *Fs) String() string {
   291  	return fmt.Sprintf("box root '%s'", f.root)
   292  }
   293  
   294  // Features returns the optional features of this Fs
   295  func (f *Fs) Features() *fs.Features {
   296  	return f.features
   297  }
   298  
   299  // parsePath parses a box 'url'
   300  func parsePath(path string) (root string) {
   301  	root = strings.Trim(path, "/")
   302  	return
   303  }
   304  
   305  // retryErrorCodes is a slice of error codes that we will retry
   306  var retryErrorCodes = []int{
   307  	429, // Too Many Requests.
   308  	500, // Internal Server Error
   309  	502, // Bad Gateway
   310  	503, // Service Unavailable
   311  	504, // Gateway Timeout
   312  	509, // Bandwidth Limit Exceeded
   313  }
   314  
   315  // shouldRetry returns a boolean as to whether this resp and err
   316  // deserve to be retried.  It returns the err as a convenience
   317  func shouldRetry(resp *http.Response, err error) (bool, error) {
   318  	authRetry := false
   319  
   320  	if resp != nil && resp.StatusCode == 401 && len(resp.Header["Www-Authenticate"]) == 1 && strings.Index(resp.Header["Www-Authenticate"][0], "expired_token") >= 0 {
   321  		authRetry = true
   322  		fs.Debugf(nil, "Should retry: %v", err)
   323  	}
   324  	return authRetry || fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   325  }
   326  
   327  // readMetaDataForPath reads the metadata from the path
   328  func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.Item, err error) {
   329  	// defer fs.Trace(f, "path=%q", path)("info=%+v, err=%v", &info, &err)
   330  	leaf, directoryID, err := f.dirCache.FindRootAndPath(ctx, path, false)
   331  	if err != nil {
   332  		if err == fs.ErrorDirNotFound {
   333  			return nil, fs.ErrorObjectNotFound
   334  		}
   335  		return nil, err
   336  	}
   337  
   338  	found, err := f.listAll(ctx, directoryID, false, true, func(item *api.Item) bool {
   339  		if item.Name == leaf {
   340  			info = item
   341  			return true
   342  		}
   343  		return false
   344  	})
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	if !found {
   349  		return nil, fs.ErrorObjectNotFound
   350  	}
   351  	return info, nil
   352  }
   353  
   354  // errorHandler parses a non 2xx error response into an error
   355  func errorHandler(resp *http.Response) error {
   356  	// Decode error response
   357  	errResponse := new(api.Error)
   358  	err := rest.DecodeJSON(resp, &errResponse)
   359  	if err != nil {
   360  		fs.Debugf(nil, "Couldn't decode error response: %v", err)
   361  	}
   362  	if errResponse.Code == "" {
   363  		errResponse.Code = resp.Status
   364  	}
   365  	if errResponse.Status == 0 {
   366  		errResponse.Status = resp.StatusCode
   367  	}
   368  	return errResponse
   369  }
   370  
   371  // NewFs constructs an Fs from the path, container:path
   372  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   373  	ctx := context.Background()
   374  	// Parse config into Options struct
   375  	opt := new(Options)
   376  	err := configstruct.Set(m, opt)
   377  	if err != nil {
   378  		return nil, err
   379  	}
   380  
   381  	if opt.UploadCutoff < minUploadCutoff {
   382  		return nil, errors.Errorf("box: upload cutoff (%v) must be greater than equal to %v", opt.UploadCutoff, fs.SizeSuffix(minUploadCutoff))
   383  	}
   384  
   385  	root = parsePath(root)
   386  	oAuthClient, ts, err := oauthutil.NewClient(name, m, oauthConfig)
   387  	if err != nil {
   388  		return nil, errors.Wrap(err, "failed to configure Box")
   389  	}
   390  
   391  	f := &Fs{
   392  		name:        name,
   393  		root:        root,
   394  		opt:         *opt,
   395  		srv:         rest.NewClient(oAuthClient).SetRoot(rootURL),
   396  		pacer:       fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   397  		uploadToken: pacer.NewTokenDispenser(fs.Config.Transfers),
   398  	}
   399  	f.features = (&fs.Features{
   400  		CaseInsensitive:         true,
   401  		CanHaveEmptyDirectories: true,
   402  	}).Fill(f)
   403  	f.srv.SetErrorHandler(errorHandler)
   404  
   405  	jsonFile, ok := m.Get("box_config_file")
   406  	boxSubType, boxSubTypeOk := m.Get("box_sub_type")
   407  
   408  	// If using box config.json and JWT, renewing should just refresh the token and
   409  	// should do so whether there are uploads pending or not.
   410  	if ok && boxSubTypeOk && jsonFile != "" && boxSubType != "" {
   411  		f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
   412  			err := refreshJWTToken(jsonFile, boxSubType, name, m)
   413  			return err
   414  		})
   415  		f.tokenRenewer.Start()
   416  	} else {
   417  		// Renew the token in the background
   418  		f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
   419  			_, err := f.readMetaDataForPath(ctx, "")
   420  			return err
   421  		})
   422  	}
   423  
   424  	// Get rootFolderID
   425  	rootID := f.opt.RootFolderID
   426  	f.dirCache = dircache.New(root, rootID, f)
   427  
   428  	// Find the current root
   429  	err = f.dirCache.FindRoot(ctx, false)
   430  	if err != nil {
   431  		// Assume it is a file
   432  		newRoot, remote := dircache.SplitPath(root)
   433  		tempF := *f
   434  		tempF.dirCache = dircache.New(newRoot, rootID, &tempF)
   435  		tempF.root = newRoot
   436  		// Make new Fs which is the parent
   437  		err = tempF.dirCache.FindRoot(ctx, false)
   438  		if err != nil {
   439  			// No root so return old f
   440  			return f, nil
   441  		}
   442  		_, err := tempF.newObjectWithInfo(ctx, remote, nil)
   443  		if err != nil {
   444  			if err == fs.ErrorObjectNotFound {
   445  				// File doesn't exist so return old f
   446  				return f, nil
   447  			}
   448  			return nil, err
   449  		}
   450  		f.features.Fill(&tempF)
   451  		// XXX: update the old f here instead of returning tempF, since
   452  		// `features` were already filled with functions having *f as a receiver.
   453  		// See https://github.com/rclone/rclone/issues/2182
   454  		f.dirCache = tempF.dirCache
   455  		f.root = tempF.root
   456  		// return an error with an fs which points to the parent
   457  		return f, fs.ErrorIsFile
   458  	}
   459  	return f, nil
   460  }
   461  
   462  // rootSlash returns root with a slash on if it is empty, otherwise empty string
   463  func (f *Fs) rootSlash() string {
   464  	if f.root == "" {
   465  		return f.root
   466  	}
   467  	return f.root + "/"
   468  }
   469  
   470  // Return an Object from a path
   471  //
   472  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   473  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.Item) (fs.Object, error) {
   474  	o := &Object{
   475  		fs:     f,
   476  		remote: remote,
   477  	}
   478  	var err error
   479  	if info != nil {
   480  		// Set info
   481  		err = o.setMetaData(info)
   482  	} else {
   483  		err = o.readMetaData(ctx) // reads info and meta, returning an error
   484  	}
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  	return o, nil
   489  }
   490  
   491  // NewObject finds the Object at remote.  If it can't be found
   492  // it returns the error fs.ErrorObjectNotFound.
   493  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   494  	return f.newObjectWithInfo(ctx, remote, nil)
   495  }
   496  
   497  // FindLeaf finds a directory of name leaf in the folder with ID pathID
   498  func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) {
   499  	// Find the leaf in pathID
   500  	found, err = f.listAll(ctx, pathID, true, false, func(item *api.Item) bool {
   501  		if item.Name == leaf {
   502  			pathIDOut = item.ID
   503  			return true
   504  		}
   505  		return false
   506  	})
   507  	return pathIDOut, found, err
   508  }
   509  
   510  // fieldsValue creates a url.Values with fields set to those in api.Item
   511  func fieldsValue() url.Values {
   512  	values := url.Values{}
   513  	values.Set("fields", api.ItemFields)
   514  	return values
   515  }
   516  
   517  // CreateDir makes a directory with pathID as parent and name leaf
   518  func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
   519  	// fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, leaf)
   520  	var resp *http.Response
   521  	var info *api.Item
   522  	opts := rest.Opts{
   523  		Method:     "POST",
   524  		Path:       "/folders",
   525  		Parameters: fieldsValue(),
   526  	}
   527  	mkdir := api.CreateFolder{
   528  		Name: f.opt.Enc.FromStandardName(leaf),
   529  		Parent: api.Parent{
   530  			ID: pathID,
   531  		},
   532  	}
   533  	err = f.pacer.Call(func() (bool, error) {
   534  		resp, err = f.srv.CallJSON(ctx, &opts, &mkdir, &info)
   535  		return shouldRetry(resp, err)
   536  	})
   537  	if err != nil {
   538  		//fmt.Printf("...Error %v\n", err)
   539  		return "", err
   540  	}
   541  	// fmt.Printf("...Id %q\n", *info.Id)
   542  	return info.ID, nil
   543  }
   544  
   545  // list the objects into the function supplied
   546  //
   547  // If directories is set it only sends directories
   548  // User function to process a File item from listAll
   549  //
   550  // Should return true to finish processing
   551  type listAllFn func(*api.Item) bool
   552  
   553  // Lists the directory required calling the user function on each item found
   554  //
   555  // If the user fn ever returns true then it early exits with found = true
   556  func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) {
   557  	opts := rest.Opts{
   558  		Method:     "GET",
   559  		Path:       "/folders/" + dirID + "/items",
   560  		Parameters: fieldsValue(),
   561  	}
   562  	opts.Parameters.Set("limit", strconv.Itoa(listChunks))
   563  	offset := 0
   564  OUTER:
   565  	for {
   566  		opts.Parameters.Set("offset", strconv.Itoa(offset))
   567  
   568  		var result api.FolderItems
   569  		var resp *http.Response
   570  		err = f.pacer.Call(func() (bool, error) {
   571  			resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
   572  			return shouldRetry(resp, err)
   573  		})
   574  		if err != nil {
   575  			return found, errors.Wrap(err, "couldn't list files")
   576  		}
   577  		for i := range result.Entries {
   578  			item := &result.Entries[i]
   579  			if item.Type == api.ItemTypeFolder {
   580  				if filesOnly {
   581  					continue
   582  				}
   583  			} else if item.Type == api.ItemTypeFile {
   584  				if directoriesOnly {
   585  					continue
   586  				}
   587  			} else {
   588  				fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type)
   589  				continue
   590  			}
   591  			if item.ItemStatus != api.ItemStatusActive {
   592  				continue
   593  			}
   594  			item.Name = f.opt.Enc.ToStandardName(item.Name)
   595  			if fn(item) {
   596  				found = true
   597  				break OUTER
   598  			}
   599  		}
   600  		offset += result.Limit
   601  		if offset >= result.TotalCount {
   602  			break
   603  		}
   604  	}
   605  	return
   606  }
   607  
   608  // List the objects and directories in dir into entries.  The
   609  // entries can be returned in any order but should be for a
   610  // complete directory.
   611  //
   612  // dir should be "" to list the root, and should not have
   613  // trailing slashes.
   614  //
   615  // This should return ErrDirNotFound if the directory isn't
   616  // found.
   617  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   618  	err = f.dirCache.FindRoot(ctx, false)
   619  	if err != nil {
   620  		return nil, err
   621  	}
   622  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  	var iErr error
   627  	_, err = f.listAll(ctx, directoryID, false, false, func(info *api.Item) bool {
   628  		remote := path.Join(dir, info.Name)
   629  		if info.Type == api.ItemTypeFolder {
   630  			// cache the directory ID for later lookups
   631  			f.dirCache.Put(remote, info.ID)
   632  			d := fs.NewDir(remote, info.ModTime()).SetID(info.ID)
   633  			// FIXME more info from dir?
   634  			entries = append(entries, d)
   635  		} else if info.Type == api.ItemTypeFile {
   636  			o, err := f.newObjectWithInfo(ctx, remote, info)
   637  			if err != nil {
   638  				iErr = err
   639  				return true
   640  			}
   641  			entries = append(entries, o)
   642  		}
   643  		return false
   644  	})
   645  	if err != nil {
   646  		return nil, err
   647  	}
   648  	if iErr != nil {
   649  		return nil, iErr
   650  	}
   651  	return entries, nil
   652  }
   653  
   654  // Creates from the parameters passed in a half finished Object which
   655  // must have setMetaData called on it
   656  //
   657  // Returns the object, leaf, directoryID and error
   658  //
   659  // Used to create new objects
   660  func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) {
   661  	// Create the directory for the object if it doesn't exist
   662  	leaf, directoryID, err = f.dirCache.FindRootAndPath(ctx, remote, true)
   663  	if err != nil {
   664  		return
   665  	}
   666  	// Temporary Object under construction
   667  	o = &Object{
   668  		fs:     f,
   669  		remote: remote,
   670  	}
   671  	return o, leaf, directoryID, nil
   672  }
   673  
   674  // Put the object
   675  //
   676  // Copy the reader in to the new object which is returned
   677  //
   678  // The new object may have been created if an error is returned
   679  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   680  	existingObj, err := f.newObjectWithInfo(ctx, src.Remote(), nil)
   681  	switch err {
   682  	case nil:
   683  		return existingObj, existingObj.Update(ctx, in, src, options...)
   684  	case fs.ErrorObjectNotFound:
   685  		// Not found so create it
   686  		return f.PutUnchecked(ctx, in, src)
   687  	default:
   688  		return nil, err
   689  	}
   690  }
   691  
   692  // PutStream uploads to the remote path with the modTime given of indeterminate size
   693  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   694  	return f.Put(ctx, in, src, options...)
   695  }
   696  
   697  // PutUnchecked the object into the container
   698  //
   699  // This will produce an error if the object already exists
   700  //
   701  // Copy the reader in to the new object which is returned
   702  //
   703  // The new object may have been created if an error is returned
   704  func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   705  	remote := src.Remote()
   706  	size := src.Size()
   707  	modTime := src.ModTime(ctx)
   708  
   709  	o, _, _, err := f.createObject(ctx, remote, modTime, size)
   710  	if err != nil {
   711  		return nil, err
   712  	}
   713  	return o, o.Update(ctx, in, src, options...)
   714  }
   715  
   716  // Mkdir creates the container if it doesn't exist
   717  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   718  	err := f.dirCache.FindRoot(ctx, true)
   719  	if err != nil {
   720  		return err
   721  	}
   722  	if dir != "" {
   723  		_, err = f.dirCache.FindDir(ctx, dir, true)
   724  	}
   725  	return err
   726  }
   727  
   728  // deleteObject removes an object by ID
   729  func (f *Fs) deleteObject(ctx context.Context, id string) error {
   730  	opts := rest.Opts{
   731  		Method:     "DELETE",
   732  		Path:       "/files/" + id,
   733  		NoResponse: true,
   734  	}
   735  	return f.pacer.Call(func() (bool, error) {
   736  		resp, err := f.srv.Call(ctx, &opts)
   737  		return shouldRetry(resp, err)
   738  	})
   739  }
   740  
   741  // purgeCheck removes the root directory, if check is set then it
   742  // refuses to do so if it has anything in
   743  func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error {
   744  	root := path.Join(f.root, dir)
   745  	if root == "" {
   746  		return errors.New("can't purge root directory")
   747  	}
   748  	dc := f.dirCache
   749  	err := dc.FindRoot(ctx, false)
   750  	if err != nil {
   751  		return err
   752  	}
   753  	rootID, err := dc.FindDir(ctx, dir, false)
   754  	if err != nil {
   755  		return err
   756  	}
   757  
   758  	opts := rest.Opts{
   759  		Method:     "DELETE",
   760  		Path:       "/folders/" + rootID,
   761  		Parameters: url.Values{},
   762  		NoResponse: true,
   763  	}
   764  	opts.Parameters.Set("recursive", strconv.FormatBool(!check))
   765  	var resp *http.Response
   766  	err = f.pacer.Call(func() (bool, error) {
   767  		resp, err = f.srv.Call(ctx, &opts)
   768  		return shouldRetry(resp, err)
   769  	})
   770  	if err != nil {
   771  		return errors.Wrap(err, "rmdir failed")
   772  	}
   773  	f.dirCache.FlushDir(dir)
   774  	if err != nil {
   775  		return err
   776  	}
   777  	return nil
   778  }
   779  
   780  // Rmdir deletes the root folder
   781  //
   782  // Returns an error if it isn't empty
   783  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   784  	return f.purgeCheck(ctx, dir, true)
   785  }
   786  
   787  // Precision return the precision of this Fs
   788  func (f *Fs) Precision() time.Duration {
   789  	return time.Second
   790  }
   791  
   792  // Copy src to this remote using server side copy operations.
   793  //
   794  // This is stored with the remote path given
   795  //
   796  // It returns the destination Object and a possible error
   797  //
   798  // Will only be called if src.Fs().Name() == f.Name()
   799  //
   800  // If it isn't possible then return fs.ErrorCantCopy
   801  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   802  	srcObj, ok := src.(*Object)
   803  	if !ok {
   804  		fs.Debugf(src, "Can't copy - not same remote type")
   805  		return nil, fs.ErrorCantCopy
   806  	}
   807  	err := srcObj.readMetaData(ctx)
   808  	if err != nil {
   809  		return nil, err
   810  	}
   811  
   812  	srcPath := srcObj.fs.rootSlash() + srcObj.remote
   813  	dstPath := f.rootSlash() + remote
   814  	if strings.ToLower(srcPath) == strings.ToLower(dstPath) {
   815  		return nil, errors.Errorf("can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
   816  	}
   817  
   818  	// Create temporary object
   819  	dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size)
   820  	if err != nil {
   821  		return nil, err
   822  	}
   823  
   824  	// Copy the object
   825  	opts := rest.Opts{
   826  		Method:     "POST",
   827  		Path:       "/files/" + srcObj.id + "/copy",
   828  		Parameters: fieldsValue(),
   829  	}
   830  	copyFile := api.CopyFile{
   831  		Name: f.opt.Enc.FromStandardName(leaf),
   832  		Parent: api.Parent{
   833  			ID: directoryID,
   834  		},
   835  	}
   836  	var resp *http.Response
   837  	var info *api.Item
   838  	err = f.pacer.Call(func() (bool, error) {
   839  		resp, err = f.srv.CallJSON(ctx, &opts, &copyFile, &info)
   840  		return shouldRetry(resp, err)
   841  	})
   842  	if err != nil {
   843  		return nil, err
   844  	}
   845  	err = dstObj.setMetaData(info)
   846  	if err != nil {
   847  		return nil, err
   848  	}
   849  	return dstObj, nil
   850  }
   851  
   852  // Purge deletes all the files and the container
   853  //
   854  // Optional interface: Only implement this if you have a way of
   855  // deleting all the files quicker than just running Remove() on the
   856  // result of List()
   857  func (f *Fs) Purge(ctx context.Context) error {
   858  	return f.purgeCheck(ctx, "", false)
   859  }
   860  
   861  // move a file or folder
   862  func (f *Fs) move(ctx context.Context, endpoint, id, leaf, directoryID string) (info *api.Item, err error) {
   863  	// Move the object
   864  	opts := rest.Opts{
   865  		Method:     "PUT",
   866  		Path:       endpoint + id,
   867  		Parameters: fieldsValue(),
   868  	}
   869  	move := api.UpdateFileMove{
   870  		Name: f.opt.Enc.FromStandardName(leaf),
   871  		Parent: api.Parent{
   872  			ID: directoryID,
   873  		},
   874  	}
   875  	var resp *http.Response
   876  	err = f.pacer.Call(func() (bool, error) {
   877  		resp, err = f.srv.CallJSON(ctx, &opts, &move, &info)
   878  		return shouldRetry(resp, err)
   879  	})
   880  	if err != nil {
   881  		return nil, err
   882  	}
   883  	return info, nil
   884  }
   885  
   886  // About gets quota information
   887  func (f *Fs) About(ctx context.Context) (usage *fs.Usage, err error) {
   888  	opts := rest.Opts{
   889  		Method: "GET",
   890  		Path:   "/users/me",
   891  	}
   892  	var user api.User
   893  	var resp *http.Response
   894  	err = f.pacer.Call(func() (bool, error) {
   895  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &user)
   896  		return shouldRetry(resp, err)
   897  	})
   898  	if err != nil {
   899  		return nil, errors.Wrap(err, "failed to read user info")
   900  	}
   901  	// FIXME max upload size would be useful to use in Update
   902  	usage = &fs.Usage{
   903  		Used:  fs.NewUsageValue(user.SpaceUsed),                    // bytes in use
   904  		Total: fs.NewUsageValue(user.SpaceAmount),                  // bytes total
   905  		Free:  fs.NewUsageValue(user.SpaceAmount - user.SpaceUsed), // bytes free
   906  	}
   907  	return usage, nil
   908  }
   909  
   910  // Move src to this remote using server side move operations.
   911  //
   912  // This is stored with the remote path given
   913  //
   914  // It returns the destination Object and a possible error
   915  //
   916  // Will only be called if src.Fs().Name() == f.Name()
   917  //
   918  // If it isn't possible then return fs.ErrorCantMove
   919  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   920  	srcObj, ok := src.(*Object)
   921  	if !ok {
   922  		fs.Debugf(src, "Can't move - not same remote type")
   923  		return nil, fs.ErrorCantMove
   924  	}
   925  
   926  	// Create temporary object
   927  	dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size)
   928  	if err != nil {
   929  		return nil, err
   930  	}
   931  
   932  	// Do the move
   933  	info, err := f.move(ctx, "/files/", srcObj.id, leaf, directoryID)
   934  	if err != nil {
   935  		return nil, err
   936  	}
   937  
   938  	err = dstObj.setMetaData(info)
   939  	if err != nil {
   940  		return nil, err
   941  	}
   942  	return dstObj, nil
   943  }
   944  
   945  // DirMove moves src, srcRemote to this remote at dstRemote
   946  // using server side move operations.
   947  //
   948  // Will only be called if src.Fs().Name() == f.Name()
   949  //
   950  // If it isn't possible then return fs.ErrorCantDirMove
   951  //
   952  // If destination exists then return fs.ErrorDirExists
   953  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   954  	srcFs, ok := src.(*Fs)
   955  	if !ok {
   956  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   957  		return fs.ErrorCantDirMove
   958  	}
   959  	srcPath := path.Join(srcFs.root, srcRemote)
   960  	dstPath := path.Join(f.root, dstRemote)
   961  
   962  	// Refuse to move to or from the root
   963  	if srcPath == "" || dstPath == "" {
   964  		fs.Debugf(src, "DirMove error: Can't move root")
   965  		return errors.New("can't move root directory")
   966  	}
   967  
   968  	// find the root src directory
   969  	err := srcFs.dirCache.FindRoot(ctx, false)
   970  	if err != nil {
   971  		return err
   972  	}
   973  
   974  	// find the root dst directory
   975  	if dstRemote != "" {
   976  		err = f.dirCache.FindRoot(ctx, true)
   977  		if err != nil {
   978  			return err
   979  		}
   980  	} else {
   981  		if f.dirCache.FoundRoot() {
   982  			return fs.ErrorDirExists
   983  		}
   984  	}
   985  
   986  	// Find ID of dst parent, creating subdirs if necessary
   987  	var leaf, directoryID string
   988  	findPath := dstRemote
   989  	if dstRemote == "" {
   990  		findPath = f.root
   991  	}
   992  	leaf, directoryID, err = f.dirCache.FindPath(ctx, findPath, true)
   993  	if err != nil {
   994  		return err
   995  	}
   996  
   997  	// Check destination does not exist
   998  	if dstRemote != "" {
   999  		_, err = f.dirCache.FindDir(ctx, dstRemote, false)
  1000  		if err == fs.ErrorDirNotFound {
  1001  			// OK
  1002  		} else if err != nil {
  1003  			return err
  1004  		} else {
  1005  			return fs.ErrorDirExists
  1006  		}
  1007  	}
  1008  
  1009  	// Find ID of src
  1010  	srcID, err := srcFs.dirCache.FindDir(ctx, srcRemote, false)
  1011  	if err != nil {
  1012  		return err
  1013  	}
  1014  
  1015  	// Do the move
  1016  	_, err = f.move(ctx, "/folders/", srcID, leaf, directoryID)
  1017  	if err != nil {
  1018  		return err
  1019  	}
  1020  	srcFs.dirCache.FlushDir(srcRemote)
  1021  	return nil
  1022  }
  1023  
  1024  // PublicLink adds a "readable by anyone with link" permission on the given file or folder.
  1025  func (f *Fs) PublicLink(ctx context.Context, remote string) (string, error) {
  1026  	id, err := f.dirCache.FindDir(ctx, remote, false)
  1027  	var opts rest.Opts
  1028  	if err == nil {
  1029  		fs.Debugf(f, "attempting to share directory '%s'", remote)
  1030  
  1031  		opts = rest.Opts{
  1032  			Method:     "PUT",
  1033  			Path:       "/folders/" + id,
  1034  			Parameters: fieldsValue(),
  1035  		}
  1036  	} else {
  1037  		fs.Debugf(f, "attempting to share single file '%s'", remote)
  1038  		o, err := f.NewObject(ctx, remote)
  1039  		if err != nil {
  1040  			return "", err
  1041  		}
  1042  
  1043  		if o.(*Object).publicLink != "" {
  1044  			return o.(*Object).publicLink, nil
  1045  		}
  1046  
  1047  		opts = rest.Opts{
  1048  			Method:     "PUT",
  1049  			Path:       "/files/" + o.(*Object).id,
  1050  			Parameters: fieldsValue(),
  1051  		}
  1052  	}
  1053  
  1054  	shareLink := api.CreateSharedLink{}
  1055  	var info api.Item
  1056  	var resp *http.Response
  1057  	err = f.pacer.Call(func() (bool, error) {
  1058  		resp, err = f.srv.CallJSON(ctx, &opts, &shareLink, &info)
  1059  		return shouldRetry(resp, err)
  1060  	})
  1061  	return info.SharedLink.URL, err
  1062  }
  1063  
  1064  // DirCacheFlush resets the directory cache - used in testing as an
  1065  // optional interface
  1066  func (f *Fs) DirCacheFlush() {
  1067  	f.dirCache.ResetRoot()
  1068  }
  1069  
  1070  // Hashes returns the supported hash sets.
  1071  func (f *Fs) Hashes() hash.Set {
  1072  	return hash.Set(hash.SHA1)
  1073  }
  1074  
  1075  // ------------------------------------------------------------
  1076  
  1077  // Fs returns the parent Fs
  1078  func (o *Object) Fs() fs.Info {
  1079  	return o.fs
  1080  }
  1081  
  1082  // Return a string version
  1083  func (o *Object) String() string {
  1084  	if o == nil {
  1085  		return "<nil>"
  1086  	}
  1087  	return o.remote
  1088  }
  1089  
  1090  // Remote returns the remote path
  1091  func (o *Object) Remote() string {
  1092  	return o.remote
  1093  }
  1094  
  1095  // Hash returns the SHA-1 of an object returning a lowercase hex string
  1096  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
  1097  	if t != hash.SHA1 {
  1098  		return "", hash.ErrUnsupported
  1099  	}
  1100  	return o.sha1, nil
  1101  }
  1102  
  1103  // Size returns the size of an object in bytes
  1104  func (o *Object) Size() int64 {
  1105  	err := o.readMetaData(context.TODO())
  1106  	if err != nil {
  1107  		fs.Logf(o, "Failed to read metadata: %v", err)
  1108  		return 0
  1109  	}
  1110  	return o.size
  1111  }
  1112  
  1113  // setMetaData sets the metadata from info
  1114  func (o *Object) setMetaData(info *api.Item) (err error) {
  1115  	if info.Type != api.ItemTypeFile {
  1116  		return errors.Wrapf(fs.ErrorNotAFile, "%q is %q", o.remote, info.Type)
  1117  	}
  1118  	o.hasMetaData = true
  1119  	o.size = int64(info.Size)
  1120  	o.sha1 = info.SHA1
  1121  	o.modTime = info.ModTime()
  1122  	o.id = info.ID
  1123  	o.publicLink = info.SharedLink.URL
  1124  	return nil
  1125  }
  1126  
  1127  // readMetaData gets the metadata if it hasn't already been fetched
  1128  //
  1129  // it also sets the info
  1130  func (o *Object) readMetaData(ctx context.Context) (err error) {
  1131  	if o.hasMetaData {
  1132  		return nil
  1133  	}
  1134  	info, err := o.fs.readMetaDataForPath(ctx, o.remote)
  1135  	if err != nil {
  1136  		if apiErr, ok := err.(*api.Error); ok {
  1137  			if apiErr.Code == "not_found" || apiErr.Code == "trashed" {
  1138  				return fs.ErrorObjectNotFound
  1139  			}
  1140  		}
  1141  		return err
  1142  	}
  1143  	return o.setMetaData(info)
  1144  }
  1145  
  1146  // ModTime returns the modification time of the object
  1147  //
  1148  //
  1149  // It attempts to read the objects mtime and if that isn't present the
  1150  // LastModified returned in the http headers
  1151  func (o *Object) ModTime(ctx context.Context) time.Time {
  1152  	err := o.readMetaData(ctx)
  1153  	if err != nil {
  1154  		fs.Logf(o, "Failed to read metadata: %v", err)
  1155  		return time.Now()
  1156  	}
  1157  	return o.modTime
  1158  }
  1159  
  1160  // setModTime sets the modification time of the local fs object
  1161  func (o *Object) setModTime(ctx context.Context, modTime time.Time) (*api.Item, error) {
  1162  	opts := rest.Opts{
  1163  		Method:     "PUT",
  1164  		Path:       "/files/" + o.id,
  1165  		Parameters: fieldsValue(),
  1166  	}
  1167  	update := api.UpdateFileModTime{
  1168  		ContentModifiedAt: api.Time(modTime),
  1169  	}
  1170  	var info *api.Item
  1171  	err := o.fs.pacer.Call(func() (bool, error) {
  1172  		resp, err := o.fs.srv.CallJSON(ctx, &opts, &update, &info)
  1173  		return shouldRetry(resp, err)
  1174  	})
  1175  	return info, err
  1176  }
  1177  
  1178  // SetModTime sets the modification time of the local fs object
  1179  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
  1180  	info, err := o.setModTime(ctx, modTime)
  1181  	if err != nil {
  1182  		return err
  1183  	}
  1184  	return o.setMetaData(info)
  1185  }
  1186  
  1187  // Storable returns a boolean showing whether this object storable
  1188  func (o *Object) Storable() bool {
  1189  	return true
  1190  }
  1191  
  1192  // Open an object for read
  1193  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  1194  	if o.id == "" {
  1195  		return nil, errors.New("can't download - no id")
  1196  	}
  1197  	fs.FixRangeOption(options, o.size)
  1198  	var resp *http.Response
  1199  	opts := rest.Opts{
  1200  		Method:  "GET",
  1201  		Path:    "/files/" + o.id + "/content",
  1202  		Options: options,
  1203  	}
  1204  	err = o.fs.pacer.Call(func() (bool, error) {
  1205  		resp, err = o.fs.srv.Call(ctx, &opts)
  1206  		return shouldRetry(resp, err)
  1207  	})
  1208  	if err != nil {
  1209  		return nil, err
  1210  	}
  1211  	return resp.Body, err
  1212  }
  1213  
  1214  // upload does a single non-multipart upload
  1215  //
  1216  // This is recommended for less than 50 MB of content
  1217  func (o *Object) upload(ctx context.Context, in io.Reader, leaf, directoryID string, modTime time.Time, options ...fs.OpenOption) (err error) {
  1218  	upload := api.UploadFile{
  1219  		Name:              o.fs.opt.Enc.FromStandardName(leaf),
  1220  		ContentModifiedAt: api.Time(modTime),
  1221  		ContentCreatedAt:  api.Time(modTime),
  1222  		Parent: api.Parent{
  1223  			ID: directoryID,
  1224  		},
  1225  	}
  1226  
  1227  	var resp *http.Response
  1228  	var result api.FolderItems
  1229  	opts := rest.Opts{
  1230  		Method:                "POST",
  1231  		Body:                  in,
  1232  		MultipartMetadataName: "attributes",
  1233  		MultipartContentName:  "contents",
  1234  		MultipartFileName:     upload.Name,
  1235  		RootURL:               uploadURL,
  1236  		Options:               options,
  1237  	}
  1238  	// If object has an ID then it is existing so create a new version
  1239  	if o.id != "" {
  1240  		opts.Path = "/files/" + o.id + "/content"
  1241  	} else {
  1242  		opts.Path = "/files/content"
  1243  	}
  1244  	err = o.fs.pacer.CallNoRetry(func() (bool, error) {
  1245  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &upload, &result)
  1246  		return shouldRetry(resp, err)
  1247  	})
  1248  	if err != nil {
  1249  		return err
  1250  	}
  1251  	if result.TotalCount != 1 || len(result.Entries) != 1 {
  1252  		return errors.Errorf("failed to upload %v - not sure why", o)
  1253  	}
  1254  	return o.setMetaData(&result.Entries[0])
  1255  }
  1256  
  1257  // Update the object with the contents of the io.Reader, modTime and size
  1258  //
  1259  // If existing is set then it updates the object rather than creating a new one
  1260  //
  1261  // The new object may have been created if an error is returned
  1262  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
  1263  	o.fs.tokenRenewer.Start()
  1264  	defer o.fs.tokenRenewer.Stop()
  1265  
  1266  	size := src.Size()
  1267  	modTime := src.ModTime(ctx)
  1268  	remote := o.Remote()
  1269  
  1270  	// Create the directory for the object if it doesn't exist
  1271  	leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, remote, true)
  1272  	if err != nil {
  1273  		return err
  1274  	}
  1275  
  1276  	// Upload with simple or multipart
  1277  	if size <= int64(o.fs.opt.UploadCutoff) {
  1278  		err = o.upload(ctx, in, leaf, directoryID, modTime, options...)
  1279  	} else {
  1280  		err = o.uploadMultipart(ctx, in, leaf, directoryID, size, modTime, options...)
  1281  	}
  1282  	return err
  1283  }
  1284  
  1285  // Remove an object
  1286  func (o *Object) Remove(ctx context.Context) error {
  1287  	return o.fs.deleteObject(ctx, o.id)
  1288  }
  1289  
  1290  // ID returns the ID of the Object if known, or "" if not
  1291  func (o *Object) ID() string {
  1292  	return o.id
  1293  }
  1294  
  1295  // Check the interfaces are satisfied
  1296  var (
  1297  	_ fs.Fs              = (*Fs)(nil)
  1298  	_ fs.Purger          = (*Fs)(nil)
  1299  	_ fs.PutStreamer     = (*Fs)(nil)
  1300  	_ fs.Copier          = (*Fs)(nil)
  1301  	_ fs.Abouter         = (*Fs)(nil)
  1302  	_ fs.Mover           = (*Fs)(nil)
  1303  	_ fs.DirMover        = (*Fs)(nil)
  1304  	_ fs.DirCacheFlusher = (*Fs)(nil)
  1305  	_ fs.PublicLinker    = (*Fs)(nil)
  1306  	_ fs.Object          = (*Object)(nil)
  1307  	_ fs.IDer            = (*Object)(nil)
  1308  )