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