github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/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 a 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 a 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  				Path:   "/upload/create_file.json",
   692  			}
   693  			resp, err = o.fs.srv.CallJSON(ctx, &opts, &createFileData, &response)
   694  			return o.fs.shouldRetry(resp, err)
   695  		})
   696  		if err != nil {
   697  			return nil, errors.Wrap(err, "failed to create file")
   698  		}
   699  
   700  		o.id = response.FileID
   701  	}
   702  
   703  	return o, o.Update(ctx, in, src, options...)
   704  }
   705  
   706  // retryErrorCodes is a slice of error codes that we will retry
   707  var retryErrorCodes = []int{
   708  	400, // Bad request (seen in "Next token is expired")
   709  	401, // Unauthorized (seen in "Token has expired")
   710  	408, // Request Timeout
   711  	423, // Locked - get this on folders sometimes
   712  	429, // Rate exceeded.
   713  	500, // Get occasional 500 Internal Server Error
   714  	502, // Bad Gateway when doing big listings
   715  	503, // Service Unavailable
   716  	504, // Gateway Time-out
   717  }
   718  
   719  // shouldRetry returns a boolean as to whether this resp and err
   720  // deserve to be retried.  It returns the err as a convenience
   721  func (f *Fs) shouldRetry(resp *http.Response, err error) (bool, error) {
   722  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   723  }
   724  
   725  // DirCacher methods
   726  
   727  // CreateDir makes a directory with pathID as parent and name leaf
   728  func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
   729  	// fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, replaceReservedChars(leaf))
   730  	var resp *http.Response
   731  	response := createFolderResponse{}
   732  	err = f.pacer.Call(func() (bool, error) {
   733  		createDirData := createFolder{
   734  			SessionID:           f.session.SessionID,
   735  			FolderName:          f.opt.Enc.FromStandardName(leaf),
   736  			FolderSubParent:     pathID,
   737  			FolderIsPublic:      0,
   738  			FolderPublicUpl:     0,
   739  			FolderPublicDisplay: 0,
   740  			FolderPublicDnl:     0,
   741  		}
   742  		opts := rest.Opts{
   743  			Method: "POST",
   744  			Path:   "/folder.json",
   745  		}
   746  		resp, err = f.srv.CallJSON(ctx, &opts, &createDirData, &response)
   747  		return f.shouldRetry(resp, err)
   748  	})
   749  	if err != nil {
   750  		return "", err
   751  	}
   752  
   753  	return response.FolderID, nil
   754  }
   755  
   756  // FindLeaf finds a directory of name leaf in the folder with ID pathID
   757  func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) {
   758  	// fs.Debugf(nil, "FindLeaf(\"%s\", \"%s\")", pathID, leaf)
   759  
   760  	if pathID == "0" && leaf == "" {
   761  		// fs.Debugf(nil, "Found OpenDrive root")
   762  		// that's the root directory
   763  		return pathID, true, nil
   764  	}
   765  
   766  	// get the folderIDs
   767  	var resp *http.Response
   768  	folderList := FolderList{}
   769  	err = f.pacer.Call(func() (bool, error) {
   770  		opts := rest.Opts{
   771  			Method: "GET",
   772  			Path:   "/folder/list.json/" + f.session.SessionID + "/" + pathID,
   773  		}
   774  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &folderList)
   775  		return f.shouldRetry(resp, err)
   776  	})
   777  	if err != nil {
   778  		return "", false, errors.Wrap(err, "failed to get folder list")
   779  	}
   780  
   781  	leaf = f.opt.Enc.FromStandardName(leaf)
   782  	for _, folder := range folderList.Folders {
   783  		// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
   784  
   785  		if leaf == folder.Name {
   786  			// found
   787  			return folder.FolderID, true, nil
   788  		}
   789  	}
   790  
   791  	return "", false, nil
   792  }
   793  
   794  // List the objects and directories in dir into entries.  The
   795  // entries can be returned in any order but should be for a
   796  // complete directory.
   797  //
   798  // dir should be "" to list the root, and should not have
   799  // trailing slashes.
   800  //
   801  // This should return ErrDirNotFound if the directory isn't
   802  // found.
   803  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   804  	// fs.Debugf(nil, "List(%v)", dir)
   805  	err = f.dirCache.FindRoot(ctx, false)
   806  	if err != nil {
   807  		return nil, err
   808  	}
   809  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   810  	if err != nil {
   811  		return nil, err
   812  	}
   813  
   814  	var resp *http.Response
   815  	opts := rest.Opts{
   816  		Method: "GET",
   817  		Path:   "/folder/list.json/" + f.session.SessionID + "/" + directoryID,
   818  	}
   819  	folderList := FolderList{}
   820  	err = f.pacer.Call(func() (bool, error) {
   821  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &folderList)
   822  		return f.shouldRetry(resp, err)
   823  	})
   824  	if err != nil {
   825  		return nil, errors.Wrap(err, "failed to get folder list")
   826  	}
   827  
   828  	for _, folder := range folderList.Folders {
   829  		folder.Name = f.opt.Enc.ToStandardName(folder.Name)
   830  		// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
   831  		remote := path.Join(dir, folder.Name)
   832  		// cache the directory ID for later lookups
   833  		f.dirCache.Put(remote, folder.FolderID)
   834  		d := fs.NewDir(remote, time.Unix(folder.DateModified, 0)).SetID(folder.FolderID)
   835  		d.SetItems(int64(folder.ChildFolders))
   836  		entries = append(entries, d)
   837  	}
   838  
   839  	for _, file := range folderList.Files {
   840  		file.Name = f.opt.Enc.ToStandardName(file.Name)
   841  		// fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID)
   842  		remote := path.Join(dir, file.Name)
   843  		o, err := f.newObjectWithInfo(ctx, remote, &file)
   844  		if err != nil {
   845  			return nil, err
   846  		}
   847  		entries = append(entries, o)
   848  	}
   849  
   850  	return entries, nil
   851  }
   852  
   853  // ------------------------------------------------------------
   854  
   855  // Fs returns the parent Fs
   856  func (o *Object) Fs() fs.Info {
   857  	return o.fs
   858  }
   859  
   860  // Return a string version
   861  func (o *Object) String() string {
   862  	if o == nil {
   863  		return "<nil>"
   864  	}
   865  	return o.remote
   866  }
   867  
   868  // Remote returns the remote path
   869  func (o *Object) Remote() string {
   870  	return o.remote
   871  }
   872  
   873  // Hash returns the Md5sum of an object returning a lowercase hex string
   874  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
   875  	if t != hash.MD5 {
   876  		return "", hash.ErrUnsupported
   877  	}
   878  	return o.md5, nil
   879  }
   880  
   881  // Size returns the size of an object in bytes
   882  func (o *Object) Size() int64 {
   883  	return o.size // Object is likely PENDING
   884  }
   885  
   886  // ModTime returns the modification time of the object
   887  //
   888  //
   889  // It attempts to read the objects mtime and if that isn't present the
   890  // LastModified returned in the http headers
   891  func (o *Object) ModTime(ctx context.Context) time.Time {
   892  	return o.modTime
   893  }
   894  
   895  // SetModTime sets the modification time of the local fs object
   896  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
   897  	// fs.Debugf(nil, "SetModTime(%v)", modTime.String())
   898  	opts := rest.Opts{
   899  		Method:     "PUT",
   900  		NoResponse: true,
   901  		Path:       "/file/filesettings.json",
   902  	}
   903  	update := modTimeFile{
   904  		SessionID:            o.fs.session.SessionID,
   905  		FileID:               o.id,
   906  		FileModificationTime: strconv.FormatInt(modTime.Unix(), 10),
   907  	}
   908  	err := o.fs.pacer.Call(func() (bool, error) {
   909  		resp, err := o.fs.srv.CallJSON(ctx, &opts, &update, nil)
   910  		return o.fs.shouldRetry(resp, err)
   911  	})
   912  
   913  	o.modTime = modTime
   914  
   915  	return err
   916  }
   917  
   918  // Open an object for read
   919  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
   920  	// fs.Debugf(nil, "Open(\"%v\")", o.remote)
   921  	fs.FixRangeOption(options, o.size)
   922  	opts := rest.Opts{
   923  		Method:  "GET",
   924  		Path:    "/download/file.json/" + o.id + "?session_id=" + o.fs.session.SessionID,
   925  		Options: options,
   926  	}
   927  	var resp *http.Response
   928  	err = o.fs.pacer.Call(func() (bool, error) {
   929  		resp, err = o.fs.srv.Call(ctx, &opts)
   930  		return o.fs.shouldRetry(resp, err)
   931  	})
   932  	if err != nil {
   933  		return nil, errors.Wrap(err, "failed to open file)")
   934  	}
   935  
   936  	return resp.Body, nil
   937  }
   938  
   939  // Remove an object
   940  func (o *Object) Remove(ctx context.Context) error {
   941  	// fs.Debugf(nil, "Remove(\"%s\")", o.id)
   942  	return o.fs.pacer.Call(func() (bool, error) {
   943  		opts := rest.Opts{
   944  			Method:     "DELETE",
   945  			NoResponse: true,
   946  			Path:       "/file.json/" + o.fs.session.SessionID + "/" + o.id,
   947  		}
   948  		resp, err := o.fs.srv.Call(ctx, &opts)
   949  		return o.fs.shouldRetry(resp, err)
   950  	})
   951  }
   952  
   953  // Storable returns a boolean showing whether this object storable
   954  func (o *Object) Storable() bool {
   955  	return true
   956  }
   957  
   958  // Update the object with the contents of the io.Reader, modTime and size
   959  //
   960  // The new object may have been created if an error is returned
   961  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
   962  	size := src.Size()
   963  	modTime := src.ModTime(ctx)
   964  	// fs.Debugf(nil, "Update(\"%s\", \"%s\")", o.id, o.remote)
   965  
   966  	// Open file for upload
   967  	var resp *http.Response
   968  	openResponse := openUploadResponse{}
   969  	err := o.fs.pacer.Call(func() (bool, error) {
   970  		openUploadData := openUpload{SessionID: o.fs.session.SessionID, FileID: o.id, Size: size}
   971  		// fs.Debugf(nil, "PreOpen: %#v", openUploadData)
   972  		opts := rest.Opts{
   973  			Method: "POST",
   974  			Path:   "/upload/open_file_upload.json",
   975  		}
   976  		resp, err := o.fs.srv.CallJSON(ctx, &opts, &openUploadData, &openResponse)
   977  		return o.fs.shouldRetry(resp, err)
   978  	})
   979  	if err != nil {
   980  		return errors.Wrap(err, "failed to create file")
   981  	}
   982  	// resp.Body.Close()
   983  	// fs.Debugf(nil, "PostOpen: %#v", openResponse)
   984  
   985  	buf := make([]byte, o.fs.opt.ChunkSize)
   986  	chunkOffset := int64(0)
   987  	remainingBytes := size
   988  	chunkCounter := 0
   989  
   990  	for remainingBytes > 0 {
   991  		currentChunkSize := int64(o.fs.opt.ChunkSize)
   992  		if currentChunkSize > remainingBytes {
   993  			currentChunkSize = remainingBytes
   994  		}
   995  		remainingBytes -= currentChunkSize
   996  		fs.Debugf(o, "Uploading chunk %d, size=%d, remain=%d", chunkCounter, currentChunkSize, remainingBytes)
   997  
   998  		chunk := readers.NewRepeatableLimitReaderBuffer(in, buf, currentChunkSize)
   999  		var reply uploadFileChunkReply
  1000  		err = o.fs.pacer.Call(func() (bool, error) {
  1001  			// seek to the start in case this is a retry
  1002  			if _, err = chunk.Seek(0, io.SeekStart); err != nil {
  1003  				return false, err
  1004  			}
  1005  			opts := rest.Opts{
  1006  				Method: "POST",
  1007  				Path:   "/upload/upload_file_chunk.json",
  1008  				Body:   chunk,
  1009  				MultipartParams: url.Values{
  1010  					"session_id":    []string{o.fs.session.SessionID},
  1011  					"file_id":       []string{o.id},
  1012  					"temp_location": []string{openResponse.TempLocation},
  1013  					"chunk_offset":  []string{strconv.FormatInt(chunkOffset, 10)},
  1014  					"chunk_size":    []string{strconv.FormatInt(currentChunkSize, 10)},
  1015  				},
  1016  				MultipartContentName: "file_data", // ..name of the parameter which is the attached file
  1017  				MultipartFileName:    o.remote,    // ..name of the file for the attached file
  1018  
  1019  			}
  1020  			resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &reply)
  1021  			return o.fs.shouldRetry(resp, err)
  1022  		})
  1023  		if err != nil {
  1024  			return errors.Wrap(err, "failed to create file")
  1025  		}
  1026  		if reply.TotalWritten != currentChunkSize {
  1027  			return errors.Errorf("failed to create file: incomplete write of %d/%d bytes", reply.TotalWritten, currentChunkSize)
  1028  		}
  1029  
  1030  		chunkCounter++
  1031  		chunkOffset += currentChunkSize
  1032  	}
  1033  
  1034  	// Close file for upload
  1035  	closeResponse := closeUploadResponse{}
  1036  	err = o.fs.pacer.Call(func() (bool, error) {
  1037  		closeUploadData := closeUpload{SessionID: o.fs.session.SessionID, FileID: o.id, Size: size, TempLocation: openResponse.TempLocation}
  1038  		// fs.Debugf(nil, "PreClose: %#v", closeUploadData)
  1039  		opts := rest.Opts{
  1040  			Method: "POST",
  1041  			Path:   "/upload/close_file_upload.json",
  1042  		}
  1043  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &closeUploadData, &closeResponse)
  1044  		return o.fs.shouldRetry(resp, err)
  1045  	})
  1046  	if err != nil {
  1047  		return errors.Wrap(err, "failed to create file")
  1048  	}
  1049  	// fs.Debugf(nil, "PostClose: %#v", closeResponse)
  1050  
  1051  	o.id = closeResponse.FileID
  1052  	o.size = closeResponse.Size
  1053  
  1054  	// Set the mod time now
  1055  	err = o.SetModTime(ctx, modTime)
  1056  	if err != nil {
  1057  		return err
  1058  	}
  1059  
  1060  	// Set permissions
  1061  	err = o.fs.pacer.Call(func() (bool, error) {
  1062  		update := permissions{SessionID: o.fs.session.SessionID, FileID: o.id, FileIsPublic: 0}
  1063  		// fs.Debugf(nil, "Permissions : %#v", update)
  1064  		opts := rest.Opts{
  1065  			Method:     "POST",
  1066  			NoResponse: true,
  1067  			Path:       "/file/access.json",
  1068  		}
  1069  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &update, nil)
  1070  		return o.fs.shouldRetry(resp, err)
  1071  	})
  1072  	if err != nil {
  1073  		return err
  1074  	}
  1075  
  1076  	return o.readMetaData(ctx)
  1077  }
  1078  
  1079  func (o *Object) readMetaData(ctx context.Context) (err error) {
  1080  	leaf, directoryID, err := o.fs.dirCache.FindRootAndPath(ctx, o.remote, false)
  1081  	if err != nil {
  1082  		if err == fs.ErrorDirNotFound {
  1083  			return fs.ErrorObjectNotFound
  1084  		}
  1085  		return err
  1086  	}
  1087  	var resp *http.Response
  1088  	folderList := FolderList{}
  1089  	err = o.fs.pacer.Call(func() (bool, error) {
  1090  		opts := rest.Opts{
  1091  			Method: "GET",
  1092  			Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s",
  1093  				o.fs.session.SessionID, directoryID, url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf))),
  1094  		}
  1095  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList)
  1096  		return o.fs.shouldRetry(resp, err)
  1097  	})
  1098  	if err != nil {
  1099  		return errors.Wrap(err, "failed to get folder list")
  1100  	}
  1101  
  1102  	if len(folderList.Files) == 0 {
  1103  		return fs.ErrorObjectNotFound
  1104  	}
  1105  
  1106  	leafFile := folderList.Files[0]
  1107  	o.id = leafFile.FileID
  1108  	o.modTime = time.Unix(leafFile.DateModified, 0)
  1109  	o.md5 = leafFile.FileHash
  1110  	o.size = leafFile.Size
  1111  
  1112  	return nil
  1113  }
  1114  
  1115  // ID returns the ID of the Object if known, or "" if not
  1116  func (o *Object) ID() string {
  1117  	return o.id
  1118  }
  1119  
  1120  // Check the interfaces are satisfied
  1121  var (
  1122  	_ fs.Fs              = (*Fs)(nil)
  1123  	_ fs.Purger          = (*Fs)(nil)
  1124  	_ fs.Copier          = (*Fs)(nil)
  1125  	_ fs.Mover           = (*Fs)(nil)
  1126  	_ fs.DirMover        = (*Fs)(nil)
  1127  	_ fs.DirCacheFlusher = (*Fs)(nil)
  1128  	_ fs.Object          = (*Object)(nil)
  1129  	_ fs.IDer            = (*Object)(nil)
  1130  )