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