github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/uptobox/uptobox.go (about)

     1  // Package uptobox provides an interface to the Uptobox storage system.
     2  package uptobox
     3  
     4  import (
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/url"
    12  	"path"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/rclone/rclone/backend/uptobox/api"
    19  	"github.com/rclone/rclone/fs"
    20  	"github.com/rclone/rclone/fs/config"
    21  	"github.com/rclone/rclone/fs/config/configmap"
    22  	"github.com/rclone/rclone/fs/config/configstruct"
    23  	"github.com/rclone/rclone/fs/fserrors"
    24  	"github.com/rclone/rclone/fs/fshttp"
    25  	"github.com/rclone/rclone/fs/hash"
    26  	"github.com/rclone/rclone/lib/encoder"
    27  	"github.com/rclone/rclone/lib/pacer"
    28  	"github.com/rclone/rclone/lib/random"
    29  	"github.com/rclone/rclone/lib/rest"
    30  )
    31  
    32  const (
    33  	apiBaseURL     = "https://uptobox.com/api"
    34  	minSleep       = 400 * time.Millisecond // api is extremely rate limited now
    35  	maxSleep       = 5 * time.Second
    36  	decayConstant  = 2 // bigger for slower decay, exponential
    37  	attackConstant = 0 // start with max sleep
    38  )
    39  
    40  func init() {
    41  	fs.Register(&fs.RegInfo{
    42  		Name:        "uptobox",
    43  		Description: "Uptobox",
    44  		NewFs:       NewFs,
    45  		Options: []fs.Option{{
    46  			Help:      "Your access token.\n\nGet it from https://uptobox.com/my_account.",
    47  			Name:      "access_token",
    48  			Sensitive: true,
    49  		}, {
    50  			Help:     "Set to make uploaded files private",
    51  			Name:     "private",
    52  			Advanced: true,
    53  			Default:  false,
    54  		}, {
    55  			Name:     config.ConfigEncoding,
    56  			Help:     config.ConfigEncodingHelp,
    57  			Advanced: true,
    58  			// maxFileLength = 255
    59  			Default: (encoder.Display |
    60  				encoder.EncodeBackQuote |
    61  				encoder.EncodeDoubleQuote |
    62  				encoder.EncodeLtGt |
    63  				encoder.EncodeLeftSpace |
    64  				encoder.EncodeInvalidUtf8),
    65  		}},
    66  	})
    67  }
    68  
    69  // Options defines the configuration for this backend
    70  type Options struct {
    71  	AccessToken string               `config:"access_token"`
    72  	Private     bool                 `config:"private"`
    73  	Enc         encoder.MultiEncoder `config:"encoding"`
    74  }
    75  
    76  // Fs is the interface a cloud storage system must provide
    77  type Fs struct {
    78  	root     string
    79  	name     string
    80  	opt      Options
    81  	features *fs.Features
    82  	srv      *rest.Client
    83  	pacer    *fs.Pacer
    84  	IDRegexp *regexp.Regexp
    85  	public   string // "0" to make objects private
    86  }
    87  
    88  // Object represents an Uptobox object
    89  type Object struct {
    90  	fs          *Fs    // what this object is part of
    91  	remote      string // The remote path
    92  	hasMetaData bool   // whether info below has been set
    93  	size        int64  // Bytes in the object
    94  	//	modTime     time.Time // Modified time of the object
    95  	code string
    96  }
    97  
    98  // Name of the remote (as passed into NewFs)
    99  func (f *Fs) Name() string {
   100  	return f.name
   101  }
   102  
   103  // Root of the remote (as passed into NewFs)
   104  func (f *Fs) Root() string {
   105  	return f.root
   106  }
   107  
   108  // String returns a description of the FS
   109  func (f *Fs) String() string {
   110  	return fmt.Sprintf("Uptobox root '%s'", f.root)
   111  }
   112  
   113  // Precision of the ModTimes in this Fs
   114  func (f *Fs) Precision() time.Duration {
   115  	return fs.ModTimeNotSupported
   116  }
   117  
   118  // Hashes returns the supported hash types of the filesystem
   119  func (f *Fs) Hashes() hash.Set {
   120  	return hash.Set(hash.None)
   121  }
   122  
   123  // Features returns the optional features of this Fs
   124  func (f *Fs) Features() *fs.Features {
   125  	return f.features
   126  }
   127  
   128  // retryErrorCodes is a slice of error codes that we will retry
   129  var retryErrorCodes = []int{
   130  	429, // Too Many Requests.
   131  	500, // Internal Server Error
   132  	502, // Bad Gateway
   133  	503, // Service Unavailable
   134  	504, // Gateway Timeout
   135  	509, // Bandwidth Limit Exceeded
   136  }
   137  
   138  // shouldRetry returns a boolean as to whether this resp and err
   139  // deserve to be retried.  It returns the err as a convenience
   140  func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) {
   141  	if fserrors.ContextError(ctx, &err) {
   142  		return false, err
   143  	}
   144  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   145  }
   146  
   147  // dirPath returns an escaped file path (f.root, file)
   148  func (f *Fs) dirPath(file string) string {
   149  	//return path.Join(f.diskRoot, file)
   150  	if file == "" || file == "." {
   151  		return "//" + f.root
   152  	}
   153  	return "//" + path.Join(f.root, file)
   154  }
   155  
   156  // returns the full path based on root and the last element
   157  func (f *Fs) splitPathFull(pth string) (string, string) {
   158  	fullPath := strings.Trim(path.Join(f.root, pth), "/")
   159  
   160  	i := len(fullPath) - 1
   161  	for i >= 0 && fullPath[i] != '/' {
   162  		i--
   163  	}
   164  
   165  	if i < 0 {
   166  		return "//" + fullPath[:i+1], fullPath[i+1:]
   167  	}
   168  
   169  	// do not include the / at the split
   170  	return "//" + fullPath[:i], fullPath[i+1:]
   171  }
   172  
   173  // splitPath is modified splitPath version that doesn't include the separator
   174  // in the base path
   175  func (f *Fs) splitPath(pth string) (string, string) {
   176  	// chop of any leading or trailing '/'
   177  	pth = strings.Trim(pth, "/")
   178  
   179  	i := len(pth) - 1
   180  	for i >= 0 && pth[i] != '/' {
   181  		i--
   182  	}
   183  
   184  	if i < 0 {
   185  		return pth[:i+1], pth[i+1:]
   186  	}
   187  	return pth[:i], pth[i+1:]
   188  }
   189  
   190  // NewFs makes a new Fs object from the path
   191  //
   192  // The path is of the form remote:path
   193  //
   194  // Remotes are looked up in the config file.  If the remote isn't
   195  // found then NotFoundInConfigFile will be returned.
   196  //
   197  // On Windows avoid single character remote names as they can be mixed
   198  // up with drive letters.
   199  func NewFs(ctx context.Context, name string, root string, config configmap.Mapper) (fs.Fs, error) {
   200  	opt := new(Options)
   201  	err := configstruct.Set(config, opt)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	f := &Fs{
   207  		name:  name,
   208  		root:  root,
   209  		opt:   *opt,
   210  		pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant), pacer.AttackConstant(attackConstant))),
   211  	}
   212  	if root == "/" || root == "." {
   213  		f.root = ""
   214  	} else {
   215  		f.root = root
   216  	}
   217  	f.features = (&fs.Features{
   218  		DuplicateFiles:          true,
   219  		CanHaveEmptyDirectories: true,
   220  		ReadMimeType:            false,
   221  	}).Fill(ctx, f)
   222  	if f.opt.Private {
   223  		f.public = "0"
   224  	}
   225  
   226  	client := fshttp.NewClient(ctx)
   227  	f.srv = rest.NewClient(client).SetRoot(apiBaseURL)
   228  	f.IDRegexp = regexp.MustCompile(`^https://uptobox\.com/([a-zA-Z0-9]+)`)
   229  
   230  	_, err = f.readMetaDataForPath(ctx, f.dirPath(""), &api.MetadataRequestOptions{Limit: 10})
   231  	if err != nil {
   232  		if _, ok := err.(api.Error); !ok {
   233  			return nil, err
   234  		}
   235  		// assume it's a file than
   236  		oldRoot := f.root
   237  		rootDir, file := f.splitPath(root)
   238  		f.root = rootDir
   239  		_, err = f.NewObject(ctx, file)
   240  		if err == nil {
   241  			return f, fs.ErrorIsFile
   242  		}
   243  		f.root = oldRoot
   244  	}
   245  
   246  	return f, nil
   247  }
   248  
   249  func (f *Fs) decodeError(resp *http.Response, response interface{}) (err error) {
   250  	defer fs.CheckClose(resp.Body, &err)
   251  
   252  	body, err := io.ReadAll(resp.Body)
   253  	if err != nil {
   254  		return err
   255  	}
   256  	// try to unmarshal into correct structure
   257  	err = json.Unmarshal(body, response)
   258  	if err == nil {
   259  		return nil
   260  	}
   261  	// try to unmarshal into Error
   262  	var apiErr api.Error
   263  	err = json.Unmarshal(body, &apiErr)
   264  	if err != nil {
   265  		return err
   266  	}
   267  	return apiErr
   268  }
   269  
   270  func (f *Fs) readMetaDataForPath(ctx context.Context, path string, options *api.MetadataRequestOptions) (*api.ReadMetadataResponse, error) {
   271  	opts := rest.Opts{
   272  		Method: "GET",
   273  		Path:   "/user/files",
   274  		Parameters: url.Values{
   275  			"token": []string{f.opt.AccessToken},
   276  			"path":  []string{f.opt.Enc.FromStandardPath(path)},
   277  			"limit": []string{strconv.FormatUint(options.Limit, 10)},
   278  		},
   279  	}
   280  
   281  	if options.Offset != 0 {
   282  		opts.Parameters.Set("offset", strconv.FormatUint(options.Offset, 10))
   283  	}
   284  
   285  	var err error
   286  	var info api.ReadMetadataResponse
   287  	var resp *http.Response
   288  	err = f.pacer.Call(func() (bool, error) {
   289  		resp, err = f.srv.Call(ctx, &opts)
   290  		return shouldRetry(ctx, resp, err)
   291  	})
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	err = f.decodeError(resp, &info)
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	if info.StatusCode != 0 {
   302  		return nil, errors.New(info.Message)
   303  	}
   304  
   305  	return &info, nil
   306  }
   307  
   308  // List the objects and directories in dir into entries.  The
   309  // entries can be returned in any order but should be for a
   310  // complete directory.
   311  //
   312  // dir should be "" to list the root, and should not have
   313  // trailing slashes.
   314  //
   315  // This should return ErrDirNotFound if the directory isn't
   316  // found.
   317  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   318  	root := f.dirPath(dir)
   319  
   320  	var limit uint64 = 100 // max number of objects per request - 100 seems to be the maximum the api accepts
   321  	var page uint64 = 1
   322  	var offset uint64 // for the next page of requests
   323  
   324  	for {
   325  		opts := &api.MetadataRequestOptions{
   326  			Limit:  limit,
   327  			Offset: offset,
   328  		}
   329  
   330  		info, err := f.readMetaDataForPath(ctx, root, opts)
   331  		if err != nil {
   332  			if apiErr, ok := err.(api.Error); ok {
   333  				// might indicate other errors but we can probably assume not found here
   334  				if apiErr.StatusCode == 1 {
   335  					return nil, fs.ErrorDirNotFound
   336  				}
   337  			}
   338  			return nil, err
   339  		}
   340  
   341  		for _, item := range info.Data.Files {
   342  			remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
   343  			o, err := f.newObjectWithInfo(ctx, remote, &item)
   344  			if err != nil {
   345  				continue
   346  			}
   347  			entries = append(entries, o)
   348  		}
   349  
   350  		// folders are always listed entirely on every page grr.
   351  		if page == 1 {
   352  			for _, item := range info.Data.Folders {
   353  				remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
   354  				d := fs.NewDir(remote, time.Time{}).SetID(strconv.FormatUint(item.FolderID, 10))
   355  				entries = append(entries, d)
   356  			}
   357  		}
   358  
   359  		//offset for the next page of items
   360  		page++
   361  		offset += limit
   362  		//check if we reached end of list
   363  		if page > uint64(info.Data.PageCount) {
   364  			break
   365  		}
   366  	}
   367  	return entries, nil
   368  }
   369  
   370  // Return an Object from a path
   371  //
   372  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   373  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.FileInfo) (fs.Object, error) {
   374  	o := &Object{
   375  		fs:          f,
   376  		remote:      remote,
   377  		size:        info.Size,
   378  		code:        info.Code,
   379  		hasMetaData: true,
   380  	}
   381  	return o, nil
   382  }
   383  
   384  // NewObject finds the Object at remote.  If it can't be found it
   385  // returns the error fs.ErrorObjectNotFound.
   386  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   387  	// no way to directly access an object by path so we have to list the parent dir
   388  	entries, err := f.List(ctx, path.Dir(remote))
   389  	if err != nil {
   390  		// need to change error type
   391  		// if the parent dir doesn't exist the object doesn't exist either
   392  		if err == fs.ErrorDirNotFound {
   393  			return nil, fs.ErrorObjectNotFound
   394  		}
   395  		return nil, err
   396  	}
   397  	for _, entry := range entries {
   398  		if o, ok := entry.(fs.Object); ok {
   399  			if o.Remote() == remote {
   400  				return o, nil
   401  			}
   402  		}
   403  	}
   404  	return nil, fs.ErrorObjectNotFound
   405  }
   406  
   407  func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, filename string, uploadURL string, options ...fs.OpenOption) (*api.UploadResponse, error) {
   408  	opts := rest.Opts{
   409  		Method:               "POST",
   410  		RootURL:              "https:" + uploadURL,
   411  		Body:                 in,
   412  		ContentLength:        &size,
   413  		Options:              options,
   414  		MultipartContentName: "files",
   415  		MultipartFileName:    filename,
   416  	}
   417  
   418  	var err error
   419  	var resp *http.Response
   420  	var ul api.UploadResponse
   421  	err = f.pacer.CallNoRetry(func() (bool, error) {
   422  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &ul)
   423  		return shouldRetry(ctx, resp, err)
   424  	})
   425  	if err != nil {
   426  		return nil, fmt.Errorf("couldn't upload file: %w", err)
   427  	}
   428  	return &ul, nil
   429  }
   430  
   431  // dstPath starts from root and includes //
   432  func (f *Fs) move(ctx context.Context, dstPath string, fileID string) (err error) {
   433  	meta, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 10})
   434  	if err != nil {
   435  		return err
   436  	}
   437  
   438  	opts := rest.Opts{
   439  		Method: "PATCH",
   440  		Path:   "/user/files",
   441  	}
   442  	mv := api.CopyMoveFileRequest{
   443  		Token:               f.opt.AccessToken,
   444  		FileCodes:           fileID,
   445  		DestinationFolderID: meta.Data.CurrentFolder.FolderID,
   446  		Action:              "move",
   447  	}
   448  
   449  	var resp *http.Response
   450  	var info api.UpdateResponse
   451  	err = f.pacer.Call(func() (bool, error) {
   452  		resp, err = f.srv.CallJSON(ctx, &opts, &mv, &info)
   453  		return shouldRetry(ctx, resp, err)
   454  	})
   455  	if err != nil {
   456  		return fmt.Errorf("couldn't move file: %w", err)
   457  	}
   458  	if info.StatusCode != 0 {
   459  		return fmt.Errorf("move: api error: %d - %s", info.StatusCode, info.Message)
   460  	}
   461  	return err
   462  }
   463  
   464  // updateFileInformation set's various file attributes most importantly it's name
   465  func (f *Fs) updateFileInformation(ctx context.Context, update *api.UpdateFileInformation) (err error) {
   466  	opts := rest.Opts{
   467  		Method: "PATCH",
   468  		Path:   "/user/files",
   469  	}
   470  
   471  	var resp *http.Response
   472  	var info api.UpdateResponse
   473  	err = f.pacer.Call(func() (bool, error) {
   474  		resp, err = f.srv.CallJSON(ctx, &opts, update, &info)
   475  		return shouldRetry(ctx, resp, err)
   476  	})
   477  	if err != nil {
   478  		return fmt.Errorf("couldn't update file info: %w", err)
   479  	}
   480  	if info.StatusCode != 0 {
   481  		return fmt.Errorf("updateFileInfo: api error: %d - %s", info.StatusCode, info.Message)
   482  	}
   483  	return err
   484  }
   485  
   486  func (f *Fs) putUnchecked(ctx context.Context, in io.Reader, remote string, size int64, options ...fs.OpenOption) error {
   487  	if size > int64(200e9) { // max size 200GB
   488  		return errors.New("file too big, can't upload")
   489  	} else if size == 0 {
   490  		return fs.ErrorCantUploadEmptyFiles
   491  	}
   492  	// yes it does take 4 requests if we're uploading to root and 6+ if we're uploading to any subdir :(
   493  
   494  	// create upload request
   495  	opts := rest.Opts{
   496  		Method: "GET",
   497  		Path:   "/upload",
   498  	}
   499  	token := api.Token{
   500  		Token: f.opt.AccessToken,
   501  	}
   502  	var info api.UploadInfo
   503  	err := f.pacer.Call(func() (bool, error) {
   504  		resp, err := f.srv.CallJSON(ctx, &opts, &token, &info)
   505  		return shouldRetry(ctx, resp, err)
   506  	})
   507  	if err != nil {
   508  		return err
   509  	}
   510  	if info.StatusCode != 0 {
   511  		return fmt.Errorf("putUnchecked api error: %d - %s", info.StatusCode, info.Message)
   512  	}
   513  	// we need to have a safe name for the upload to work
   514  	tmpName := "rcloneTemp" + random.String(8)
   515  	upload, err := f.uploadFile(ctx, in, size, tmpName, info.Data.UploadLink, options...)
   516  	if err != nil {
   517  		return err
   518  	}
   519  	if len(upload.Files) != 1 {
   520  		return errors.New("upload unexpected response")
   521  	}
   522  	match := f.IDRegexp.FindStringSubmatch(upload.Files[0].URL)
   523  
   524  	// move file to destination folder
   525  	base, leaf := f.splitPath(remote)
   526  	fullBase := f.dirPath(base)
   527  
   528  	if fullBase != "//" {
   529  		// make all the parent folders
   530  		err = f.Mkdir(ctx, base)
   531  		if err != nil {
   532  			// this might need some more error handling. if any of the following requests fail
   533  			// we'll leave an orphaned temporary file floating around somewhere
   534  			// they rarely fail though
   535  			return err
   536  		}
   537  
   538  		err = f.move(ctx, fullBase, match[1])
   539  		if err != nil {
   540  			return err
   541  		}
   542  	}
   543  
   544  	// rename file to final name
   545  	err = f.updateFileInformation(ctx, &api.UpdateFileInformation{
   546  		Token:    f.opt.AccessToken,
   547  		FileCode: match[1],
   548  		NewName:  f.opt.Enc.FromStandardName(leaf),
   549  		Public:   f.public,
   550  	})
   551  	if err != nil {
   552  		return err
   553  	}
   554  
   555  	return nil
   556  }
   557  
   558  // Put in to the remote path with the modTime given of the given size
   559  //
   560  // When called from outside an Fs by rclone, src.Size() will always be >= 0.
   561  // But for unknown-sized objects (indicated by src.Size() == -1), Put should either
   562  // return an error or upload it properly (rather than e.g. calling panic).
   563  //
   564  // May create the object even if it returns an error - if so
   565  // will return the object and the error, otherwise will return
   566  // nil and the error
   567  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   568  	existingObj, err := f.NewObject(ctx, src.Remote())
   569  	switch err {
   570  	case nil:
   571  		return existingObj, existingObj.Update(ctx, in, src, options...)
   572  	case fs.ErrorObjectNotFound:
   573  		// Not found so create it
   574  		return f.PutUnchecked(ctx, in, src, options...)
   575  	default:
   576  		return nil, err
   577  	}
   578  }
   579  
   580  // PutUnchecked uploads the object
   581  //
   582  // This will create a duplicate if we upload a new file without
   583  // checking to see if there is one already - use Put() for that.
   584  func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   585  	err := f.putUnchecked(ctx, in, src.Remote(), src.Size(), options...)
   586  	if err != nil {
   587  		return nil, err
   588  	}
   589  	return f.NewObject(ctx, src.Remote())
   590  }
   591  
   592  // CreateDir dir creates a directory with the given parent path
   593  // base starts from root and may or may not include //
   594  func (f *Fs) CreateDir(ctx context.Context, base string, leaf string) (err error) {
   595  	base = "//" + strings.Trim(base, "/")
   596  
   597  	var resp *http.Response
   598  	var apiErr api.Error
   599  	opts := rest.Opts{
   600  		Method: "PUT",
   601  		Path:   "/user/files",
   602  	}
   603  	mkdir := api.CreateFolderRequest{
   604  		Name:  f.opt.Enc.FromStandardName(leaf),
   605  		Path:  f.opt.Enc.FromStandardPath(base),
   606  		Token: f.opt.AccessToken,
   607  	}
   608  	err = f.pacer.Call(func() (bool, error) {
   609  		resp, err = f.srv.CallJSON(ctx, &opts, &mkdir, &apiErr)
   610  		return shouldRetry(ctx, resp, err)
   611  	})
   612  	if err != nil {
   613  		return err
   614  	}
   615  	// checking if the dir exists beforehand would be slower so we'll just ignore the error here
   616  	if apiErr.StatusCode != 0 && !strings.Contains(apiErr.Data, "already exists") {
   617  		return apiErr
   618  	}
   619  	return nil
   620  }
   621  
   622  func (f *Fs) mkDirs(ctx context.Context, path string) (err error) {
   623  	// chop of any leading or trailing slashes
   624  	dirs := strings.Split(path, "/")
   625  	var base = ""
   626  	for _, element := range dirs {
   627  		// create every dir one by one
   628  		if element != "" {
   629  			err = f.CreateDir(ctx, base, element)
   630  			if err != nil {
   631  				return err
   632  			}
   633  			base += "/" + element
   634  		}
   635  	}
   636  	return nil
   637  }
   638  
   639  // Mkdir makes the directory (container, bucket)
   640  //
   641  // Shouldn't return an error if it already exists
   642  func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) {
   643  	if dir == "" || dir == "." {
   644  		return f.mkDirs(ctx, f.root)
   645  	}
   646  	return f.mkDirs(ctx, path.Join(f.root, dir))
   647  }
   648  
   649  // may or may not delete folders with contents?
   650  func (f *Fs) purge(ctx context.Context, folderID uint64) (err error) {
   651  	var resp *http.Response
   652  	var apiErr api.Error
   653  	opts := rest.Opts{
   654  		Method: "DELETE",
   655  		Path:   "/user/files",
   656  	}
   657  	rm := api.DeleteFolderRequest{
   658  		FolderID: folderID,
   659  		Token:    f.opt.AccessToken,
   660  	}
   661  	err = f.pacer.Call(func() (bool, error) {
   662  		resp, err = f.srv.CallJSON(ctx, &opts, &rm, &apiErr)
   663  		return shouldRetry(ctx, resp, err)
   664  	})
   665  	if err != nil {
   666  		return err
   667  	}
   668  	if apiErr.StatusCode != 0 {
   669  		return apiErr
   670  	}
   671  	return nil
   672  }
   673  
   674  // Rmdir removes the directory (container, bucket) if empty
   675  //
   676  // Return an error if it doesn't exist or isn't empty
   677  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   678  	info, err := f.readMetaDataForPath(ctx, f.dirPath(dir), &api.MetadataRequestOptions{Limit: 10})
   679  	if err != nil {
   680  		return err
   681  	}
   682  	if len(info.Data.Folders) > 0 || len(info.Data.Files) > 0 {
   683  		return fs.ErrorDirectoryNotEmpty
   684  	}
   685  
   686  	return f.purge(ctx, info.Data.CurrentFolder.FolderID)
   687  }
   688  
   689  // Move src to this remote using server side move operations.
   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  	srcBase, srcLeaf := srcObj.fs.splitPathFull(src.Remote())
   698  	dstBase, dstLeaf := f.splitPathFull(remote)
   699  
   700  	needRename := srcLeaf != dstLeaf
   701  	needMove := srcBase != dstBase
   702  
   703  	// do the move if required
   704  	if needMove {
   705  		err := f.mkDirs(ctx, strings.Trim(dstBase, "/"))
   706  		if err != nil {
   707  			return nil, fmt.Errorf("move: failed to make destination dirs: %w", err)
   708  		}
   709  
   710  		err = f.move(ctx, dstBase, srcObj.code)
   711  		if err != nil {
   712  			return nil, err
   713  		}
   714  	}
   715  
   716  	// rename to final name if we need to
   717  	if needRename {
   718  		err := f.updateFileInformation(ctx, &api.UpdateFileInformation{
   719  			Token:    f.opt.AccessToken,
   720  			FileCode: srcObj.code,
   721  			NewName:  f.opt.Enc.FromStandardName(dstLeaf),
   722  			Public:   f.public,
   723  		})
   724  		if err != nil {
   725  			return nil, fmt.Errorf("move: failed final rename: %w", err)
   726  		}
   727  	}
   728  
   729  	// copy the old object and apply the changes
   730  	newObj := *srcObj
   731  	newObj.remote = remote
   732  	newObj.fs = f
   733  	return &newObj, nil
   734  }
   735  
   736  // renameDir renames a directory
   737  func (f *Fs) renameDir(ctx context.Context, folderID uint64, newName string) (err error) {
   738  	var resp *http.Response
   739  	var apiErr api.Error
   740  	opts := rest.Opts{
   741  		Method: "PATCH",
   742  		Path:   "/user/files",
   743  	}
   744  	rename := api.RenameFolderRequest{
   745  		Token:    f.opt.AccessToken,
   746  		FolderID: folderID,
   747  		NewName:  newName,
   748  	}
   749  	err = f.pacer.Call(func() (bool, error) {
   750  		resp, err = f.srv.CallJSON(ctx, &opts, &rename, &apiErr)
   751  		return shouldRetry(ctx, resp, err)
   752  	})
   753  	if err != nil {
   754  		return err
   755  	}
   756  	if apiErr.StatusCode != 0 {
   757  		return apiErr
   758  	}
   759  	return nil
   760  }
   761  
   762  // DirMove moves src, srcRemote to this remote at dstRemote
   763  // using server-side move operations.
   764  //
   765  // Will only be called if src.Fs().Name() == f.Name()
   766  //
   767  // If it isn't possible then return fs.ErrorCantDirMove
   768  //
   769  // If destination exists then return fs.ErrorDirExists
   770  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   771  	srcFs, ok := src.(*Fs)
   772  	if !ok {
   773  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   774  		return fs.ErrorCantDirMove
   775  	}
   776  
   777  	// find out source
   778  	srcPath := srcFs.dirPath(srcRemote)
   779  	srcInfo, err := f.readMetaDataForPath(ctx, srcPath, &api.MetadataRequestOptions{Limit: 1})
   780  	if err != nil {
   781  		return fmt.Errorf("dirmove: source not found: %w", err)
   782  	}
   783  	// check if the destination already exists
   784  	dstPath := f.dirPath(dstRemote)
   785  	_, err = f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 1})
   786  	if err == nil {
   787  		return fs.ErrorDirExists
   788  	}
   789  
   790  	// make the destination parent path
   791  	dstBase, dstName := f.splitPathFull(dstRemote)
   792  	err = f.mkDirs(ctx, strings.Trim(dstBase, "/"))
   793  	if err != nil {
   794  		return fmt.Errorf("dirmove: failed to create dirs: %w", err)
   795  	}
   796  
   797  	// find the destination parent dir
   798  	dstInfo, err := f.readMetaDataForPath(ctx, dstBase, &api.MetadataRequestOptions{Limit: 1})
   799  	if err != nil {
   800  		return fmt.Errorf("dirmove: failed to read destination: %w", err)
   801  	}
   802  	srcBase, srcName := srcFs.splitPathFull(srcRemote)
   803  
   804  	needRename := srcName != dstName
   805  	needMove := srcBase != dstBase
   806  
   807  	// if we have to rename we'll have to use a temporary name since
   808  	// there could already be a directory with the same name as the src directory
   809  	if needRename {
   810  		// rename to a temporary name
   811  		tmpName := "rcloneTemp" + random.String(8)
   812  		err = f.renameDir(ctx, srcInfo.Data.CurrentFolder.FolderID, tmpName)
   813  		if err != nil {
   814  			return fmt.Errorf("dirmove: failed initial rename: %w", err)
   815  		}
   816  	}
   817  
   818  	// do the move
   819  	if needMove {
   820  		opts := rest.Opts{
   821  			Method: "PATCH",
   822  			Path:   "/user/files",
   823  		}
   824  		move := api.MoveFolderRequest{
   825  			Token:               f.opt.AccessToken,
   826  			FolderID:            srcInfo.Data.CurrentFolder.FolderID,
   827  			DestinationFolderID: dstInfo.Data.CurrentFolder.FolderID,
   828  			Action:              "move",
   829  		}
   830  		var resp *http.Response
   831  		var apiErr api.Error
   832  		err = f.pacer.Call(func() (bool, error) {
   833  			resp, err = f.srv.CallJSON(ctx, &opts, &move, &apiErr)
   834  			return shouldRetry(ctx, resp, err)
   835  		})
   836  		if err != nil {
   837  			return fmt.Errorf("dirmove: failed to move: %w", err)
   838  		}
   839  		if apiErr.StatusCode != 0 {
   840  			return apiErr
   841  		}
   842  	}
   843  
   844  	// rename to final name
   845  	if needRename {
   846  		err = f.renameDir(ctx, srcInfo.Data.CurrentFolder.FolderID, dstName)
   847  		if err != nil {
   848  			return fmt.Errorf("dirmove: failed final rename: %w", err)
   849  		}
   850  	}
   851  	return nil
   852  }
   853  
   854  func (f *Fs) copy(ctx context.Context, dstPath string, fileID string) (err error) {
   855  	meta, err := f.readMetaDataForPath(ctx, dstPath, &api.MetadataRequestOptions{Limit: 10})
   856  	if err != nil {
   857  		return err
   858  	}
   859  
   860  	opts := rest.Opts{
   861  		Method: "PATCH",
   862  		Path:   "/user/files",
   863  	}
   864  	cp := api.CopyMoveFileRequest{
   865  		Token:               f.opt.AccessToken,
   866  		FileCodes:           fileID,
   867  		DestinationFolderID: meta.Data.CurrentFolder.FolderID,
   868  		Action:              "copy",
   869  	}
   870  
   871  	var resp *http.Response
   872  	var info api.UpdateResponse
   873  	err = f.pacer.Call(func() (bool, error) {
   874  		resp, err = f.srv.CallJSON(ctx, &opts, &cp, &info)
   875  		return shouldRetry(ctx, resp, err)
   876  	})
   877  	if err != nil {
   878  		return fmt.Errorf("couldn't copy file: %w", err)
   879  	}
   880  	if info.StatusCode != 0 {
   881  		return fmt.Errorf("copy: api error: %d - %s", info.StatusCode, info.Message)
   882  	}
   883  	return err
   884  }
   885  
   886  // Copy src to this remote using server side move operations.
   887  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   888  	srcObj, ok := src.(*Object)
   889  	if !ok {
   890  		fs.Debugf(src, "Can't copy - not same remote type")
   891  		return nil, fs.ErrorCantMove
   892  	}
   893  
   894  	_, srcLeaf := f.splitPath(src.Remote())
   895  	dstBase, dstLeaf := f.splitPath(remote)
   896  
   897  	needRename := srcLeaf != dstLeaf
   898  
   899  	err := f.mkDirs(ctx, path.Join(f.root, dstBase))
   900  	if err != nil {
   901  		return nil, fmt.Errorf("copy: failed to make destination dirs: %w", err)
   902  	}
   903  
   904  	err = f.copy(ctx, f.dirPath(dstBase), srcObj.code)
   905  	if err != nil {
   906  		return nil, err
   907  	}
   908  
   909  	newObj, err := f.NewObject(ctx, path.Join(dstBase, srcLeaf))
   910  	if err != nil {
   911  		return nil, fmt.Errorf("copy: couldn't find copied object: %w", err)
   912  	}
   913  
   914  	if needRename {
   915  		err := f.updateFileInformation(ctx, &api.UpdateFileInformation{
   916  			Token:    f.opt.AccessToken,
   917  			FileCode: newObj.(*Object).code,
   918  			NewName:  f.opt.Enc.FromStandardName(dstLeaf),
   919  			Public:   f.public,
   920  		})
   921  		if err != nil {
   922  			return nil, fmt.Errorf("copy: failed final rename: %w", err)
   923  		}
   924  		newObj.(*Object).remote = remote
   925  	}
   926  
   927  	return newObj, nil
   928  }
   929  
   930  // ------------------------------------------------------------
   931  
   932  // Fs returns the parent Fs
   933  func (o *Object) Fs() fs.Info {
   934  	return o.fs
   935  }
   936  
   937  // Return a string version
   938  func (o *Object) String() string {
   939  	if o == nil {
   940  		return "<nil>"
   941  	}
   942  	return o.remote
   943  }
   944  
   945  // Remote returns the remote path
   946  func (o *Object) Remote() string {
   947  	return o.remote
   948  }
   949  
   950  // ModTime returns the modification time of the object
   951  //
   952  // It attempts to read the objects mtime and if that isn't present the
   953  // LastModified returned in the http headers
   954  func (o *Object) ModTime(ctx context.Context) time.Time {
   955  	ci := fs.GetConfig(ctx)
   956  	return time.Time(ci.DefaultTime)
   957  }
   958  
   959  // Size returns the size of an object in bytes
   960  func (o *Object) Size() int64 {
   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  	return "", hash.ErrUnsupported
   967  }
   968  
   969  // ID returns the ID of the Object if known, or "" if not
   970  func (o *Object) ID() string {
   971  	return o.code
   972  }
   973  
   974  // Storable returns whether this object is storable
   975  func (o *Object) Storable() bool {
   976  	return true
   977  }
   978  
   979  // SetModTime sets the modification time of the local fs object
   980  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
   981  	return fs.ErrorCantSetModTime
   982  }
   983  
   984  // Open an object for read
   985  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
   986  	opts := rest.Opts{
   987  		Method: "GET",
   988  		Path:   "/link",
   989  		Parameters: url.Values{
   990  			"token":     []string{o.fs.opt.AccessToken},
   991  			"file_code": []string{o.code},
   992  		},
   993  	}
   994  	var dl api.Download
   995  	var resp *http.Response
   996  	err = o.fs.pacer.Call(func() (bool, error) {
   997  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl)
   998  		return shouldRetry(ctx, resp, err)
   999  	})
  1000  	if err != nil {
  1001  		return nil, fmt.Errorf("open: failed to get download link: %w", err)
  1002  	}
  1003  
  1004  	fs.FixRangeOption(options, o.size)
  1005  	opts = rest.Opts{
  1006  		Method:  "GET",
  1007  		RootURL: dl.Data.DownloadLink,
  1008  		Options: options,
  1009  	}
  1010  
  1011  	err = o.fs.pacer.Call(func() (bool, error) {
  1012  		resp, err = o.fs.srv.Call(ctx, &opts)
  1013  		return shouldRetry(ctx, resp, err)
  1014  	})
  1015  
  1016  	if err != nil {
  1017  		return nil, err
  1018  	}
  1019  	return resp.Body, err
  1020  }
  1021  
  1022  // Update the already existing object
  1023  //
  1024  // Copy the reader into the object updating modTime and size.
  1025  //
  1026  // The new object may have been created if an error is returned
  1027  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
  1028  	if src.Size() < 0 {
  1029  		return errors.New("refusing to update with unknown size")
  1030  	}
  1031  
  1032  	// upload with new size but old name
  1033  	err := o.fs.putUnchecked(ctx, in, o.Remote(), src.Size(), options...)
  1034  	if err != nil {
  1035  		return err
  1036  	}
  1037  
  1038  	// delete duplicate object after successful upload
  1039  	err = o.Remove(ctx)
  1040  	if err != nil {
  1041  		return fmt.Errorf("failed to remove old version: %w", err)
  1042  	}
  1043  
  1044  	// Fetch new object after deleting the duplicate
  1045  	info, err := o.fs.NewObject(ctx, o.Remote())
  1046  	if err != nil {
  1047  		return err
  1048  	}
  1049  
  1050  	// Replace guts of old object with new one
  1051  	*o = *info.(*Object)
  1052  
  1053  	return nil
  1054  }
  1055  
  1056  // Remove an object
  1057  func (o *Object) Remove(ctx context.Context) error {
  1058  	opts := rest.Opts{
  1059  		Method: "DELETE",
  1060  		Path:   "/user/files",
  1061  	}
  1062  	delete := api.RemoveFileRequest{
  1063  		Token:     o.fs.opt.AccessToken,
  1064  		FileCodes: o.code,
  1065  	}
  1066  	var info api.UpdateResponse
  1067  	err := o.fs.pacer.Call(func() (bool, error) {
  1068  		resp, err := o.fs.srv.CallJSON(ctx, &opts, &delete, &info)
  1069  		return shouldRetry(ctx, resp, err)
  1070  	})
  1071  	if err != nil {
  1072  		return err
  1073  	}
  1074  	if info.StatusCode != 0 {
  1075  		return fmt.Errorf("remove: api error: %d - %s", info.StatusCode, info.Message)
  1076  	}
  1077  	return nil
  1078  }
  1079  
  1080  // Check the interfaces are satisfied
  1081  var (
  1082  	_ fs.Fs       = (*Fs)(nil)
  1083  	_ fs.Copier   = (*Fs)(nil)
  1084  	_ fs.Mover    = (*Fs)(nil)
  1085  	_ fs.DirMover = (*Fs)(nil)
  1086  	_ fs.Object   = (*Object)(nil)
  1087  )