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

     1  package yandex
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"net/http"
    10  	"net/url"
    11  	"path"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/pkg/errors"
    17  	"github.com/rclone/rclone/backend/yandex/api"
    18  	"github.com/rclone/rclone/fs"
    19  	"github.com/rclone/rclone/fs/config"
    20  	"github.com/rclone/rclone/fs/config/configmap"
    21  	"github.com/rclone/rclone/fs/config/configstruct"
    22  	"github.com/rclone/rclone/fs/config/obscure"
    23  	"github.com/rclone/rclone/fs/fserrors"
    24  	"github.com/rclone/rclone/fs/hash"
    25  	"github.com/rclone/rclone/lib/encoder"
    26  	"github.com/rclone/rclone/lib/oauthutil"
    27  	"github.com/rclone/rclone/lib/pacer"
    28  	"github.com/rclone/rclone/lib/readers"
    29  	"github.com/rclone/rclone/lib/rest"
    30  	"golang.org/x/oauth2"
    31  )
    32  
    33  //oAuth
    34  const (
    35  	rcloneClientID              = "ac39b43b9eba4cae8ffb788c06d816a8"
    36  	rcloneEncryptedClientSecret = "EfyyNZ3YUEwXM5yAhi72G9YwKn2mkFrYwJNS7cY0TJAhFlX9K-uJFbGlpO-RYjrJ"
    37  	rootURL                     = "https://cloud-api.yandex.com/v1/disk"
    38  	minSleep                    = 10 * time.Millisecond
    39  	maxSleep                    = 2 * time.Second // may needs to be increased, testing needed
    40  	decayConstant               = 2               // bigger for slower decay, exponential
    41  )
    42  
    43  // Globals
    44  var (
    45  	// Description of how to auth for this app
    46  	oauthConfig = &oauth2.Config{
    47  		Endpoint: oauth2.Endpoint{
    48  			AuthURL:  "https://oauth.yandex.com/authorize", //same as https://oauth.yandex.ru/authorize
    49  			TokenURL: "https://oauth.yandex.com/token",     //same as https://oauth.yandex.ru/token
    50  		},
    51  		ClientID:     rcloneClientID,
    52  		ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
    53  		RedirectURL:  oauthutil.RedirectURL,
    54  	}
    55  )
    56  
    57  // Register with Fs
    58  func init() {
    59  	fs.Register(&fs.RegInfo{
    60  		Name:        "yandex",
    61  		Description: "Yandex Disk",
    62  		NewFs:       NewFs,
    63  		Config: func(name string, m configmap.Mapper) {
    64  			err := oauthutil.Config("yandex", name, m, oauthConfig, nil)
    65  			if err != nil {
    66  				log.Fatalf("Failed to configure token: %v", err)
    67  				return
    68  			}
    69  		},
    70  		Options: []fs.Option{{
    71  			Name: config.ConfigClientID,
    72  			Help: "Yandex Client Id\nLeave blank normally.",
    73  		}, {
    74  			Name: config.ConfigClientSecret,
    75  			Help: "Yandex Client Secret\nLeave blank normally.",
    76  		}, {
    77  			Name:     "unlink",
    78  			Help:     "Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.",
    79  			Default:  false,
    80  			Advanced: true,
    81  		}, {
    82  			Name:     config.ConfigEncoding,
    83  			Help:     config.ConfigEncodingHelp,
    84  			Advanced: true,
    85  			// Of the control characters \t \n \r are allowed
    86  			// it doesn't seem worth making an exception for this
    87  			Default: (encoder.Display |
    88  				encoder.EncodeInvalidUtf8),
    89  		}},
    90  	})
    91  }
    92  
    93  // Options defines the configuration for this backend
    94  type Options struct {
    95  	Token  string               `config:"token"`
    96  	Unlink bool                 `config:"unlink"`
    97  	Enc    encoder.MultiEncoder `config:"encoding"`
    98  }
    99  
   100  // Fs represents a remote yandex
   101  type Fs struct {
   102  	name     string
   103  	root     string       // root path
   104  	opt      Options      // parsed options
   105  	features *fs.Features // optional features
   106  	srv      *rest.Client // the connection to the yandex server
   107  	pacer    *fs.Pacer    // pacer for API calls
   108  	diskRoot string       // root path with "disk:/" container name
   109  }
   110  
   111  // Object describes a swift object
   112  type Object struct {
   113  	fs          *Fs       // what this object is part of
   114  	remote      string    // The remote path
   115  	hasMetaData bool      // whether info below has been set
   116  	md5sum      string    // The MD5Sum of the object
   117  	size        int64     // Bytes in the object
   118  	modTime     time.Time // Modified time of the object
   119  	mimeType    string    // Content type according to the server
   120  
   121  }
   122  
   123  // ------------------------------------------------------------
   124  
   125  // Name of the remote (as passed into NewFs)
   126  func (f *Fs) Name() string {
   127  	return f.name
   128  }
   129  
   130  // Root of the remote (as passed into NewFs)
   131  func (f *Fs) Root() string {
   132  	return f.root
   133  }
   134  
   135  // String converts this Fs to a string
   136  func (f *Fs) String() string {
   137  	return fmt.Sprintf("Yandex %s", f.root)
   138  }
   139  
   140  // Precision return the precision of this Fs
   141  func (f *Fs) Precision() time.Duration {
   142  	return time.Nanosecond
   143  }
   144  
   145  // Hashes returns the supported hash sets.
   146  func (f *Fs) Hashes() hash.Set {
   147  	return hash.Set(hash.MD5)
   148  }
   149  
   150  // Features returns the optional features of this Fs
   151  func (f *Fs) Features() *fs.Features {
   152  	return f.features
   153  }
   154  
   155  // retryErrorCodes is a slice of error codes that we will retry
   156  var retryErrorCodes = []int{
   157  	429, // Too Many Requests.
   158  	500, // Internal Server Error
   159  	502, // Bad Gateway
   160  	503, // Service Unavailable
   161  	504, // Gateway Timeout
   162  	509, // Bandwidth Limit Exceeded
   163  }
   164  
   165  // shouldRetry returns a boolean as to whether this resp and err
   166  // deserve to be retried.  It returns the err as a convenience
   167  func shouldRetry(resp *http.Response, err error) (bool, error) {
   168  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   169  }
   170  
   171  // errorHandler parses a non 2xx error response into an error
   172  func errorHandler(resp *http.Response) error {
   173  	// Decode error response
   174  	errResponse := new(api.ErrorResponse)
   175  	err := rest.DecodeJSON(resp, &errResponse)
   176  	if err != nil {
   177  		fs.Debugf(nil, "Couldn't decode error response: %v", err)
   178  	}
   179  	if errResponse.Message == "" {
   180  		errResponse.Message = resp.Status
   181  	}
   182  	if errResponse.StatusCode == 0 {
   183  		errResponse.StatusCode = resp.StatusCode
   184  	}
   185  	return errResponse
   186  }
   187  
   188  // Sets root in f
   189  func (f *Fs) setRoot(root string) {
   190  	//Set root path
   191  	f.root = strings.Trim(root, "/")
   192  	//Set disk root path.
   193  	//Adding "disk:" to root path as all paths on disk start with it
   194  	var diskRoot string
   195  	if f.root == "" {
   196  		diskRoot = "disk:/"
   197  	} else {
   198  		diskRoot = "disk:/" + f.root + "/"
   199  	}
   200  	f.diskRoot = diskRoot
   201  }
   202  
   203  // filePath returns an escaped file path (f.root, file)
   204  func (f *Fs) filePath(file string) string {
   205  	return path.Join(f.diskRoot, file)
   206  }
   207  
   208  // dirPath returns an escaped file path (f.root, file) ending with '/'
   209  func (f *Fs) dirPath(file string) string {
   210  	return path.Join(f.diskRoot, file) + "/"
   211  }
   212  
   213  func (f *Fs) readMetaDataForPath(ctx context.Context, path string, options *api.ResourceInfoRequestOptions) (*api.ResourceInfoResponse, error) {
   214  	opts := rest.Opts{
   215  		Method:     "GET",
   216  		Path:       "/resources",
   217  		Parameters: url.Values{},
   218  	}
   219  
   220  	opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
   221  
   222  	if options.SortMode != nil {
   223  		opts.Parameters.Set("sort", options.SortMode.String())
   224  	}
   225  	if options.Limit != 0 {
   226  		opts.Parameters.Set("limit", strconv.FormatUint(options.Limit, 10))
   227  	}
   228  	if options.Offset != 0 {
   229  		opts.Parameters.Set("offset", strconv.FormatUint(options.Offset, 10))
   230  	}
   231  	if options.Fields != nil {
   232  		opts.Parameters.Set("fields", strings.Join(options.Fields, ","))
   233  	}
   234  
   235  	var err error
   236  	var info api.ResourceInfoResponse
   237  	var resp *http.Response
   238  	err = f.pacer.Call(func() (bool, error) {
   239  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &info)
   240  		return shouldRetry(resp, err)
   241  	})
   242  
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	info.Name = f.opt.Enc.ToStandardName(info.Name)
   248  	return &info, nil
   249  }
   250  
   251  // NewFs constructs an Fs from the path, container:path
   252  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   253  	ctx := context.TODO()
   254  	// Parse config into Options struct
   255  	opt := new(Options)
   256  	err := configstruct.Set(m, opt)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	token, err := oauthutil.GetToken(name, m)
   262  	if err != nil {
   263  		log.Fatalf("Couldn't read OAuth token (this should never happen).")
   264  	}
   265  	if token.RefreshToken == "" {
   266  		log.Fatalf("Unable to get RefreshToken. If you are upgrading from older versions of rclone, please run `rclone config` and re-configure this backend.")
   267  	}
   268  	if token.TokenType != "OAuth" {
   269  		token.TokenType = "OAuth"
   270  		err = oauthutil.PutToken(name, m, token, false)
   271  		if err != nil {
   272  			log.Fatalf("Couldn't save OAuth token (this should never happen).")
   273  		}
   274  		log.Printf("Automatically upgraded OAuth config.")
   275  	}
   276  	oAuthClient, _, err := oauthutil.NewClient(name, m, oauthConfig)
   277  	if err != nil {
   278  		log.Fatalf("Failed to configure Yandex: %v", err)
   279  	}
   280  
   281  	f := &Fs{
   282  		name:  name,
   283  		opt:   *opt,
   284  		srv:   rest.NewClient(oAuthClient).SetRoot(rootURL),
   285  		pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   286  	}
   287  	f.setRoot(root)
   288  	f.features = (&fs.Features{
   289  		ReadMimeType:            true,
   290  		WriteMimeType:           true,
   291  		CanHaveEmptyDirectories: true,
   292  	}).Fill(f)
   293  	f.srv.SetErrorHandler(errorHandler)
   294  
   295  	// Check to see if the object exists and is a file
   296  	//request object meta info
   297  	// Check to see if the object exists and is a file
   298  	//request object meta info
   299  	if info, err := f.readMetaDataForPath(ctx, f.diskRoot, &api.ResourceInfoRequestOptions{}); err != nil {
   300  
   301  	} else {
   302  		if info.ResourceType == "file" {
   303  			rootDir := path.Dir(root)
   304  			if rootDir == "." {
   305  				rootDir = ""
   306  			}
   307  			f.setRoot(rootDir)
   308  			// return an error with an fs which points to the parent
   309  			return f, fs.ErrorIsFile
   310  		}
   311  	}
   312  	return f, nil
   313  }
   314  
   315  // Convert a list item into a DirEntry
   316  func (f *Fs) itemToDirEntry(ctx context.Context, remote string, object *api.ResourceInfoResponse) (fs.DirEntry, error) {
   317  	switch object.ResourceType {
   318  	case "dir":
   319  		t, err := time.Parse(time.RFC3339Nano, object.Modified)
   320  		if err != nil {
   321  			return nil, errors.Wrap(err, "error parsing time in directory item")
   322  		}
   323  		d := fs.NewDir(remote, t).SetSize(object.Size)
   324  		return d, nil
   325  	case "file":
   326  		o, err := f.newObjectWithInfo(ctx, remote, object)
   327  		if err != nil {
   328  			return nil, err
   329  		}
   330  		return o, nil
   331  	default:
   332  		fs.Debugf(f, "Unknown resource type %q", object.ResourceType)
   333  	}
   334  	return nil, nil
   335  }
   336  
   337  // List the objects and directories in dir into entries.  The
   338  // entries can be returned in any order but should be for a
   339  // complete directory.
   340  //
   341  // dir should be "" to list the root, and should not have
   342  // trailing slashes.
   343  //
   344  // This should return ErrDirNotFound if the directory isn't
   345  // found.
   346  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   347  	root := f.dirPath(dir)
   348  
   349  	var limit uint64 = 1000 // max number of objects per request
   350  	var itemsCount uint64   // number of items per page in response
   351  	var offset uint64       // for the next page of requests
   352  
   353  	for {
   354  		opts := &api.ResourceInfoRequestOptions{
   355  			Limit:  limit,
   356  			Offset: offset,
   357  		}
   358  		info, err := f.readMetaDataForPath(ctx, root, opts)
   359  
   360  		if err != nil {
   361  			if apiErr, ok := err.(*api.ErrorResponse); ok {
   362  				// does not exist
   363  				if apiErr.ErrorName == "DiskNotFoundError" {
   364  					return nil, fs.ErrorDirNotFound
   365  				}
   366  			}
   367  			return nil, err
   368  		}
   369  		itemsCount = uint64(len(info.Embedded.Items))
   370  
   371  		if info.ResourceType == "dir" {
   372  			//list all subdirs
   373  			for _, element := range info.Embedded.Items {
   374  				element.Name = f.opt.Enc.ToStandardName(element.Name)
   375  				remote := path.Join(dir, element.Name)
   376  				entry, err := f.itemToDirEntry(ctx, remote, &element)
   377  				if err != nil {
   378  					return nil, err
   379  				}
   380  				if entry != nil {
   381  					entries = append(entries, entry)
   382  				}
   383  			}
   384  		} else if info.ResourceType == "file" {
   385  			return nil, fs.ErrorIsFile
   386  		}
   387  
   388  		//offset for the next page of items
   389  		offset += itemsCount
   390  		//check if we reached end of list
   391  		if itemsCount < limit {
   392  			break
   393  		}
   394  	}
   395  
   396  	return entries, nil
   397  }
   398  
   399  // Return an Object from a path
   400  //
   401  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   402  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.ResourceInfoResponse) (fs.Object, error) {
   403  	o := &Object{
   404  		fs:     f,
   405  		remote: remote,
   406  	}
   407  	var err error
   408  	if info != nil {
   409  		err = o.setMetaData(info)
   410  	} else {
   411  		err = o.readMetaData(ctx)
   412  		if apiErr, ok := err.(*api.ErrorResponse); ok {
   413  			// does not exist
   414  			if apiErr.ErrorName == "DiskNotFoundError" {
   415  				return nil, fs.ErrorObjectNotFound
   416  			}
   417  		}
   418  	}
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	return o, nil
   423  }
   424  
   425  // NewObject finds the Object at remote.  If it can't be found it
   426  // returns the error fs.ErrorObjectNotFound.
   427  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   428  	return f.newObjectWithInfo(ctx, remote, nil)
   429  }
   430  
   431  // Creates from the parameters passed in a half finished Object which
   432  // must have setMetaData called on it
   433  //
   434  // Used to create new objects
   435  func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Object) {
   436  	// Temporary Object under construction
   437  	o = &Object{
   438  		fs:      f,
   439  		remote:  remote,
   440  		size:    size,
   441  		modTime: modTime,
   442  	}
   443  	return o
   444  }
   445  
   446  // Put the object
   447  //
   448  // Copy the reader in to the new object which is returned
   449  //
   450  // The new object may have been created if an error is returned
   451  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   452  	o := f.createObject(src.Remote(), src.ModTime(ctx), src.Size())
   453  	return o, o.Update(ctx, in, src, options...)
   454  }
   455  
   456  // PutStream uploads to the remote path with the modTime given of indeterminate size
   457  func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   458  	return f.Put(ctx, in, src, options...)
   459  }
   460  
   461  // CreateDir makes a directory
   462  func (f *Fs) CreateDir(ctx context.Context, path string) (err error) {
   463  	//fmt.Printf("CreateDir: %s\n", path)
   464  
   465  	var resp *http.Response
   466  	opts := rest.Opts{
   467  		Method:     "PUT",
   468  		Path:       "/resources",
   469  		Parameters: url.Values{},
   470  		NoResponse: true,
   471  	}
   472  
   473  	// If creating a directory with a : use (undocumented) disk: prefix
   474  	if strings.IndexRune(path, ':') >= 0 {
   475  		path = "disk:" + path
   476  	}
   477  	opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
   478  
   479  	err = f.pacer.Call(func() (bool, error) {
   480  		resp, err = f.srv.Call(ctx, &opts)
   481  		return shouldRetry(resp, err)
   482  	})
   483  	if err != nil {
   484  		// fmt.Printf("CreateDir %q Error: %s\n", path, err.Error())
   485  		return err
   486  	}
   487  	// fmt.Printf("...Id %q\n", *info.Id)
   488  	return nil
   489  }
   490  
   491  // This really needs improvement and especially proper error checking
   492  // but Yandex does not publish a List of possible errors and when they're
   493  // expected to occur.
   494  func (f *Fs) mkDirs(ctx context.Context, path string) (err error) {
   495  	//trim filename from path
   496  	//dirString := strings.TrimSuffix(path, filepath.Base(path))
   497  	//trim "disk:" from path
   498  	dirString := strings.TrimPrefix(path, "disk:")
   499  	if dirString == "" {
   500  		return nil
   501  	}
   502  
   503  	if err = f.CreateDir(ctx, dirString); err != nil {
   504  		if apiErr, ok := err.(*api.ErrorResponse); ok {
   505  			// already exists
   506  			if apiErr.ErrorName != "DiskPathPointsToExistentDirectoryError" {
   507  				// 2 if it fails then create all directories in the path from root.
   508  				dirs := strings.Split(dirString, "/") //path separator
   509  				var mkdirpath = "/"                   //path separator /
   510  				for _, element := range dirs {
   511  					if element != "" {
   512  						mkdirpath += element + "/" //path separator /
   513  						if err = f.CreateDir(ctx, mkdirpath); err != nil {
   514  							// ignore errors while creating dirs
   515  						}
   516  					}
   517  				}
   518  			}
   519  			return nil
   520  		}
   521  	}
   522  	return err
   523  }
   524  
   525  func (f *Fs) mkParentDirs(ctx context.Context, resPath string) error {
   526  	// defer log.Trace(dirPath, "")("")
   527  	// chop off trailing / if it exists
   528  	if strings.HasSuffix(resPath, "/") {
   529  		resPath = resPath[:len(resPath)-1]
   530  	}
   531  	parent := path.Dir(resPath)
   532  	if parent == "." {
   533  		parent = ""
   534  	}
   535  	return f.mkDirs(ctx, parent)
   536  }
   537  
   538  // Mkdir creates the container if it doesn't exist
   539  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   540  	path := f.filePath(dir)
   541  	return f.mkDirs(ctx, path)
   542  }
   543  
   544  // waitForJob waits for the job with status in url to complete
   545  func (f *Fs) waitForJob(ctx context.Context, location string) (err error) {
   546  	opts := rest.Opts{
   547  		RootURL: location,
   548  		Method:  "GET",
   549  	}
   550  	deadline := time.Now().Add(fs.Config.Timeout)
   551  	for time.Now().Before(deadline) {
   552  		var resp *http.Response
   553  		var body []byte
   554  		err = f.pacer.Call(func() (bool, error) {
   555  			resp, err = f.srv.Call(ctx, &opts)
   556  			if err != nil {
   557  				return fserrors.ShouldRetry(err), err
   558  			}
   559  			body, err = rest.ReadBody(resp)
   560  			return fserrors.ShouldRetry(err), err
   561  		})
   562  		if err != nil {
   563  			return err
   564  		}
   565  		// Try to decode the body first as an api.AsyncOperationStatus
   566  		var status api.AsyncStatus
   567  		err = json.Unmarshal(body, &status)
   568  		if err != nil {
   569  			return errors.Wrapf(err, "async status result not JSON: %q", body)
   570  		}
   571  
   572  		switch status.Status {
   573  		case "failure":
   574  			return errors.Errorf("async operation returned %q", status.Status)
   575  		case "success":
   576  			return nil
   577  		}
   578  
   579  		time.Sleep(1 * time.Second)
   580  	}
   581  	return errors.Errorf("async operation didn't complete after %v", fs.Config.Timeout)
   582  }
   583  
   584  func (f *Fs) delete(ctx context.Context, path string, hardDelete bool) (err error) {
   585  	opts := rest.Opts{
   586  		Method:     "DELETE",
   587  		Path:       "/resources",
   588  		Parameters: url.Values{},
   589  	}
   590  
   591  	opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
   592  	opts.Parameters.Set("permanently", strconv.FormatBool(hardDelete))
   593  
   594  	var resp *http.Response
   595  	var body []byte
   596  	err = f.pacer.Call(func() (bool, error) {
   597  		resp, err = f.srv.Call(ctx, &opts)
   598  		if err != nil {
   599  			return fserrors.ShouldRetry(err), err
   600  		}
   601  		body, err = rest.ReadBody(resp)
   602  		return fserrors.ShouldRetry(err), err
   603  	})
   604  	if err != nil {
   605  		return err
   606  	}
   607  
   608  	// if 202 Accepted it's an async operation we have to wait for it complete before retuning
   609  	if resp.StatusCode == 202 {
   610  		var info api.AsyncInfo
   611  		err = json.Unmarshal(body, &info)
   612  		if err != nil {
   613  			return errors.Wrapf(err, "async info result not JSON: %q", body)
   614  		}
   615  		return f.waitForJob(ctx, info.HRef)
   616  	}
   617  	return nil
   618  }
   619  
   620  // purgeCheck remotes the root directory, if check is set then it
   621  // refuses to do so if it has anything in
   622  func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error {
   623  	root := f.filePath(dir)
   624  	if check {
   625  		//to comply with rclone logic we check if the directory is empty before delete.
   626  		//send request to get list of objects in this directory.
   627  		info, err := f.readMetaDataForPath(ctx, root, &api.ResourceInfoRequestOptions{})
   628  		if err != nil {
   629  			return errors.Wrap(err, "rmdir failed")
   630  		}
   631  		if len(info.Embedded.Items) != 0 {
   632  			return fs.ErrorDirectoryNotEmpty
   633  		}
   634  	}
   635  	//delete directory
   636  	return f.delete(ctx, root, false)
   637  }
   638  
   639  // Rmdir deletes the container
   640  //
   641  // Returns an error if it isn't empty
   642  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   643  	return f.purgeCheck(ctx, dir, true)
   644  }
   645  
   646  // Purge deletes all the files and the container
   647  //
   648  // Optional interface: Only implement this if you have a way of
   649  // deleting all the files quicker than just running Remove() on the
   650  // result of List()
   651  func (f *Fs) Purge(ctx context.Context) error {
   652  	return f.purgeCheck(ctx, "", false)
   653  }
   654  
   655  // copyOrMoves copies or moves directories or files depending on the method parameter
   656  func (f *Fs) copyOrMove(ctx context.Context, method, src, dst string, overwrite bool) (err error) {
   657  	opts := rest.Opts{
   658  		Method:     "POST",
   659  		Path:       "/resources/" + method,
   660  		Parameters: url.Values{},
   661  	}
   662  
   663  	opts.Parameters.Set("from", f.opt.Enc.FromStandardPath(src))
   664  	opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(dst))
   665  	opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite))
   666  
   667  	var resp *http.Response
   668  	var body []byte
   669  	err = f.pacer.Call(func() (bool, error) {
   670  		resp, err = f.srv.Call(ctx, &opts)
   671  		if err != nil {
   672  			return fserrors.ShouldRetry(err), err
   673  		}
   674  		body, err = rest.ReadBody(resp)
   675  		return fserrors.ShouldRetry(err), err
   676  	})
   677  	if err != nil {
   678  		return err
   679  	}
   680  
   681  	// if 202 Accepted it's an async operation we have to wait for it complete before retuning
   682  	if resp.StatusCode == 202 {
   683  		var info api.AsyncInfo
   684  		err = json.Unmarshal(body, &info)
   685  		if err != nil {
   686  			return errors.Wrapf(err, "async info result not JSON: %q", body)
   687  		}
   688  		return f.waitForJob(ctx, info.HRef)
   689  	}
   690  	return nil
   691  }
   692  
   693  // Copy src to this remote using server side copy operations.
   694  //
   695  // This is stored with the remote path given
   696  //
   697  // It returns the destination Object and a possible error
   698  //
   699  // Will only be called if src.Fs().Name() == f.Name()
   700  //
   701  // If it isn't possible then return fs.ErrorCantCopy
   702  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   703  	srcObj, ok := src.(*Object)
   704  	if !ok {
   705  		fs.Debugf(src, "Can't copy - not same remote type")
   706  		return nil, fs.ErrorCantCopy
   707  	}
   708  
   709  	dstPath := f.filePath(remote)
   710  	err := f.mkParentDirs(ctx, dstPath)
   711  	if err != nil {
   712  		return nil, err
   713  	}
   714  	err = f.copyOrMove(ctx, "copy", srcObj.filePath(), dstPath, false)
   715  
   716  	if err != nil {
   717  		return nil, errors.Wrap(err, "couldn't copy file")
   718  	}
   719  
   720  	return f.NewObject(ctx, remote)
   721  }
   722  
   723  // Move src to this remote using server side move operations.
   724  //
   725  // This is stored with the remote path given
   726  //
   727  // It returns the destination Object and a possible error
   728  //
   729  // Will only be called if src.Fs().Name() == f.Name()
   730  //
   731  // If it isn't possible then return fs.ErrorCantMove
   732  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   733  	srcObj, ok := src.(*Object)
   734  	if !ok {
   735  		fs.Debugf(src, "Can't move - not same remote type")
   736  		return nil, fs.ErrorCantMove
   737  	}
   738  
   739  	dstPath := f.filePath(remote)
   740  	err := f.mkParentDirs(ctx, dstPath)
   741  	if err != nil {
   742  		return nil, err
   743  	}
   744  	err = f.copyOrMove(ctx, "move", srcObj.filePath(), dstPath, false)
   745  
   746  	if err != nil {
   747  		return nil, errors.Wrap(err, "couldn't move file")
   748  	}
   749  
   750  	return f.NewObject(ctx, remote)
   751  }
   752  
   753  // DirMove moves src, srcRemote to this remote at dstRemote
   754  // using server side move operations.
   755  //
   756  // Will only be called if src.Fs().Name() == f.Name()
   757  //
   758  // If it isn't possible then return fs.ErrorCantDirMove
   759  //
   760  // If destination exists then return fs.ErrorDirExists
   761  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   762  	srcFs, ok := src.(*Fs)
   763  	if !ok {
   764  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   765  		return fs.ErrorCantDirMove
   766  	}
   767  	srcPath := path.Join(srcFs.diskRoot, srcRemote)
   768  	dstPath := f.dirPath(dstRemote)
   769  
   770  	//fmt.Printf("Move src: %s (FullPath: %s), dst: %s (FullPath: %s)\n", srcRemote, srcPath, dstRemote, dstPath)
   771  
   772  	// Refuse to move to or from the root
   773  	if srcPath == "disk:/" || dstPath == "disk:/" {
   774  		fs.Debugf(src, "DirMove error: Can't move root")
   775  		return errors.New("can't move root directory")
   776  	}
   777  
   778  	err := f.mkParentDirs(ctx, dstPath)
   779  	if err != nil {
   780  		return err
   781  	}
   782  
   783  	_, err = f.readMetaDataForPath(ctx, dstPath, &api.ResourceInfoRequestOptions{})
   784  	if apiErr, ok := err.(*api.ErrorResponse); ok {
   785  		// does not exist
   786  		if apiErr.ErrorName == "DiskNotFoundError" {
   787  			// OK
   788  		}
   789  	} else if err != nil {
   790  		return err
   791  	} else {
   792  		return fs.ErrorDirExists
   793  	}
   794  
   795  	err = f.copyOrMove(ctx, "move", srcPath, dstPath, false)
   796  
   797  	if err != nil {
   798  		return errors.Wrap(err, "couldn't move directory")
   799  	}
   800  	return nil
   801  }
   802  
   803  // PublicLink generates a public link to the remote path (usually readable by anyone)
   804  func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err error) {
   805  	var path string
   806  	if f.opt.Unlink {
   807  		path = "/resources/unpublish"
   808  	} else {
   809  		path = "/resources/publish"
   810  	}
   811  	opts := rest.Opts{
   812  		Method:     "PUT",
   813  		Path:       f.opt.Enc.FromStandardPath(path),
   814  		Parameters: url.Values{},
   815  		NoResponse: true,
   816  	}
   817  
   818  	opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(f.filePath(remote)))
   819  
   820  	var resp *http.Response
   821  	err = f.pacer.Call(func() (bool, error) {
   822  		resp, err = f.srv.Call(ctx, &opts)
   823  		return shouldRetry(resp, err)
   824  	})
   825  
   826  	if apiErr, ok := err.(*api.ErrorResponse); ok {
   827  		// does not exist
   828  		if apiErr.ErrorName == "DiskNotFoundError" {
   829  			return "", fs.ErrorObjectNotFound
   830  		}
   831  	}
   832  	if err != nil {
   833  		if f.opt.Unlink {
   834  			return "", errors.Wrap(err, "couldn't remove public link")
   835  		}
   836  		return "", errors.Wrap(err, "couldn't create public link")
   837  	}
   838  
   839  	info, err := f.readMetaDataForPath(ctx, f.filePath(remote), &api.ResourceInfoRequestOptions{})
   840  	if err != nil {
   841  		return "", err
   842  	}
   843  
   844  	if info.PublicURL == "" {
   845  		return "", errors.New("couldn't create public link - no link path received")
   846  	}
   847  	return info.PublicURL, nil
   848  }
   849  
   850  // CleanUp permanently deletes all trashed files/folders
   851  func (f *Fs) CleanUp(ctx context.Context) (err error) {
   852  	var resp *http.Response
   853  	opts := rest.Opts{
   854  		Method:     "DELETE",
   855  		Path:       "/trash/resources",
   856  		NoResponse: true,
   857  	}
   858  
   859  	err = f.pacer.Call(func() (bool, error) {
   860  		resp, err = f.srv.Call(ctx, &opts)
   861  		return shouldRetry(resp, err)
   862  	})
   863  	return err
   864  }
   865  
   866  // About gets quota information
   867  func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
   868  	opts := rest.Opts{
   869  		Method: "GET",
   870  		Path:   "/",
   871  	}
   872  
   873  	var resp *http.Response
   874  	var info api.DiskInfo
   875  	var err error
   876  	err = f.pacer.Call(func() (bool, error) {
   877  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &info)
   878  		return shouldRetry(resp, err)
   879  	})
   880  
   881  	if err != nil {
   882  		return nil, err
   883  	}
   884  
   885  	usage := &fs.Usage{
   886  		Total: fs.NewUsageValue(info.TotalSpace),
   887  		Used:  fs.NewUsageValue(info.UsedSpace),
   888  		Free:  fs.NewUsageValue(info.TotalSpace - info.UsedSpace),
   889  	}
   890  	return usage, nil
   891  }
   892  
   893  // ------------------------------------------------------------
   894  
   895  // Fs returns the parent Fs
   896  func (o *Object) Fs() fs.Info {
   897  	return o.fs
   898  }
   899  
   900  // Return a string version
   901  func (o *Object) String() string {
   902  	if o == nil {
   903  		return "<nil>"
   904  	}
   905  	return o.remote
   906  }
   907  
   908  // Remote returns the remote path
   909  func (o *Object) Remote() string {
   910  	return o.remote
   911  }
   912  
   913  // Returns the full remote path for the object
   914  func (o *Object) filePath() string {
   915  	return o.fs.filePath(o.remote)
   916  }
   917  
   918  // setMetaData sets the fs data from a storage.Object
   919  func (o *Object) setMetaData(info *api.ResourceInfoResponse) (err error) {
   920  	o.hasMetaData = true
   921  	o.size = info.Size
   922  	o.md5sum = info.Md5
   923  	o.mimeType = info.MimeType
   924  
   925  	var modTimeString string
   926  	modTimeObj, ok := info.CustomProperties["rclone_modified"]
   927  	if ok {
   928  		// read modTime from rclone_modified custom_property of object
   929  		modTimeString, ok = modTimeObj.(string)
   930  	}
   931  	if !ok {
   932  		// read modTime from Modified property of object as a fallback
   933  		modTimeString = info.Modified
   934  	}
   935  	t, err := time.Parse(time.RFC3339Nano, modTimeString)
   936  	if err != nil {
   937  		return errors.Wrapf(err, "failed to parse modtime from %q", modTimeString)
   938  	}
   939  	o.modTime = t
   940  	return nil
   941  }
   942  
   943  // readMetaData reads ands sets the new metadata for a storage.Object
   944  func (o *Object) readMetaData(ctx context.Context) (err error) {
   945  	if o.hasMetaData {
   946  		return nil
   947  	}
   948  	info, err := o.fs.readMetaDataForPath(ctx, o.filePath(), &api.ResourceInfoRequestOptions{})
   949  	if err != nil {
   950  		return err
   951  	}
   952  	if info.ResourceType != "file" {
   953  		return fs.ErrorNotAFile
   954  	}
   955  	return o.setMetaData(info)
   956  }
   957  
   958  // ModTime returns the modification time of the object
   959  //
   960  // It attempts to read the objects mtime and if that isn't present the
   961  // LastModified returned in the http headers
   962  func (o *Object) ModTime(ctx context.Context) time.Time {
   963  	err := o.readMetaData(ctx)
   964  	if err != nil {
   965  		fs.Logf(o, "Failed to read metadata: %v", err)
   966  		return time.Now()
   967  	}
   968  	return o.modTime
   969  }
   970  
   971  // Size returns the size of an object in bytes
   972  func (o *Object) Size() int64 {
   973  	ctx := context.TODO()
   974  	err := o.readMetaData(ctx)
   975  	if err != nil {
   976  		fs.Logf(o, "Failed to read metadata: %v", err)
   977  		return 0
   978  	}
   979  	return o.size
   980  }
   981  
   982  // Hash returns the Md5sum of an object returning a lowercase hex string
   983  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   984  	if t != hash.MD5 {
   985  		return "", hash.ErrUnsupported
   986  	}
   987  	return o.md5sum, nil
   988  }
   989  
   990  // Storable returns whether this object is storable
   991  func (o *Object) Storable() bool {
   992  	return true
   993  }
   994  
   995  func (o *Object) setCustomProperty(ctx context.Context, property string, value string) (err error) {
   996  	var resp *http.Response
   997  	opts := rest.Opts{
   998  		Method:     "PATCH",
   999  		Path:       "/resources",
  1000  		Parameters: url.Values{},
  1001  		NoResponse: true,
  1002  	}
  1003  
  1004  	opts.Parameters.Set("path", o.fs.opt.Enc.FromStandardPath(o.filePath()))
  1005  	rcm := map[string]interface{}{
  1006  		property: value,
  1007  	}
  1008  	cpr := api.CustomPropertyResponse{CustomProperties: rcm}
  1009  
  1010  	err = o.fs.pacer.Call(func() (bool, error) {
  1011  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &cpr, nil)
  1012  		return shouldRetry(resp, err)
  1013  	})
  1014  	return err
  1015  }
  1016  
  1017  // SetModTime sets the modification time of the local fs object
  1018  //
  1019  // Commits the datastore
  1020  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
  1021  	// set custom_property 'rclone_modified' of object to modTime
  1022  	err := o.setCustomProperty(ctx, "rclone_modified", modTime.Format(time.RFC3339Nano))
  1023  	if err != nil {
  1024  		return err
  1025  	}
  1026  	o.modTime = modTime
  1027  	return nil
  1028  }
  1029  
  1030  // Open an object for read
  1031  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  1032  	// prepare download
  1033  	var resp *http.Response
  1034  	var dl api.AsyncInfo
  1035  	opts := rest.Opts{
  1036  		Method:     "GET",
  1037  		Path:       "/resources/download",
  1038  		Parameters: url.Values{},
  1039  	}
  1040  
  1041  	opts.Parameters.Set("path", o.fs.opt.Enc.FromStandardPath(o.filePath()))
  1042  
  1043  	err = o.fs.pacer.Call(func() (bool, error) {
  1044  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl)
  1045  		return shouldRetry(resp, err)
  1046  	})
  1047  
  1048  	if err != nil {
  1049  		return nil, err
  1050  	}
  1051  
  1052  	// perform the download
  1053  	opts = rest.Opts{
  1054  		RootURL: dl.HRef,
  1055  		Method:  "GET",
  1056  		Options: options,
  1057  	}
  1058  	err = o.fs.pacer.Call(func() (bool, error) {
  1059  		resp, err = o.fs.srv.Call(ctx, &opts)
  1060  		return shouldRetry(resp, err)
  1061  	})
  1062  	if err != nil {
  1063  		return nil, err
  1064  	}
  1065  	return resp.Body, err
  1066  }
  1067  
  1068  func (o *Object) upload(ctx context.Context, in io.Reader, overwrite bool, mimeType string, options ...fs.OpenOption) (err error) {
  1069  	// prepare upload
  1070  	var resp *http.Response
  1071  	var ur api.AsyncInfo
  1072  	opts := rest.Opts{
  1073  		Method:     "GET",
  1074  		Path:       "/resources/upload",
  1075  		Parameters: url.Values{},
  1076  		Options:    options,
  1077  	}
  1078  
  1079  	opts.Parameters.Set("path", o.fs.opt.Enc.FromStandardPath(o.filePath()))
  1080  	opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite))
  1081  
  1082  	err = o.fs.pacer.Call(func() (bool, error) {
  1083  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &ur)
  1084  		return shouldRetry(resp, err)
  1085  	})
  1086  
  1087  	if err != nil {
  1088  		return err
  1089  	}
  1090  
  1091  	// perform the actual upload
  1092  	opts = rest.Opts{
  1093  		RootURL:     ur.HRef,
  1094  		Method:      "PUT",
  1095  		ContentType: mimeType,
  1096  		Body:        in,
  1097  		NoResponse:  true,
  1098  	}
  1099  
  1100  	err = o.fs.pacer.Call(func() (bool, error) {
  1101  		resp, err = o.fs.srv.Call(ctx, &opts)
  1102  		return shouldRetry(resp, err)
  1103  	})
  1104  
  1105  	return err
  1106  }
  1107  
  1108  // Update the already existing object
  1109  //
  1110  // Copy the reader into the object updating modTime and size
  1111  //
  1112  // The new object may have been created if an error is returned
  1113  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
  1114  	in1 := readers.NewCountingReader(in)
  1115  	modTime := src.ModTime(ctx)
  1116  	remote := o.filePath()
  1117  
  1118  	//create full path to file before upload.
  1119  	err := o.fs.mkParentDirs(ctx, remote)
  1120  	if err != nil {
  1121  		return err
  1122  	}
  1123  
  1124  	//upload file
  1125  	err = o.upload(ctx, in1, true, fs.MimeType(ctx, src), options...)
  1126  	if err != nil {
  1127  		return err
  1128  	}
  1129  
  1130  	//if file uploaded successfully then return metadata
  1131  	o.modTime = modTime
  1132  	o.md5sum = ""                   // according to unit tests after put the md5 is empty.
  1133  	o.size = int64(in1.BytesRead()) // better solution o.readMetaData() ?
  1134  	//and set modTime of uploaded file
  1135  	err = o.SetModTime(ctx, modTime)
  1136  
  1137  	return err
  1138  }
  1139  
  1140  // Remove an object
  1141  func (o *Object) Remove(ctx context.Context) error {
  1142  	return o.fs.delete(ctx, o.filePath(), false)
  1143  }
  1144  
  1145  // MimeType of an Object if known, "" otherwise
  1146  func (o *Object) MimeType(ctx context.Context) string {
  1147  	return o.mimeType
  1148  }
  1149  
  1150  // Check the interfaces are satisfied
  1151  var (
  1152  	_ fs.Fs           = (*Fs)(nil)
  1153  	_ fs.Purger       = (*Fs)(nil)
  1154  	_ fs.Copier       = (*Fs)(nil)
  1155  	_ fs.Mover        = (*Fs)(nil)
  1156  	_ fs.DirMover     = (*Fs)(nil)
  1157  	_ fs.PublicLinker = (*Fs)(nil)
  1158  	_ fs.CleanUpper   = (*Fs)(nil)
  1159  	_ fs.Abouter      = (*Fs)(nil)
  1160  	_ fs.Object       = (*Object)(nil)
  1161  	_ fs.MimeTyper    = (*Object)(nil)
  1162  )