github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/pcloud/pcloud.go (about)

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