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