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

     1  package opendrive
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/pkg/errors"
    15  	"github.com/rclone/rclone/fs"
    16  	"github.com/rclone/rclone/fs/config"
    17  	"github.com/rclone/rclone/fs/config/configmap"
    18  	"github.com/rclone/rclone/fs/config/configstruct"
    19  	"github.com/rclone/rclone/fs/config/obscure"
    20  	"github.com/rclone/rclone/fs/fserrors"
    21  	"github.com/rclone/rclone/fs/fshttp"
    22  	"github.com/rclone/rclone/fs/hash"
    23  	"github.com/rclone/rclone/lib/dircache"
    24  	"github.com/rclone/rclone/lib/encoder"
    25  	"github.com/rclone/rclone/lib/pacer"
    26  	"github.com/rclone/rclone/lib/readers"
    27  	"github.com/rclone/rclone/lib/rest"
    28  )
    29  
    30  const (
    31  	defaultEndpoint = "https://dev.opendrive.com/api/v1"
    32  	minSleep        = 10 * time.Millisecond
    33  	maxSleep        = 5 * time.Minute
    34  	decayConstant   = 1 // bigger for slower decay, exponential
    35  )
    36  
    37  // Register with Fs
    38  func init() {
    39  	fs.Register(&fs.RegInfo{
    40  		Name:        "opendrive",
    41  		Description: "OpenDrive",
    42  		NewFs:       NewFs,
    43  		Options: []fs.Option{{
    44  			Name:     "username",
    45  			Help:     "Username",
    46  			Required: true,
    47  		}, {
    48  			Name:       "password",
    49  			Help:       "Password.",
    50  			IsPassword: true,
    51  			Required:   true,
    52  		}, {
    53  			Name:     config.ConfigEncoding,
    54  			Help:     config.ConfigEncodingHelp,
    55  			Advanced: true,
    56  			// List of replaced characters:
    57  			//   < (less than)     -> '<' // FULLWIDTH LESS-THAN SIGN
    58  			//   > (greater than)  -> '>' // FULLWIDTH GREATER-THAN SIGN
    59  			//   : (colon)         -> ':' // FULLWIDTH COLON
    60  			//   " (double quote)  -> '"' // FULLWIDTH QUOTATION MARK
    61  			//   \ (backslash)     -> '\' // FULLWIDTH REVERSE SOLIDUS
    62  			//   | (vertical line) -> '|' // FULLWIDTH VERTICAL LINE
    63  			//   ? (question mark) -> '?' // FULLWIDTH QUESTION MARK
    64  			//   * (asterisk)      -> '*' // FULLWIDTH ASTERISK
    65  			//
    66  			// Additionally names can't begin or end with an ASCII whitespace.
    67  			// List of replaced characters:
    68  			//     (space)           -> '␠'  // SYMBOL FOR SPACE
    69  			//     (horizontal tab)  -> '␉'  // SYMBOL FOR HORIZONTAL TABULATION
    70  			//     (line feed)       -> '␊'  // SYMBOL FOR LINE FEED
    71  			//     (vertical tab)    -> '␋'  // SYMBOL FOR VERTICAL TABULATION
    72  			//     (carriage return) -> '␍'  // SYMBOL FOR CARRIAGE RETURN
    73  			//
    74  			// Also encode invalid UTF-8 bytes as json doesn't handle them properly.
    75  			//
    76  			// https://www.opendrive.com/wp-content/uploads/guides/OpenDrive_API_guide.pdf
    77  			Default: (encoder.Base |
    78  				encoder.EncodeWin |
    79  				encoder.EncodeLeftCrLfHtVt |
    80  				encoder.EncodeRightCrLfHtVt |
    81  				encoder.EncodeBackSlash |
    82  				encoder.EncodeLeftSpace |
    83  				encoder.EncodeRightSpace |
    84  				encoder.EncodeInvalidUtf8),
    85  		}, {
    86  			Name: "chunk_size",
    87  			Help: `Files will be uploaded in chunks this size.
    88  
    89  Note that these chunks are buffered in memory so increasing them will
    90  increase memory use.`,
    91  			Default:  10 * fs.MebiByte,
    92  			Advanced: true,
    93  		}},
    94  	})
    95  }
    96  
    97  // Options defines the configuration for this backend
    98  type Options struct {
    99  	UserName  string               `config:"username"`
   100  	Password  string               `config:"password"`
   101  	Enc       encoder.MultiEncoder `config:"encoding"`
   102  	ChunkSize fs.SizeSuffix        `config:"chunk_size"`
   103  }
   104  
   105  // Fs represents a remote server
   106  type Fs struct {
   107  	name     string             // name of this remote
   108  	root     string             // the path we are working on
   109  	opt      Options            // parsed options
   110  	features *fs.Features       // optional features
   111  	srv      *rest.Client       // the connection to the server
   112  	pacer    *fs.Pacer          // To pace and retry the API calls
   113  	session  UserSessionInfo    // contains the session data
   114  	dirCache *dircache.DirCache // Map of directory path to directory id
   115  }
   116  
   117  // Object describes an object
   118  type Object struct {
   119  	fs      *Fs       // what this object is part of
   120  	remote  string    // The remote path
   121  	id      string    // ID of the file
   122  	modTime time.Time // The modified time of the object if known
   123  	md5     string    // MD5 hash if known
   124  	size    int64     // Size of the object
   125  }
   126  
   127  // parsePath parses an incoming 'url'
   128  func parsePath(path string) (root string) {
   129  	root = strings.Trim(path, "/")
   130  	return
   131  }
   132  
   133  // ------------------------------------------------------------
   134  
   135  // Name of the remote (as passed into NewFs)
   136  func (f *Fs) Name() string {
   137  	return f.name
   138  }
   139  
   140  // Root of the remote (as passed into NewFs)
   141  func (f *Fs) Root() string {
   142  	return f.root
   143  }
   144  
   145  // String converts this Fs to a string
   146  func (f *Fs) String() string {
   147  	return fmt.Sprintf("OpenDrive root '%s'", f.root)
   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  // Hashes returns the supported hash sets.
   156  func (f *Fs) Hashes() hash.Set {
   157  	return hash.Set(hash.MD5)
   158  }
   159  
   160  // DirCacheFlush resets the directory cache - used in testing as an
   161  // optional interface
   162  func (f *Fs) DirCacheFlush() {
   163  	f.dirCache.ResetRoot()
   164  }
   165  
   166  // NewFs constructs an Fs from the path, bucket:path
   167  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   168  	ctx := context.Background()
   169  	// Parse config into Options struct
   170  	opt := new(Options)
   171  	err := configstruct.Set(m, opt)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	root = parsePath(root)
   176  	if opt.UserName == "" {
   177  		return nil, errors.New("username not found")
   178  	}
   179  	opt.Password, err = obscure.Reveal(opt.Password)
   180  	if err != nil {
   181  		return nil, errors.New("password could not revealed")
   182  	}
   183  	if opt.Password == "" {
   184  		return nil, errors.New("password not found")
   185  	}
   186  
   187  	f := &Fs{
   188  		name:  name,
   189  		root:  root,
   190  		opt:   *opt,
   191  		srv:   rest.NewClient(fshttp.NewClient(fs.Config)).SetErrorHandler(errorHandler),
   192  		pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   193  	}
   194  
   195  	f.dirCache = dircache.New(root, "0", f)
   196  
   197  	// set the rootURL for the REST client
   198  	f.srv.SetRoot(defaultEndpoint)
   199  
   200  	// get sessionID
   201  	var resp *http.Response
   202  	err = f.pacer.Call(func() (bool, error) {
   203  		account := Account{Username: opt.UserName, Password: opt.Password}
   204  
   205  		opts := rest.Opts{
   206  			Method: "POST",
   207  			Path:   "/session/login.json",
   208  		}
   209  		resp, err = f.srv.CallJSON(ctx, &opts, &account, &f.session)
   210  		return f.shouldRetry(resp, err)
   211  	})
   212  	if err != nil {
   213  		return nil, errors.Wrap(err, "failed to create session")
   214  	}
   215  	fs.Debugf(nil, "Starting OpenDrive session with ID: %s", f.session.SessionID)
   216  
   217  	f.features = (&fs.Features{
   218  		CaseInsensitive:         true,
   219  		CanHaveEmptyDirectories: true,
   220  	}).Fill(f)
   221  
   222  	// Find the current root
   223  	err = f.dirCache.FindRoot(ctx, false)
   224  	if err != nil {
   225  		// Assume it is a file
   226  		newRoot, remote := dircache.SplitPath(root)
   227  		tempF := *f
   228  		tempF.dirCache = dircache.New(newRoot, "0", &tempF)
   229  		tempF.root = newRoot
   230  
   231  		// Make new Fs which is the parent
   232  		err = tempF.dirCache.FindRoot(ctx, false)
   233  		if err != nil {
   234  			// No root so return old f
   235  			return f, nil
   236  		}
   237  		_, err := tempF.newObjectWithInfo(ctx, remote, nil)
   238  		if err != nil {
   239  			if err == fs.ErrorObjectNotFound {
   240  				// File doesn't exist so return old f
   241  				return f, nil
   242  			}
   243  			return nil, err
   244  		}
   245  		// XXX: update the old f here instead of returning tempF, since
   246  		// `features` were already filled with functions having *f as a receiver.
   247  		// See https://github.com/rclone/rclone/issues/2182
   248  		f.dirCache = tempF.dirCache
   249  		f.root = tempF.root
   250  		// return an error with an fs which points to the parent
   251  		return f, fs.ErrorIsFile
   252  	}
   253  	return f, nil
   254  }
   255  
   256  // rootSlash returns root with a slash on if it is empty, otherwise empty string
   257  func (f *Fs) rootSlash() string {
   258  	if f.root == "" {
   259  		return f.root
   260  	}
   261  	return f.root + "/"
   262  }
   263  
   264  // errorHandler parses a non 2xx error response into an error
   265  func errorHandler(resp *http.Response) error {
   266  	errResponse := new(Error)
   267  	err := rest.DecodeJSON(resp, &errResponse)
   268  	if err != nil {
   269  		fs.Debugf(nil, "Couldn't decode error response: %v", err)
   270  	}
   271  	if errResponse.Info.Code == 0 {
   272  		errResponse.Info.Code = resp.StatusCode
   273  	}
   274  	if errResponse.Info.Message == "" {
   275  		errResponse.Info.Message = "Unknown " + resp.Status
   276  	}
   277  	return errResponse
   278  }
   279  
   280  // Mkdir creates the folder if it doesn't exist
   281  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   282  	// fs.Debugf(nil, "Mkdir(\"%s\")", dir)
   283  	err := f.dirCache.FindRoot(ctx, true)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	if dir != "" {
   288  		_, err = f.dirCache.FindDir(ctx, dir, true)
   289  	}
   290  	return err
   291  }
   292  
   293  // deleteObject removes an object by ID
   294  func (f *Fs) deleteObject(ctx context.Context, id string) error {
   295  	return f.pacer.Call(func() (bool, error) {
   296  		removeDirData := removeFolder{SessionID: f.session.SessionID, FolderID: id}
   297  		opts := rest.Opts{
   298  			Method:     "POST",
   299  			NoResponse: true,
   300  			Path:       "/folder/remove.json",
   301  		}
   302  		resp, err := f.srv.CallJSON(ctx, &opts, &removeDirData, nil)
   303  		return f.shouldRetry(resp, err)
   304  	})
   305  }
   306  
   307  // purgeCheck remotes the root directory, if check is set then it
   308  // refuses to do so if it has anything in
   309  func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error {
   310  	root := path.Join(f.root, dir)
   311  	if root == "" {
   312  		return errors.New("can't purge root directory")
   313  	}
   314  	dc := f.dirCache
   315  	err := dc.FindRoot(ctx, false)
   316  	if err != nil {
   317  		return err
   318  	}
   319  	rootID, err := dc.FindDir(ctx, dir, false)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	item, err := f.readMetaDataForFolderID(ctx, rootID)
   324  	if err != nil {
   325  		return err
   326  	}
   327  	if check && len(item.Files) != 0 {
   328  		return errors.New("folder not empty")
   329  	}
   330  	err = f.deleteObject(ctx, rootID)
   331  	if err != nil {
   332  		return err
   333  	}
   334  	f.dirCache.FlushDir(dir)
   335  	return nil
   336  }
   337  
   338  // Rmdir deletes the root folder
   339  //
   340  // Returns an error if it isn't empty
   341  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   342  	// fs.Debugf(nil, "Rmdir(\"%s\")", path.Join(f.root, dir))
   343  	return f.purgeCheck(ctx, dir, true)
   344  }
   345  
   346  // Precision of the remote
   347  func (f *Fs) Precision() time.Duration {
   348  	return time.Second
   349  }
   350  
   351  // Copy src to this remote using server side copy operations.
   352  //
   353  // This is stored with the remote path given
   354  //
   355  // It returns the destination Object and a possible error
   356  //
   357  // Will only be called if src.Fs().Name() == f.Name()
   358  //
   359  // If it isn't possible then return fs.ErrorCantCopy
   360  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   361  	// fs.Debugf(nil, "Copy(%v)", remote)
   362  	srcObj, ok := src.(*Object)
   363  	if !ok {
   364  		fs.Debugf(src, "Can't copy - not same remote type")
   365  		return nil, fs.ErrorCantCopy
   366  	}
   367  	err := srcObj.readMetaData(ctx)
   368  	if err != nil {
   369  		return nil, err
   370  	}
   371  
   372  	srcPath := srcObj.fs.rootSlash() + srcObj.remote
   373  	dstPath := f.rootSlash() + remote
   374  	if strings.ToLower(srcPath) == strings.ToLower(dstPath) {
   375  		return nil, errors.Errorf("Can't copy %q -> %q as are same name when lowercase", srcPath, dstPath)
   376  	}
   377  
   378  	// Create temporary object
   379  	dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size)
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	// fs.Debugf(nil, "...%#v\n...%#v", remote, directoryID)
   384  
   385  	// Copy the object
   386  	var resp *http.Response
   387  	response := moveCopyFileResponse{}
   388  	err = f.pacer.Call(func() (bool, error) {
   389  		copyFileData := moveCopyFile{
   390  			SessionID:         f.session.SessionID,
   391  			SrcFileID:         srcObj.id,
   392  			DstFolderID:       directoryID,
   393  			Move:              "false",
   394  			OverwriteIfExists: "true",
   395  			NewFileName:       leaf,
   396  		}
   397  		opts := rest.Opts{
   398  			Method: "POST",
   399  			Path:   "/file/move_copy.json",
   400  		}
   401  		resp, err = f.srv.CallJSON(ctx, &opts, &copyFileData, &response)
   402  		return f.shouldRetry(resp, err)
   403  	})
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	size, _ := strconv.ParseInt(response.Size, 10, 64)
   409  	dstObj.id = response.FileID
   410  	dstObj.size = size
   411  
   412  	return dstObj, nil
   413  }
   414  
   415  // Move src to this remote using server side move operations.
   416  //
   417  // This is stored with the remote path given
   418  //
   419  // It returns the destination Object and a possible error
   420  //
   421  // Will only be called if src.Fs().Name() == f.Name()
   422  //
   423  // If it isn't possible then return fs.ErrorCantMove
   424  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   425  	// fs.Debugf(nil, "Move(%v)", remote)
   426  	srcObj, ok := src.(*Object)
   427  	if !ok {
   428  		fs.Debugf(src, "Can't move - not same remote type")
   429  		return nil, fs.ErrorCantCopy
   430  	}
   431  	err := srcObj.readMetaData(ctx)
   432  	if err != nil {
   433  		return nil, err
   434  	}
   435  
   436  	// Create temporary object
   437  	dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size)
   438  	if err != nil {
   439  		return nil, err
   440  	}
   441  
   442  	// Copy the object
   443  	var resp *http.Response
   444  	response := moveCopyFileResponse{}
   445  	err = f.pacer.Call(func() (bool, error) {
   446  		copyFileData := moveCopyFile{
   447  			SessionID:         f.session.SessionID,
   448  			SrcFileID:         srcObj.id,
   449  			DstFolderID:       directoryID,
   450  			Move:              "true",
   451  			OverwriteIfExists: "true",
   452  			NewFileName:       leaf,
   453  		}
   454  		opts := rest.Opts{
   455  			Method: "POST",
   456  			Path:   "/file/move_copy.json",
   457  		}
   458  		resp, err = f.srv.CallJSON(ctx, &opts, &copyFileData, &response)
   459  		return f.shouldRetry(resp, err)
   460  	})
   461  	if err != nil {
   462  		return nil, err
   463  	}
   464  
   465  	size, _ := strconv.ParseInt(response.Size, 10, 64)
   466  	dstObj.id = response.FileID
   467  	dstObj.size = size
   468  
   469  	return dstObj, nil
   470  }
   471  
   472  // DirMove moves src, srcRemote to this remote at dstRemote
   473  // using server side move operations.
   474  //
   475  // Will only be called if src.Fs().Name() == f.Name()
   476  //
   477  // If it isn't possible then return fs.ErrorCantDirMove
   478  //
   479  // If destination exists then return fs.ErrorDirExists
   480  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) (err error) {
   481  	srcFs, ok := src.(*Fs)
   482  	if !ok {
   483  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   484  		return fs.ErrorCantDirMove
   485  	}
   486  	srcPath := path.Join(srcFs.root, srcRemote)
   487  	dstPath := path.Join(f.root, dstRemote)
   488  
   489  	// Refuse to move to or from the root
   490  	if srcPath == "" || dstPath == "" {
   491  		fs.Debugf(src, "DirMove error: Can't move root")
   492  		return errors.New("can't move root directory")
   493  	}
   494  
   495  	// find the root src directory
   496  	err = srcFs.dirCache.FindRoot(ctx, false)
   497  	if err != nil {
   498  		return err
   499  	}
   500  
   501  	// find the root dst directory
   502  	if dstRemote != "" {
   503  		err = f.dirCache.FindRoot(ctx, true)
   504  		if err != nil {
   505  			return err
   506  		}
   507  	} else {
   508  		if f.dirCache.FoundRoot() {
   509  			return fs.ErrorDirExists
   510  		}
   511  	}
   512  
   513  	// Find ID of dst parent, creating subdirs if necessary
   514  	var leaf, directoryID string
   515  	findPath := dstRemote
   516  	if dstRemote == "" {
   517  		findPath = f.root
   518  	}
   519  	leaf, directoryID, err = f.dirCache.FindPath(ctx, findPath, true)
   520  	if err != nil {
   521  		return err
   522  	}
   523  
   524  	// Check destination does not exist
   525  	if dstRemote != "" {
   526  		_, err = f.dirCache.FindDir(ctx, dstRemote, false)
   527  		if err == fs.ErrorDirNotFound {
   528  			// OK
   529  		} else if err != nil {
   530  			return err
   531  		} else {
   532  			return fs.ErrorDirExists
   533  		}
   534  	}
   535  
   536  	// Find ID of src
   537  	srcID, err := srcFs.dirCache.FindDir(ctx, srcRemote, false)
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	// Do the move
   543  	var resp *http.Response
   544  	response := moveCopyFolderResponse{}
   545  	err = f.pacer.Call(func() (bool, error) {
   546  		moveFolderData := moveCopyFolder{
   547  			SessionID:     f.session.SessionID,
   548  			FolderID:      srcID,
   549  			DstFolderID:   directoryID,
   550  			Move:          "true",
   551  			NewFolderName: leaf,
   552  		}
   553  		opts := rest.Opts{
   554  			Method: "POST",
   555  			Path:   "/folder/move_copy.json",
   556  		}
   557  		resp, err = f.srv.CallJSON(ctx, &opts, &moveFolderData, &response)
   558  		return f.shouldRetry(resp, err)
   559  	})
   560  	if err != nil {
   561  		fs.Debugf(src, "DirMove error %v", err)
   562  		return err
   563  	}
   564  
   565  	srcFs.dirCache.FlushDir(srcRemote)
   566  	return nil
   567  }
   568  
   569  // Purge deletes all the files and the container
   570  //
   571  // Optional interface: Only implement this if you have a way of
   572  // deleting all the files quicker than just running Remove() on the
   573  // result of List()
   574  func (f *Fs) Purge(ctx context.Context) error {
   575  	return f.purgeCheck(ctx, "", false)
   576  }
   577  
   578  // Return an Object from a path
   579  //
   580  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   581  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, file *File) (fs.Object, error) {
   582  	// fs.Debugf(nil, "newObjectWithInfo(%s, %v)", remote, file)
   583  
   584  	var o *Object
   585  	if nil != file {
   586  		o = &Object{
   587  			fs:      f,
   588  			remote:  remote,
   589  			id:      file.FileID,
   590  			modTime: time.Unix(file.DateModified, 0),
   591  			size:    file.Size,
   592  			md5:     file.FileHash,
   593  		}
   594  	} else {
   595  		o = &Object{
   596  			fs:     f,
   597  			remote: remote,
   598  		}
   599  
   600  		err := o.readMetaData(ctx)
   601  		if err != nil {
   602  			return nil, err
   603  		}
   604  	}
   605  	return o, nil
   606  }
   607  
   608  // NewObject finds the Object at remote.  If it can't be found
   609  // it returns the error fs.ErrorObjectNotFound.
   610  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   611  	// fs.Debugf(nil, "NewObject(\"%s\")", remote)
   612  	return f.newObjectWithInfo(ctx, remote, nil)
   613  }
   614  
   615  // Creates from the parameters passed in a half finished Object which
   616  // must have setMetaData called on it
   617  //
   618  // Returns the object, leaf, directoryID and error
   619  //
   620  // Used to create new objects
   621  func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) {
   622  	// Create the directory for the object if it doesn't exist
   623  	leaf, directoryID, err = f.dirCache.FindRootAndPath(ctx, remote, true)
   624  	if err != nil {
   625  		return nil, leaf, directoryID, err
   626  	}
   627  	// fs.Debugf(nil, "\n...leaf %#v\n...id %#v", leaf, directoryID)
   628  	// Temporary Object under construction
   629  	o = &Object{
   630  		fs:     f,
   631  		remote: remote,
   632  	}
   633  	return o, f.opt.Enc.FromStandardName(leaf), directoryID, nil
   634  }
   635  
   636  // readMetaDataForPath reads the metadata from the path
   637  func (f *Fs) readMetaDataForFolderID(ctx context.Context, id string) (info *FolderList, err error) {
   638  	var resp *http.Response
   639  	opts := rest.Opts{
   640  		Method: "GET",
   641  		Path:   "/folder/list.json/" + f.session.SessionID + "/" + id,
   642  	}
   643  	err = f.pacer.Call(func() (bool, error) {
   644  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &info)
   645  		return f.shouldRetry(resp, err)
   646  	})
   647  	if err != nil {
   648  		return nil, err
   649  	}
   650  	if resp != nil {
   651  	}
   652  
   653  	return info, err
   654  }
   655  
   656  // Put the object into the bucket
   657  //
   658  // Copy the reader in to the new object which is returned
   659  //
   660  // The new object may have been created if an error is returned
   661  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   662  	remote := src.Remote()
   663  	size := src.Size()
   664  	modTime := src.ModTime(ctx)
   665  
   666  	// fs.Debugf(nil, "Put(%s)", remote)
   667  
   668  	o, leaf, directoryID, err := f.createObject(ctx, remote, modTime, size)
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  
   673  	if "" == o.id {
   674  		// Attempt to read ID, ignore error
   675  		// FIXME is this correct?
   676  		_ = o.readMetaData(ctx)
   677  	}
   678  
   679  	if "" == o.id {
   680  		// We need to create an ID for this file
   681  		var resp *http.Response
   682  		response := createFileResponse{}
   683  		err := o.fs.pacer.Call(func() (bool, error) {
   684  			createFileData := createFile{
   685  				SessionID: o.fs.session.SessionID,
   686  				FolderID:  directoryID,
   687  				Name:      leaf,
   688  			}
   689  			opts := rest.Opts{
   690  				Method:  "POST",
   691  				Options: options,
   692  				Path:    "/upload/create_file.json",
   693  			}
   694  			resp, err = o.fs.srv.CallJSON(ctx, &opts, &createFileData, &response)
   695  			return o.fs.shouldRetry(resp, err)
   696  		})
   697  		if err != nil {
   698  			return nil, errors.Wrap(err, "failed to create file")
   699  		}
   700  
   701  		o.id = response.FileID
   702  	}
   703  
   704  	return o, o.Update(ctx, in, src, options...)
   705  }
   706  
   707  // retryErrorCodes is a slice of error codes that we will retry
   708  var retryErrorCodes = []int{
   709  	400, // Bad request (seen in "Next token is expired")
   710  	401, // Unauthorized (seen in "Token has expired")
   711  	408, // Request Timeout
   712  	423, // Locked - get this on folders sometimes
   713  	429, // Rate exceeded.
   714  	500, // Get occasional 500 Internal Server Error
   715  	502, // Bad Gateway when doing big listings
   716  	503, // Service Unavailable
   717  	504, // Gateway Time-out
   718  }
   719  
   720  // shouldRetry returns a boolean as to whether this resp and err
   721  // deserve to be retried.  It returns the err as a convenience
   722  func (f *Fs) shouldRetry(resp *http.Response, err error) (bool, error) {
   723  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   724  }
   725  
   726  // DirCacher methods
   727  
   728  // CreateDir makes a directory with pathID as parent and name leaf
   729  func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
   730  	// fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, replaceReservedChars(leaf))
   731  	var resp *http.Response
   732  	response := createFolderResponse{}
   733  	err = f.pacer.Call(func() (bool, error) {
   734  		createDirData := createFolder{
   735  			SessionID:           f.session.SessionID,
   736  			FolderName:          f.opt.Enc.FromStandardName(leaf),
   737  			FolderSubParent:     pathID,
   738  			FolderIsPublic:      0,
   739  			FolderPublicUpl:     0,
   740  			FolderPublicDisplay: 0,
   741  			FolderPublicDnl:     0,
   742  		}
   743  		opts := rest.Opts{
   744  			Method: "POST",
   745  			Path:   "/folder.json",
   746  		}
   747  		resp, err = f.srv.CallJSON(ctx, &opts, &createDirData, &response)
   748  		return f.shouldRetry(resp, err)
   749  	})
   750  	if err != nil {
   751  		return "", err
   752  	}
   753  
   754  	return response.FolderID, nil
   755  }
   756  
   757  // FindLeaf finds a directory of name leaf in the folder with ID pathID
   758  func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) {
   759  	// fs.Debugf(nil, "FindLeaf(\"%s\", \"%s\")", pathID, leaf)
   760  
   761  	if pathID == "0" && leaf == "" {
   762  		// fs.Debugf(nil, "Found OpenDrive root")
   763  		// that's the root directory
   764  		return pathID, true, nil
   765  	}
   766  
   767  	// get the folderIDs
   768  	var resp *http.Response
   769  	folderList := FolderList{}
   770  	err = f.pacer.Call(func() (bool, error) {
   771  		opts := rest.Opts{
   772  			Method: "GET",
   773  			Path:   "/folder/list.json/" + f.session.SessionID + "/" + pathID,
   774  		}
   775  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &folderList)
   776  		return f.shouldRetry(resp, err)
   777  	})
   778  	if err != nil {
   779  		return "", false, errors.Wrap(err, "failed to get folder list")
   780  	}
   781  
   782  	leaf = f.opt.Enc.FromStandardName(leaf)
   783  	for _, folder := range folderList.Folders {
   784  		// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
   785  
   786  		if leaf == folder.Name {
   787  			// found
   788  			return folder.FolderID, true, nil
   789  		}
   790  	}
   791  
   792  	return "", false, nil
   793  }
   794  
   795  // List the objects and directories in dir into entries.  The
   796  // entries can be returned in any order but should be for a
   797  // complete directory.
   798  //
   799  // dir should be "" to list the root, and should not have
   800  // trailing slashes.
   801  //
   802  // This should return ErrDirNotFound if the directory isn't
   803  // found.
   804  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   805  	// fs.Debugf(nil, "List(%v)", dir)
   806  	err = f.dirCache.FindRoot(ctx, false)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   811  	if err != nil {
   812  		return nil, err
   813  	}
   814  
   815  	var resp *http.Response
   816  	opts := rest.Opts{
   817  		Method: "GET",
   818  		Path:   "/folder/list.json/" + f.session.SessionID + "/" + directoryID,
   819  	}
   820  	folderList := FolderList{}
   821  	err = f.pacer.Call(func() (bool, error) {
   822  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &folderList)
   823  		return f.shouldRetry(resp, err)
   824  	})
   825  	if err != nil {
   826  		return nil, errors.Wrap(err, "failed to get folder list")
   827  	}
   828  
   829  	for _, folder := range folderList.Folders {
   830  		folder.Name = f.opt.Enc.ToStandardName(folder.Name)
   831  		// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
   832  		remote := path.Join(dir, folder.Name)
   833  		// cache the directory ID for later lookups
   834  		f.dirCache.Put(remote, folder.FolderID)
   835  		d := fs.NewDir(remote, time.Unix(folder.DateModified, 0)).SetID(folder.FolderID)
   836  		d.SetItems(int64(folder.ChildFolders))
   837  		entries = append(entries, d)
   838  	}
   839  
   840  	for _, file := range folderList.Files {
   841  		file.Name = f.opt.Enc.ToStandardName(file.Name)
   842  		// fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID)
   843  		remote := path.Join(dir, file.Name)
   844  		o, err := f.newObjectWithInfo(ctx, remote, &file)
   845  		if err != nil {
   846  			return nil, err
   847  		}
   848  		entries = append(entries, o)
   849  	}
   850  
   851  	return entries, nil
   852  }
   853  
   854  // ------------------------------------------------------------
   855  
   856  // Fs returns the parent Fs
   857  func (o *Object) Fs() fs.Info {
   858  	return o.fs
   859  }
   860  
   861  // Return a string version
   862  func (o *Object) String() string {
   863  	if o == nil {
   864  		return "<nil>"
   865  	}
   866  	return o.remote
   867  }
   868  
   869  // Remote returns the remote path
   870  func (o *Object) Remote() string {
   871  	return o.remote
   872  }
   873  
   874  // Hash returns the Md5sum of an object returning a lowercase hex string
   875  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   876  	if t != hash.MD5 {
   877  		return "", hash.ErrUnsupported
   878  	}
   879  	return o.md5, nil
   880  }
   881  
   882  // Size returns the size of an object in bytes
   883  func (o *Object) Size() int64 {
   884  	return o.size // Object is likely PENDING
   885  }
   886  
   887  // ModTime returns the modification time of the object
   888  //
   889  //
   890  // It attempts to read the objects mtime and if that isn't present the
   891  // LastModified returned in the http headers
   892  func (o *Object) ModTime(ctx context.Context) time.Time {
   893  	return o.modTime
   894  }
   895  
   896  // SetModTime sets the modification time of the local fs object
   897  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
   898  	// fs.Debugf(nil, "SetModTime(%v)", modTime.String())
   899  	opts := rest.Opts{
   900  		Method:     "PUT",
   901  		NoResponse: true,
   902  		Path:       "/file/filesettings.json",
   903  	}
   904  	update := modTimeFile{
   905  		SessionID:            o.fs.session.SessionID,
   906  		FileID:               o.id,
   907  		FileModificationTime: strconv.FormatInt(modTime.Unix(), 10),
   908  	}
   909  	err := o.fs.pacer.Call(func() (bool, error) {
   910  		resp, err := o.fs.srv.CallJSON(ctx, &opts, &update, nil)
   911  		return o.fs.shouldRetry(resp, err)
   912  	})
   913  
   914  	o.modTime = modTime
   915  
   916  	return err
   917  }
   918  
   919  // Open an object for read
   920  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
   921  	// fs.Debugf(nil, "Open(\"%v\")", o.remote)
   922  	fs.FixRangeOption(options, o.size)
   923  	opts := rest.Opts{
   924  		Method:  "GET",
   925  		Path:    "/download/file.json/" + o.id + "?session_id=" + o.fs.session.SessionID,
   926  		Options: options,
   927  	}
   928  	var resp *http.Response
   929  	err = o.fs.pacer.Call(func() (bool, error) {
   930  		resp, err = o.fs.srv.Call(ctx, &opts)
   931  		return o.fs.shouldRetry(resp, err)
   932  	})
   933  	if err != nil {
   934  		return nil, errors.Wrap(err, "failed to open file)")
   935  	}
   936  
   937  	return resp.Body, nil
   938  }
   939  
   940  // Remove an object
   941  func (o *Object) Remove(ctx context.Context) error {
   942  	// fs.Debugf(nil, "Remove(\"%s\")", o.id)
   943  	return o.fs.pacer.Call(func() (bool, error) {
   944  		opts := rest.Opts{
   945  			Method:     "DELETE",
   946  			NoResponse: true,
   947  			Path:       "/file.json/" + o.fs.session.SessionID + "/" + o.id,
   948  		}
   949  		resp, err := o.fs.srv.Call(ctx, &opts)
   950  		return o.fs.shouldRetry(resp, err)
   951  	})
   952  }
   953  
   954  // Storable returns a boolean showing whether this object storable
   955  func (o *Object) Storable() bool {
   956  	return true
   957  }
   958  
   959  // Update the object with the contents of the io.Reader, modTime and size
   960  //
   961  // The new object may have been created if an error is returned
   962  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
   963  	size := src.Size()
   964  	modTime := src.ModTime(ctx)
   965  	// fs.Debugf(nil, "Update(\"%s\", \"%s\")", o.id, o.remote)
   966  
   967  	// Open file for upload
   968  	var resp *http.Response
   969  	openResponse := openUploadResponse{}
   970  	err := o.fs.pacer.Call(func() (bool, error) {
   971  		openUploadData := openUpload{SessionID: o.fs.session.SessionID, FileID: o.id, Size: size}
   972  		// fs.Debugf(nil, "PreOpen: %#v", openUploadData)
   973  		opts := rest.Opts{
   974  			Method:  "POST",
   975  			Options: options,
   976  			Path:    "/upload/open_file_upload.json",
   977  		}
   978  		resp, err := o.fs.srv.CallJSON(ctx, &opts, &openUploadData, &openResponse)
   979  		return o.fs.shouldRetry(resp, err)
   980  	})
   981  	if err != nil {
   982  		return errors.Wrap(err, "failed to create file")
   983  	}
   984  	// resp.Body.Close()
   985  	// fs.Debugf(nil, "PostOpen: %#v", openResponse)
   986  
   987  	buf := make([]byte, o.fs.opt.ChunkSize)
   988  	chunkOffset := int64(0)
   989  	remainingBytes := size
   990  	chunkCounter := 0
   991  
   992  	for remainingBytes > 0 {
   993  		currentChunkSize := int64(o.fs.opt.ChunkSize)
   994  		if currentChunkSize > remainingBytes {
   995  			currentChunkSize = remainingBytes
   996  		}
   997  		remainingBytes -= currentChunkSize
   998  		fs.Debugf(o, "Uploading chunk %d, size=%d, remain=%d", chunkCounter, currentChunkSize, remainingBytes)
   999  
  1000  		chunk := readers.NewRepeatableLimitReaderBuffer(in, buf, currentChunkSize)
  1001  		var reply uploadFileChunkReply
  1002  		err = o.fs.pacer.Call(func() (bool, error) {
  1003  			// seek to the start in case this is a retry
  1004  			if _, err = chunk.Seek(0, io.SeekStart); err != nil {
  1005  				return false, err
  1006  			}
  1007  			opts := rest.Opts{
  1008  				Method: "POST",
  1009  				Path:   "/upload/upload_file_chunk.json",
  1010  				Body:   chunk,
  1011  				MultipartParams: url.Values{
  1012  					"session_id":    []string{o.fs.session.SessionID},
  1013  					"file_id":       []string{o.id},
  1014  					"temp_location": []string{openResponse.TempLocation},
  1015  					"chunk_offset":  []string{strconv.FormatInt(chunkOffset, 10)},
  1016  					"chunk_size":    []string{strconv.FormatInt(currentChunkSize, 10)},
  1017  				},
  1018  				MultipartContentName: "file_data", // ..name of the parameter which is the attached file
  1019  				MultipartFileName:    o.remote,    // ..name of the file for the attached file
  1020  
  1021  			}
  1022  			resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &reply)
  1023  			return o.fs.shouldRetry(resp, err)
  1024  		})
  1025  		if err != nil {
  1026  			return errors.Wrap(err, "failed to create file")
  1027  		}
  1028  		if reply.TotalWritten != currentChunkSize {
  1029  			return errors.Errorf("failed to create file: incomplete write of %d/%d bytes", reply.TotalWritten, currentChunkSize)
  1030  		}
  1031  
  1032  		chunkCounter++
  1033  		chunkOffset += currentChunkSize
  1034  	}
  1035  
  1036  	// Close file for upload
  1037  	closeResponse := closeUploadResponse{}
  1038  	err = o.fs.pacer.Call(func() (bool, error) {
  1039  		closeUploadData := closeUpload{SessionID: o.fs.session.SessionID, FileID: o.id, Size: size, TempLocation: openResponse.TempLocation}
  1040  		// fs.Debugf(nil, "PreClose: %#v", closeUploadData)
  1041  		opts := rest.Opts{
  1042  			Method: "POST",
  1043  			Path:   "/upload/close_file_upload.json",
  1044  		}
  1045  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &closeUploadData, &closeResponse)
  1046  		return o.fs.shouldRetry(resp, err)
  1047  	})
  1048  	if err != nil {
  1049  		return errors.Wrap(err, "failed to create file")
  1050  	}
  1051  	// fs.Debugf(nil, "PostClose: %#v", closeResponse)
  1052  
  1053  	o.id = closeResponse.FileID
  1054  	o.size = closeResponse.Size
  1055  
  1056  	// Set the mod time now
  1057  	err = o.SetModTime(ctx, modTime)
  1058  	if err != nil {
  1059  		return err
  1060  	}
  1061  
  1062  	// Set permissions
  1063  	err = o.fs.pacer.Call(func() (bool, error) {
  1064  		update := permissions{SessionID: o.fs.session.SessionID, FileID: o.id, FileIsPublic: 0}
  1065  		// fs.Debugf(nil, "Permissions : %#v", update)
  1066  		opts := rest.Opts{
  1067  			Method:     "POST",
  1068  			NoResponse: true,
  1069  			Path:       "/file/access.json",
  1070  		}
  1071  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &update, nil)
  1072  		return o.fs.shouldRetry(resp, err)
  1073  	})
  1074  	if err != nil {
  1075  		return err
  1076  	}
  1077  
  1078  	return o.readMetaData(ctx)
  1079  }
  1080  
  1081  func (o *Object) readMetaData(ctx context.Context) (err error) {
  1082  	leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, o.remote, false)
  1083  	if err != nil {
  1084  		if err == fs.ErrorDirNotFound {
  1085  			return fs.ErrorObjectNotFound
  1086  		}
  1087  		return err
  1088  	}
  1089  	var resp *http.Response
  1090  	folderList := FolderList{}
  1091  	err = o.fs.pacer.Call(func() (bool, error) {
  1092  		opts := rest.Opts{
  1093  			Method: "GET",
  1094  			Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s",
  1095  				o.fs.session.SessionID, directoryID, url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf))),
  1096  		}
  1097  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList)
  1098  		return o.fs.shouldRetry(resp, err)
  1099  	})
  1100  	if err != nil {
  1101  		return errors.Wrap(err, "failed to get folder list")
  1102  	}
  1103  
  1104  	if len(folderList.Files) == 0 {
  1105  		return fs.ErrorObjectNotFound
  1106  	}
  1107  
  1108  	leafFile := folderList.Files[0]
  1109  	o.id = leafFile.FileID
  1110  	o.modTime = time.Unix(leafFile.DateModified, 0)
  1111  	o.md5 = leafFile.FileHash
  1112  	o.size = leafFile.Size
  1113  
  1114  	return nil
  1115  }
  1116  
  1117  // ID returns the ID of the Object if known, or "" if not
  1118  func (o *Object) ID() string {
  1119  	return o.id
  1120  }
  1121  
  1122  // Check the interfaces are satisfied
  1123  var (
  1124  	_ fs.Fs              = (*Fs)(nil)
  1125  	_ fs.Purger          = (*Fs)(nil)
  1126  	_ fs.Copier          = (*Fs)(nil)
  1127  	_ fs.Mover           = (*Fs)(nil)
  1128  	_ fs.DirMover        = (*Fs)(nil)
  1129  	_ fs.DirCacheFlusher = (*Fs)(nil)
  1130  	_ fs.Object          = (*Object)(nil)
  1131  	_ fs.IDer            = (*Object)(nil)
  1132  )