github.com/artpar/rclone@v1.67.3/backend/sharefile/sharefile.go (about)

     1  // Package sharefile provides an interface to the Citrix Sharefile
     2  // object storage system.
     3  package sharefile
     4  
     5  //go:generate ./update-timezone.sh
     6  
     7  /* NOTES
     8  
     9  ## for docs
    10  
    11  Detail standard/chunked/streaming uploads?
    12  
    13  ## Bugs in API
    14  
    15  The times in updateItem are being parsed in EST/DST local time
    16  updateItem only sets times accurate to 1 second
    17  
    18  https://community.sharefilesupport.com/citrixsharefile/topics/bug-report-for-update-item-patch-items-id-setting-clientmodifieddate-ignores-timezone-and-milliseconds
    19  
    20  When doing a rename+move directory, the server appears to do the
    21  rename first in the local directory which can overwrite files of the
    22  same name in the local directory.
    23  
    24  https://community.sharefilesupport.com/citrixsharefile/topics/bug-report-for-update-item-patch-items-id-file-overwrite-under-certain-conditions
    25  
    26  The Copy command can't change the name at the same time which means we
    27  have to copy via a temporary directory.
    28  
    29  https://community.sharefilesupport.com/citrixsharefile/topics/copy-item-needs-to-be-able-to-set-a-new-name
    30  
    31  ## Allowed characters
    32  
    33  https://api.sharefile.com/rest/index/odata.aspx
    34  
    35  $select to limit returned fields
    36  https://www.odata.org/documentation/odata-version-3-0/odata-version-3-0-core-protocol/#theselectsystemqueryoption
    37  
    38  Also $filter to select only things we need
    39  
    40  https://support.citrix.com/article/CTX234774
    41  
    42  The following characters should not be used in folder or file names.
    43  
    44  \
    45  /
    46  .
    47  ,
    48  :
    49  ;
    50  *
    51  ?
    52  "
    53  <
    54  >
    55  A filename ending with a period without an extension
    56  File names with leading or trailing whitespaces.
    57  
    58  
    59  // sharefile
    60  stringNeedsEscaping = []byte{
    61  	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x2A, 0x2E, 0x2F, 0x3A, 0x3C, 0x3E, 0x3F, 0x7C, 0xEFBCBC
    62  }
    63  maxFileLength = 256
    64  canWriteUnnormalized = true
    65  canReadUnnormalized   = true
    66  canReadRenormalized   = false
    67  canStream = true
    68  
    69  Which is control chars + [' ', '*', '.', '/', ':', '<', '>', '?', '|']
    70  - also \ and "
    71  
    72  */
    73  
    74  import (
    75  	"context"
    76  	"encoding/json"
    77  	"errors"
    78  	"fmt"
    79  	"io"
    80  	"net/http"
    81  	"net/url"
    82  	"path"
    83  	"strings"
    84  	"time"
    85  
    86  	"github.com/artpar/rclone/backend/sharefile/api"
    87  	"github.com/artpar/rclone/fs"
    88  	"github.com/artpar/rclone/fs/config"
    89  	"github.com/artpar/rclone/fs/config/configmap"
    90  	"github.com/artpar/rclone/fs/config/configstruct"
    91  	"github.com/artpar/rclone/fs/config/obscure"
    92  	"github.com/artpar/rclone/fs/fserrors"
    93  	"github.com/artpar/rclone/fs/hash"
    94  	"github.com/artpar/rclone/lib/dircache"
    95  	"github.com/artpar/rclone/lib/encoder"
    96  	"github.com/artpar/rclone/lib/oauthutil"
    97  	"github.com/artpar/rclone/lib/pacer"
    98  	"github.com/artpar/rclone/lib/random"
    99  	"github.com/artpar/rclone/lib/rest"
   100  	"golang.org/x/oauth2"
   101  )
   102  
   103  const (
   104  	rcloneClientID              = "djQUPlHTUM9EvayYBWuKC5IrVIoQde46"
   105  	rcloneEncryptedClientSecret = "v7572bKhUindQL3yDnUAebmgP-QxiwT38JLxVPolcZBl6SSs329MtFzH73x7BeELmMVZtneUPvALSopUZ6VkhQ"
   106  	minSleep                    = 10 * time.Millisecond
   107  	maxSleep                    = 2 * time.Second
   108  	decayConstant               = 2              // bigger for slower decay, exponential
   109  	apiPath                     = "/sf/v3"       // add to endpoint to get API path
   110  	tokenPath                   = "/oauth/token" // add to endpoint to get Token path
   111  	minChunkSize                = 256 * fs.Kibi
   112  	maxChunkSize                = 2 * fs.Gibi
   113  	defaultChunkSize            = 64 * fs.Mebi
   114  	defaultUploadCutoff         = 128 * fs.Mebi
   115  )
   116  
   117  // Generate a new oauth2 config which we will update when we know the TokenURL
   118  func newOauthConfig(tokenURL string) *oauth2.Config {
   119  	return &oauth2.Config{
   120  		Scopes: nil,
   121  		Endpoint: oauth2.Endpoint{
   122  			AuthURL:  "https://secure.sharefile.com/oauth/authorize",
   123  			TokenURL: tokenURL,
   124  		},
   125  		ClientID:     rcloneClientID,
   126  		ClientSecret: obscure.MustReveal(rcloneEncryptedClientSecret),
   127  		RedirectURL:  oauthutil.RedirectPublicSecureURL,
   128  	}
   129  }
   130  
   131  // Register with Fs
   132  func init() {
   133  	fs.Register(&fs.RegInfo{
   134  		Name:        "sharefile",
   135  		Description: "Citrix Sharefile",
   136  		NewFs:       NewFs,
   137  		Config: func(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
   138  			oauthConfig := newOauthConfig("")
   139  			checkAuth := func(oauthConfig *oauth2.Config, auth *oauthutil.AuthResult) error {
   140  				if auth == nil || auth.Form == nil {
   141  					return errors.New("endpoint not found in response")
   142  				}
   143  				subdomain := auth.Form.Get("subdomain")
   144  				apicp := auth.Form.Get("apicp")
   145  				if subdomain == "" || apicp == "" {
   146  					return fmt.Errorf("subdomain or apicp not found in response: %+v", auth.Form)
   147  				}
   148  				endpoint := "https://" + subdomain + "." + apicp
   149  				m.Set("endpoint", endpoint)
   150  				oauthConfig.Endpoint.TokenURL = endpoint + tokenPath
   151  				return nil
   152  			}
   153  			return oauthutil.ConfigOut("", &oauthutil.Options{
   154  				OAuth2Config: oauthConfig,
   155  				CheckAuth:    checkAuth,
   156  			})
   157  		},
   158  		Options: append(oauthutil.SharedOptions, []fs.Option{{
   159  			Name:     "upload_cutoff",
   160  			Help:     "Cutoff for switching to multipart upload.",
   161  			Default:  defaultUploadCutoff,
   162  			Advanced: true,
   163  		}, {
   164  			Name: "root_folder_id",
   165  			Help: `ID of the root folder.
   166  
   167  Leave blank to access "Personal Folders".  You can use one of the
   168  standard values here or any folder ID (long hex number ID).`,
   169  			Examples: []fs.OptionExample{{
   170  				Value: "",
   171  				Help:  `Access the Personal Folders (default).`,
   172  			}, {
   173  				Value: "favorites",
   174  				Help:  "Access the Favorites folder.",
   175  			}, {
   176  				Value: "allshared",
   177  				Help:  "Access all the shared folders.",
   178  			}, {
   179  				Value: "connectors",
   180  				Help:  "Access all the individual connectors.",
   181  			}, {
   182  				Value: "top",
   183  				Help:  "Access the home, favorites, and shared folders as well as the connectors.",
   184  			}},
   185  			Sensitive: true,
   186  		}, {
   187  			Name:    "chunk_size",
   188  			Default: defaultChunkSize,
   189  			Help: `Upload chunk size.
   190  
   191  Must a power of 2 >= 256k.
   192  
   193  Making this larger will improve performance, but note that each chunk
   194  is buffered in memory one per transfer.
   195  
   196  Reducing this will reduce memory usage but decrease performance.`,
   197  			Advanced: true,
   198  		}, {
   199  			Name: "endpoint",
   200  			Help: `Endpoint for API calls.
   201  
   202  This is usually auto discovered as part of the oauth process, but can
   203  be set manually to something like: https://XXX.sharefile.com
   204  `,
   205  			Advanced: true,
   206  			Default:  "",
   207  		}, {
   208  			Name:     config.ConfigEncoding,
   209  			Help:     config.ConfigEncodingHelp,
   210  			Advanced: true,
   211  			Default: (encoder.Base |
   212  				encoder.EncodeWin | // :?"*<>|
   213  				encoder.EncodeBackSlash | // \
   214  				encoder.EncodeCtl |
   215  				encoder.EncodeRightSpace |
   216  				encoder.EncodeRightPeriod |
   217  				encoder.EncodeLeftSpace |
   218  				encoder.EncodeLeftPeriod |
   219  				encoder.EncodeInvalidUtf8),
   220  		}}...),
   221  	})
   222  }
   223  
   224  // Options defines the configuration for this backend
   225  type Options struct {
   226  	RootFolderID string               `config:"root_folder_id"`
   227  	UploadCutoff fs.SizeSuffix        `config:"upload_cutoff"`
   228  	ChunkSize    fs.SizeSuffix        `config:"chunk_size"`
   229  	Endpoint     string               `config:"endpoint"`
   230  	Enc          encoder.MultiEncoder `config:"encoding"`
   231  }
   232  
   233  // Fs represents a remote cloud storage system
   234  type Fs struct {
   235  	name         string             // name of this remote
   236  	root         string             // the path we are working on
   237  	opt          Options            // parsed options
   238  	ci           *fs.ConfigInfo     // global config
   239  	features     *fs.Features       // optional features
   240  	srv          *rest.Client       // the connection to the server
   241  	dirCache     *dircache.DirCache // Map of directory path to directory id
   242  	pacer        *fs.Pacer          // pacer for API calls
   243  	bufferTokens chan []byte        // control concurrency of multipart uploads
   244  	tokenRenewer *oauthutil.Renew   // renew the token on expiry
   245  	rootID       string             // ID of the users root folder
   246  	location     *time.Location     // timezone of server for SetModTime workaround
   247  }
   248  
   249  // Object describes a file
   250  type Object struct {
   251  	fs          *Fs       // what this object is part of
   252  	remote      string    // The remote path
   253  	hasMetaData bool      // metadata is present and correct
   254  	size        int64     // size of the object
   255  	modTime     time.Time // modification time of the object
   256  	id          string    // ID of the object
   257  	md5         string    // hash of the object
   258  }
   259  
   260  // ------------------------------------------------------------
   261  
   262  // Name of the remote (as passed into NewFs)
   263  func (f *Fs) Name() string {
   264  	return f.name
   265  }
   266  
   267  // Root of the remote (as passed into NewFs)
   268  func (f *Fs) Root() string {
   269  	return f.root
   270  }
   271  
   272  // String converts this Fs to a string
   273  func (f *Fs) String() string {
   274  	return fmt.Sprintf("sharefile root '%s'", f.root)
   275  }
   276  
   277  // Features returns the optional features of this Fs
   278  func (f *Fs) Features() *fs.Features {
   279  	return f.features
   280  }
   281  
   282  // parsePath parses a sharefile 'url'
   283  func parsePath(path string) (root string) {
   284  	root = strings.Trim(path, "/")
   285  	return
   286  }
   287  
   288  // retryErrorCodes is a slice of error codes that we will retry
   289  var retryErrorCodes = []int{
   290  	429, // Too Many Requests.
   291  	500, // Internal Server Error
   292  	502, // Bad Gateway
   293  	503, // Service Unavailable
   294  	504, // Gateway Timeout
   295  	509, // Bandwidth Limit Exceeded
   296  }
   297  
   298  // shouldRetry returns a boolean as to whether this resp and err
   299  // deserve to be retried.  It returns the err as a convenience
   300  func shouldRetry(ctx context.Context, resp *http.Response, err error) (bool, error) {
   301  	if fserrors.ContextError(ctx, &err) {
   302  		return false, err
   303  	}
   304  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   305  }
   306  
   307  // Reads the metadata for the id passed in.  If id is "" then it returns the root
   308  // if path is not "" then the item read use id as the root and the path is relative
   309  func (f *Fs) readMetaDataForIDPath(ctx context.Context, id, path string, directoriesOnly bool, filesOnly bool) (info *api.Item, err error) {
   310  	opts := rest.Opts{
   311  		Method: "GET",
   312  		Path:   "/Items",
   313  		Parameters: url.Values{
   314  			"$select": {api.ListRequestSelect},
   315  		},
   316  	}
   317  	if id != "" {
   318  		opts.Path += "(" + id + ")"
   319  	}
   320  	if path != "" {
   321  		opts.Path += "/ByPath"
   322  		opts.Parameters.Set("path", "/"+f.opt.Enc.FromStandardPath(path))
   323  	}
   324  	var item api.Item
   325  	var resp *http.Response
   326  	err = f.pacer.Call(func() (bool, error) {
   327  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &item)
   328  		return shouldRetry(ctx, resp, err)
   329  	})
   330  	if err != nil {
   331  		if resp != nil && resp.StatusCode == http.StatusNotFound {
   332  			if filesOnly {
   333  				return nil, fs.ErrorObjectNotFound
   334  			}
   335  			return nil, fs.ErrorDirNotFound
   336  		}
   337  		return nil, fmt.Errorf("couldn't find item: %w", err)
   338  	}
   339  	if directoriesOnly && item.Type != api.ItemTypeFolder {
   340  		return nil, fs.ErrorIsFile
   341  	}
   342  	if filesOnly {
   343  		if item.Type == api.ItemTypeFolder {
   344  			return nil, fs.ErrorIsDir
   345  		} else if item.Type != api.ItemTypeFile {
   346  			return nil, fs.ErrorNotAFile
   347  		}
   348  	}
   349  	return &item, nil
   350  }
   351  
   352  // Reads the metadata for the id passed in.  If id is "" then it returns the root
   353  func (f *Fs) readMetaDataForID(ctx context.Context, id string, directoriesOnly bool, filesOnly bool) (info *api.Item, err error) {
   354  	return f.readMetaDataForIDPath(ctx, id, "", directoriesOnly, filesOnly)
   355  }
   356  
   357  // readMetaDataForPath reads the metadata from the path
   358  func (f *Fs) readMetaDataForPath(ctx context.Context, path string, directoriesOnly bool, filesOnly bool) (info *api.Item, err error) {
   359  	leaf, directoryID, err := f.dirCache.FindPath(ctx, path, false)
   360  	if err != nil {
   361  		if err == fs.ErrorDirNotFound {
   362  			return nil, fs.ErrorObjectNotFound
   363  		}
   364  		return nil, err
   365  	}
   366  	return f.readMetaDataForIDPath(ctx, directoryID, leaf, directoriesOnly, filesOnly)
   367  }
   368  
   369  // errorHandler parses a non 2xx error response into an error
   370  func errorHandler(resp *http.Response) error {
   371  	body, err := rest.ReadBody(resp)
   372  	if err != nil {
   373  		body = nil
   374  	}
   375  	var e = api.Error{
   376  		Code:   fmt.Sprint(resp.StatusCode),
   377  		Reason: resp.Status,
   378  	}
   379  	e.Message.Lang = "en"
   380  	e.Message.Value = string(body)
   381  	if body != nil {
   382  		_ = json.Unmarshal(body, &e)
   383  	}
   384  	return &e
   385  }
   386  
   387  func checkUploadChunkSize(cs fs.SizeSuffix) error {
   388  	if cs < minChunkSize {
   389  		return fmt.Errorf("ChunkSize: %s is less than %s", cs, minChunkSize)
   390  	}
   391  	if cs > maxChunkSize {
   392  		return fmt.Errorf("ChunkSize: %s is greater than %s", cs, maxChunkSize)
   393  	}
   394  	return nil
   395  }
   396  
   397  func (f *Fs) setUploadChunkSize(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) {
   398  	err = checkUploadChunkSize(cs)
   399  	if err == nil {
   400  		old, f.opt.ChunkSize = f.opt.ChunkSize, cs
   401  		f.fillBufferTokens() // reset the buffer tokens
   402  	}
   403  	return
   404  }
   405  
   406  func checkUploadCutoff(cs fs.SizeSuffix) error {
   407  	return nil
   408  }
   409  
   410  func (f *Fs) setUploadCutoff(cs fs.SizeSuffix) (old fs.SizeSuffix, err error) {
   411  	err = checkUploadCutoff(cs)
   412  	if err == nil {
   413  		old, f.opt.UploadCutoff = f.opt.UploadCutoff, cs
   414  	}
   415  	return
   416  }
   417  
   418  // NewFs constructs an Fs from the path, container:path
   419  func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, error) {
   420  	// Parse config into Options struct
   421  	opt := new(Options)
   422  	err := configstruct.Set(m, opt)
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  
   427  	// Check parameters OK
   428  	if opt.Endpoint == "" {
   429  		return nil, errors.New("endpoint not set: rebuild the remote or set manually")
   430  	}
   431  	err = checkUploadChunkSize(opt.ChunkSize)
   432  	if err != nil {
   433  		return nil, err
   434  	}
   435  	err = checkUploadCutoff(opt.UploadCutoff)
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	root = parsePath(root)
   441  
   442  	oauthConfig := newOauthConfig(opt.Endpoint + tokenPath)
   443  	var client *http.Client
   444  	var ts *oauthutil.TokenSource
   445  	client, ts, err = oauthutil.NewClient(ctx, name, m, oauthConfig)
   446  	if err != nil {
   447  		return nil, fmt.Errorf("failed to configure sharefile: %w", err)
   448  	}
   449  
   450  	ci := fs.GetConfig(ctx)
   451  	f := &Fs{
   452  		name:  name,
   453  		root:  root,
   454  		opt:   *opt,
   455  		ci:    ci,
   456  		srv:   rest.NewClient(client).SetRoot(opt.Endpoint + apiPath),
   457  		pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   458  	}
   459  	f.features = (&fs.Features{
   460  		CaseInsensitive:         true,
   461  		CanHaveEmptyDirectories: true,
   462  		ReadMimeType:            false,
   463  	}).Fill(ctx, f)
   464  	f.srv.SetErrorHandler(errorHandler)
   465  	f.fillBufferTokens()
   466  
   467  	// Renew the token in the background
   468  	if ts != nil {
   469  		f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
   470  			_, err := f.List(ctx, "")
   471  			return err
   472  		})
   473  	}
   474  
   475  	// Load the server timezone from an internal file
   476  	// Used to correct the time in SetModTime
   477  	const serverTimezone = "America/New_York"
   478  	timezone, err := tzdata.Open(serverTimezone)
   479  	if err != nil {
   480  		return nil, fmt.Errorf("failed to open timezone db: %w", err)
   481  	}
   482  	tzdata, err := io.ReadAll(timezone)
   483  	if err != nil {
   484  		return nil, fmt.Errorf("failed to read timezone: %w", err)
   485  	}
   486  	_ = timezone.Close()
   487  	f.location, err = time.LoadLocationFromTZData(serverTimezone, tzdata)
   488  	if err != nil {
   489  		return nil, fmt.Errorf("failed to load location from timezone: %w", err)
   490  	}
   491  
   492  	// Find ID of user's root folder
   493  	if opt.RootFolderID == "" {
   494  		item, err := f.readMetaDataForID(ctx, opt.RootFolderID, true, false)
   495  		if err != nil {
   496  			return nil, fmt.Errorf("couldn't find root ID: %w", err)
   497  		}
   498  		f.rootID = item.ID
   499  	} else {
   500  		f.rootID = opt.RootFolderID
   501  	}
   502  
   503  	// Get rootID
   504  	f.dirCache = dircache.New(root, f.rootID, f)
   505  
   506  	// Find the current root
   507  	err = f.dirCache.FindRoot(ctx, false)
   508  	if err != nil {
   509  		// Assume it is a file
   510  		newRoot, remote := dircache.SplitPath(root)
   511  		tempF := *f
   512  		tempF.dirCache = dircache.New(newRoot, f.rootID, &tempF)
   513  		tempF.root = newRoot
   514  		// Make new Fs which is the parent
   515  		err = tempF.dirCache.FindRoot(ctx, false)
   516  		if err != nil {
   517  			// No root so return old f
   518  			return f, nil
   519  		}
   520  		_, err := tempF.newObjectWithInfo(ctx, remote, nil)
   521  		if err != nil {
   522  			if err == fs.ErrorObjectNotFound {
   523  				// File doesn't exist so return old f
   524  				return f, nil
   525  			}
   526  			return nil, err
   527  		}
   528  		f.features.Fill(ctx, &tempF)
   529  		// XXX: update the old f here instead of returning tempF, since
   530  		// `features` were already filled with functions having *f as a receiver.
   531  		// See https://github.com/artpar/rclone/issues/2182
   532  		f.dirCache = tempF.dirCache
   533  		f.root = tempF.root
   534  		// return an error with an fs which points to the parent
   535  		return f, fs.ErrorIsFile
   536  	}
   537  	return f, nil
   538  }
   539  
   540  // Fill up (or reset) the buffer tokens
   541  func (f *Fs) fillBufferTokens() {
   542  	f.bufferTokens = make(chan []byte, f.ci.Transfers)
   543  	for i := 0; i < f.ci.Transfers; i++ {
   544  		f.bufferTokens <- nil
   545  	}
   546  }
   547  
   548  // getUploadBlock gets a block from the pool of size chunkSize
   549  func (f *Fs) getUploadBlock() []byte {
   550  	buf := <-f.bufferTokens
   551  	if buf == nil {
   552  		buf = make([]byte, f.opt.ChunkSize)
   553  	}
   554  	// fs.Debugf(f, "Getting upload block %p", buf)
   555  	return buf
   556  }
   557  
   558  // putUploadBlock returns a block to the pool of size chunkSize
   559  func (f *Fs) putUploadBlock(buf []byte) {
   560  	buf = buf[:cap(buf)]
   561  	if len(buf) != int(f.opt.ChunkSize) {
   562  		panic("bad blocksize returned to pool")
   563  	}
   564  	// fs.Debugf(f, "Returning upload block %p", buf)
   565  	f.bufferTokens <- buf
   566  }
   567  
   568  // Return an Object from a path
   569  //
   570  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   571  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.Item) (fs.Object, error) {
   572  	o := &Object{
   573  		fs:     f,
   574  		remote: remote,
   575  	}
   576  	var err error
   577  	if info != nil {
   578  		// Set info
   579  		err = o.setMetaData(info)
   580  	} else {
   581  		err = o.readMetaData(ctx) // reads info and meta, returning an error
   582  	}
   583  	if err != nil {
   584  		return nil, err
   585  	}
   586  	return o, nil
   587  }
   588  
   589  // NewObject finds the Object at remote.  If it can't be found
   590  // it returns the error fs.ErrorObjectNotFound.
   591  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   592  	return f.newObjectWithInfo(ctx, remote, nil)
   593  }
   594  
   595  // FindLeaf finds a directory of name leaf in the folder with ID pathID
   596  func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut string, found bool, err error) {
   597  	if pathID == "top" {
   598  		// Find the leaf in pathID
   599  		found, err = f.listAll(ctx, pathID, true, false, func(item *api.Item) bool {
   600  			if item.Name == leaf {
   601  				pathIDOut = item.ID
   602  				return true
   603  			}
   604  			return false
   605  		})
   606  		return pathIDOut, found, err
   607  	}
   608  	info, err := f.readMetaDataForIDPath(ctx, pathID, leaf, true, false)
   609  	if err == nil {
   610  		found = true
   611  		pathIDOut = info.ID
   612  	} else if err == fs.ErrorDirNotFound {
   613  		err = nil // don't return an error if not found
   614  	}
   615  	return pathIDOut, found, err
   616  }
   617  
   618  // CreateDir makes a directory with pathID as parent and name leaf
   619  func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
   620  	var resp *http.Response
   621  	leaf = f.opt.Enc.FromStandardName(leaf)
   622  	var req = api.Item{
   623  		Name:      leaf,
   624  		FileName:  leaf,
   625  		CreatedAt: time.Now(),
   626  	}
   627  	var info api.Item
   628  	opts := rest.Opts{
   629  		Method: "POST",
   630  		Path:   "/Items(" + pathID + ")/Folder",
   631  		Parameters: url.Values{
   632  			"$select":     {api.ListRequestSelect},
   633  			"overwrite":   {"false"},
   634  			"passthrough": {"false"},
   635  		},
   636  	}
   637  	err = f.pacer.Call(func() (bool, error) {
   638  		resp, err = f.srv.CallJSON(ctx, &opts, &req, &info)
   639  		return shouldRetry(ctx, resp, err)
   640  	})
   641  	if err != nil {
   642  		return "", fmt.Errorf("CreateDir: %w", err)
   643  	}
   644  	return info.ID, nil
   645  }
   646  
   647  // list the objects into the function supplied
   648  //
   649  // If directories is set it only sends directories
   650  // User function to process a File item from listAll
   651  //
   652  // Should return true to finish processing
   653  type listAllFn func(*api.Item) bool
   654  
   655  // Lists the directory required calling the user function on each item found
   656  //
   657  // If the user fn ever returns true then it early exits with found = true
   658  func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, filesOnly bool, fn listAllFn) (found bool, err error) {
   659  	opts := rest.Opts{
   660  		Method: "GET",
   661  		Path:   "/Items(" + dirID + ")/Children",
   662  		Parameters: url.Values{
   663  			"$select": {api.ListRequestSelect},
   664  		},
   665  	}
   666  
   667  	var result api.ListResponse
   668  	var resp *http.Response
   669  	err = f.pacer.Call(func() (bool, error) {
   670  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
   671  		return shouldRetry(ctx, resp, err)
   672  	})
   673  	if err != nil {
   674  		return found, fmt.Errorf("couldn't list files: %w", err)
   675  	}
   676  	for i := range result.Value {
   677  		item := &result.Value[i]
   678  		if item.Type == api.ItemTypeFolder {
   679  			if filesOnly {
   680  				continue
   681  			}
   682  		} else if item.Type == api.ItemTypeFile {
   683  			if directoriesOnly {
   684  				continue
   685  			}
   686  		} else {
   687  			fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type)
   688  			continue
   689  		}
   690  		item.Name = f.opt.Enc.ToStandardName(item.Name)
   691  		if fn(item) {
   692  			found = true
   693  			break
   694  		}
   695  	}
   696  
   697  	return
   698  }
   699  
   700  // List the objects and directories in dir into entries.  The
   701  // entries can be returned in any order but should be for a
   702  // complete directory.
   703  //
   704  // dir should be "" to list the root, and should not have
   705  // trailing slashes.
   706  //
   707  // This should return ErrDirNotFound if the directory isn't
   708  // found.
   709  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   710  	directoryID, err := f.dirCache.FindDir(ctx, dir, false)
   711  	if err != nil {
   712  		return nil, err
   713  	}
   714  	var iErr error
   715  	_, err = f.listAll(ctx, directoryID, false, false, func(info *api.Item) bool {
   716  		remote := path.Join(dir, info.Name)
   717  		if info.Type == api.ItemTypeFolder {
   718  			// cache the directory ID for later lookups
   719  			f.dirCache.Put(remote, info.ID)
   720  			d := fs.NewDir(remote, info.CreatedAt).SetID(info.ID).SetSize(info.Size).SetItems(int64(info.FileCount))
   721  			entries = append(entries, d)
   722  		} else if info.Type == api.ItemTypeFile {
   723  			o, err := f.newObjectWithInfo(ctx, remote, info)
   724  			if err != nil {
   725  				iErr = err
   726  				return true
   727  			}
   728  			entries = append(entries, o)
   729  		}
   730  		return false
   731  	})
   732  	if err != nil {
   733  		return nil, err
   734  	}
   735  	if iErr != nil {
   736  		return nil, iErr
   737  	}
   738  	return entries, nil
   739  }
   740  
   741  // Creates from the parameters passed in a half finished Object which
   742  // must have setMetaData called on it
   743  //
   744  // Returns the object, leaf, directoryID and error.
   745  //
   746  // Used to create new objects
   747  func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time, size int64) (o *Object, leaf string, directoryID string, err error) {
   748  	// Create the directory for the object if it doesn't exist
   749  	leaf, directoryID, err = f.dirCache.FindPath(ctx, remote, true)
   750  	if err != nil {
   751  		return
   752  	}
   753  	// Temporary Object under construction
   754  	o = &Object{
   755  		fs:     f,
   756  		remote: remote,
   757  	}
   758  	return o, leaf, directoryID, nil
   759  }
   760  
   761  // Put the object
   762  //
   763  // Copy the reader in to the new object which is returned.
   764  //
   765  // The new object may have been created if an error is returned
   766  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   767  	existingObj, err := f.newObjectWithInfo(ctx, src.Remote(), nil)
   768  	switch err {
   769  	case nil:
   770  		return existingObj, existingObj.Update(ctx, in, src, options...)
   771  	case fs.ErrorObjectNotFound:
   772  		// Not found so create it
   773  		return f.PutUnchecked(ctx, in, src)
   774  	default:
   775  		return nil, err
   776  	}
   777  }
   778  
   779  // FIXMEPutStream uploads to the remote path with the modTime given of indeterminate size
   780  //
   781  // PutStream no longer appears to work - the streamed uploads need the
   782  // size specified at the start otherwise we get this error:
   783  //
   784  //	upload failed: file size does not match (-2)
   785  func (f *Fs) FIXMEPutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   786  	return f.Put(ctx, in, src, options...)
   787  }
   788  
   789  // PutUnchecked the object into the container
   790  //
   791  // This will produce an error if the object already exists.
   792  //
   793  // Copy the reader in to the new object which is returned.
   794  //
   795  // The new object may have been created if an error is returned
   796  func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   797  	remote := src.Remote()
   798  	size := src.Size()
   799  	modTime := src.ModTime(ctx)
   800  
   801  	o, _, _, err := f.createObject(ctx, remote, modTime, size)
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  	return o, o.Update(ctx, in, src, options...)
   806  }
   807  
   808  // Mkdir creates the container if it doesn't exist
   809  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   810  	_, err := f.dirCache.FindDir(ctx, dir, true)
   811  	return err
   812  }
   813  
   814  // purgeCheck removes the directory, if check is set then it refuses
   815  // to do so if it has anything in
   816  func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error {
   817  	root := path.Join(f.root, dir)
   818  	if root == "" {
   819  		return errors.New("can't purge root directory")
   820  	}
   821  	dc := f.dirCache
   822  	rootID, err := dc.FindDir(ctx, dir, false)
   823  	if err != nil {
   824  		return err
   825  	}
   826  
   827  	// need to check if empty as it will delete recursively by default
   828  	if check {
   829  		found, err := f.listAll(ctx, rootID, false, false, func(item *api.Item) bool {
   830  			return true
   831  		})
   832  		if err != nil {
   833  			return fmt.Errorf("purgeCheck: %w", err)
   834  		}
   835  		if found {
   836  			return fs.ErrorDirectoryNotEmpty
   837  		}
   838  	}
   839  
   840  	err = f.remove(ctx, rootID)
   841  	f.dirCache.FlushDir(dir)
   842  	if err != nil {
   843  		return err
   844  	}
   845  	return nil
   846  }
   847  
   848  // Rmdir deletes the root folder
   849  //
   850  // Returns an error if it isn't empty
   851  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   852  	return f.purgeCheck(ctx, dir, true)
   853  }
   854  
   855  // Precision return the precision of this Fs
   856  func (f *Fs) Precision() time.Duration {
   857  	// sharefile returns times accurate to the millisecond, but
   858  	// for some reason these seem only accurate 2ms.
   859  	// updateItem seems to only set times accurate to 1 second though.
   860  	return time.Second // this doesn't appear to be documented anywhere
   861  }
   862  
   863  // Purge deletes all the files and the container
   864  //
   865  // Optional interface: Only implement this if you have a way of
   866  // deleting all the files quicker than just running Remove() on the
   867  // result of List()
   868  func (f *Fs) Purge(ctx context.Context, dir string) error {
   869  	return f.purgeCheck(ctx, dir, false)
   870  }
   871  
   872  // updateItem patches a file or folder
   873  //
   874  // if leaf = "" or directoryID = "" or modTime == nil then it will be
   875  // left alone
   876  //
   877  // Note that this seems to work by renaming first, then moving to a
   878  // new directory which means that it can overwrite existing objects
   879  // :-(
   880  func (f *Fs) updateItem(ctx context.Context, id, leaf, directoryID string, modTime *time.Time) (info *api.Item, err error) {
   881  	// Move the object
   882  	opts := rest.Opts{
   883  		Method: "PATCH",
   884  		Path:   "/Items(" + id + ")",
   885  		Parameters: url.Values{
   886  			"$select":   {api.ListRequestSelect},
   887  			"overwrite": {"false"},
   888  		},
   889  	}
   890  	leaf = f.opt.Enc.FromStandardName(leaf)
   891  	// FIXME this appears to be a bug in the API
   892  	//
   893  	// If you set the modified time via PATCH then the server
   894  	// appears to parse it as a local time for America/New_York
   895  	//
   896  	// However if you set it when uploading the file then it is fine...
   897  	//
   898  	// Also it only sets the time to 1 second resolution where it
   899  	// uses 1ms resolution elsewhere
   900  	if modTime != nil && f.location != nil {
   901  		newTime := modTime.In(f.location)
   902  		isoTime := newTime.Format(time.RFC3339Nano)
   903  		// Chop TZ -05:00 off the end and replace with Z
   904  		isoTime = isoTime[:len(isoTime)-6] + "Z"
   905  		// Parse it back into a time
   906  		newModTime, err := time.Parse(time.RFC3339Nano, isoTime)
   907  		if err != nil {
   908  			return nil, fmt.Errorf("updateItem: time parse: %w", err)
   909  		}
   910  		modTime = &newModTime
   911  	}
   912  	update := api.UpdateItemRequest{
   913  		Name:       leaf,
   914  		FileName:   leaf,
   915  		ModifiedAt: modTime,
   916  	}
   917  	if directoryID != "" {
   918  		update.Parent = &api.Parent{
   919  			ID: directoryID,
   920  		}
   921  	}
   922  	var resp *http.Response
   923  	err = f.pacer.Call(func() (bool, error) {
   924  		resp, err = f.srv.CallJSON(ctx, &opts, &update, &info)
   925  		return shouldRetry(ctx, resp, err)
   926  	})
   927  	if err != nil {
   928  		return nil, err
   929  	}
   930  	return info, nil
   931  }
   932  
   933  // move a file or folder
   934  //
   935  // This is complicated by the fact that we can't use updateItem to move
   936  // to a different directory AND rename at the same time as it can
   937  // overwrite files in the source directory.
   938  func (f *Fs) move(ctx context.Context, isFile bool, id, oldLeaf, newLeaf, oldDirectoryID, newDirectoryID string) (item *api.Item, err error) {
   939  	// To demonstrate bug
   940  	// item, err = f.updateItem(ctx, id, newLeaf, newDirectoryID, nil)
   941  	// if err != nil {
   942  	// 	return nil, fmt.Errorf("Move rename leaf: %w", err)
   943  	// }
   944  	// return item, nil
   945  	doRenameLeaf := oldLeaf != newLeaf
   946  	doMove := oldDirectoryID != newDirectoryID
   947  
   948  	// Now rename the leaf to a temporary name if we are moving to
   949  	// another directory to make sure we don't overwrite something
   950  	// in the source directory by accident
   951  	if doRenameLeaf && doMove {
   952  		tmpLeaf := newLeaf + "." + random.String(8)
   953  		item, err = f.updateItem(ctx, id, tmpLeaf, "", nil)
   954  		if err != nil {
   955  			return nil, fmt.Errorf("Move rename leaf: %w", err)
   956  		}
   957  	}
   958  
   959  	// Move the object to a new directory (with the existing name)
   960  	// if required
   961  	if doMove {
   962  		item, err = f.updateItem(ctx, id, "", newDirectoryID, nil)
   963  		if err != nil {
   964  			return nil, fmt.Errorf("Move directory: %w", err)
   965  		}
   966  	}
   967  
   968  	// Rename the leaf to its final name if required
   969  	if doRenameLeaf {
   970  		item, err = f.updateItem(ctx, id, newLeaf, "", nil)
   971  		if err != nil {
   972  			return nil, fmt.Errorf("Move rename leaf: %w", err)
   973  		}
   974  	}
   975  
   976  	return item, nil
   977  }
   978  
   979  // Move src to this remote using server-side move operations.
   980  //
   981  // This is stored with the remote path given.
   982  //
   983  // It returns the destination Object and a possible error.
   984  //
   985  // Will only be called if src.Fs().Name() == f.Name()
   986  //
   987  // If it isn't possible then return fs.ErrorCantMove
   988  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   989  	srcObj, ok := src.(*Object)
   990  	if !ok {
   991  		fs.Debugf(src, "Can't move - not same remote type")
   992  		return nil, fs.ErrorCantMove
   993  	}
   994  
   995  	// Find ID of src parent, not creating subdirs
   996  	srcLeaf, srcParentID, err := srcObj.fs.dirCache.FindPath(ctx, srcObj.remote, false)
   997  	if err != nil {
   998  		return nil, err
   999  	}
  1000  
  1001  	// Create temporary object
  1002  	dstObj, leaf, directoryID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size)
  1003  	if err != nil {
  1004  		return nil, err
  1005  	}
  1006  
  1007  	// Do the move
  1008  	info, err := f.move(ctx, true, srcObj.id, srcLeaf, leaf, srcParentID, directoryID)
  1009  	if err != nil {
  1010  		return nil, err
  1011  	}
  1012  
  1013  	err = dstObj.setMetaData(info)
  1014  	if err != nil {
  1015  		return nil, err
  1016  	}
  1017  	return dstObj, nil
  1018  }
  1019  
  1020  // DirMove moves src, srcRemote to this remote at dstRemote
  1021  // using server-side move operations.
  1022  //
  1023  // Will only be called if src.Fs().Name() == f.Name()
  1024  //
  1025  // If it isn't possible then return fs.ErrorCantDirMove
  1026  //
  1027  // If destination exists then return fs.ErrorDirExists
  1028  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
  1029  	srcFs, ok := src.(*Fs)
  1030  	if !ok {
  1031  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
  1032  		return fs.ErrorCantDirMove
  1033  	}
  1034  
  1035  	srcID, srcDirectoryID, srcLeaf, dstDirectoryID, dstLeaf, err := f.dirCache.DirMove(ctx, srcFs.dirCache, srcFs.root, srcRemote, f.root, dstRemote)
  1036  	if err != nil {
  1037  		return err
  1038  	}
  1039  
  1040  	// Do the move
  1041  	_, err = f.move(ctx, false, srcID, srcLeaf, dstLeaf, srcDirectoryID, dstDirectoryID)
  1042  	if err != nil {
  1043  		return err
  1044  	}
  1045  	srcFs.dirCache.FlushDir(srcRemote)
  1046  	return nil
  1047  }
  1048  
  1049  // Copy src to this remote using server-side copy operations.
  1050  //
  1051  // This is stored with the remote path given.
  1052  //
  1053  // It returns the destination Object and a possible error.
  1054  //
  1055  // Will only be called if src.Fs().Name() == f.Name()
  1056  //
  1057  // If it isn't possible then return fs.ErrorCantCopy
  1058  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Object, err error) {
  1059  	srcObj, ok := src.(*Object)
  1060  	if !ok {
  1061  		fs.Debugf(src, "Can't copy - not same remote type")
  1062  		return nil, fs.ErrorCantCopy
  1063  	}
  1064  
  1065  	err = srcObj.readMetaData(ctx)
  1066  	if err != nil {
  1067  		return nil, err
  1068  	}
  1069  
  1070  	// Find ID of src parent, not creating subdirs
  1071  	srcLeaf, srcParentID, err := srcObj.fs.dirCache.FindPath(ctx, srcObj.remote, false)
  1072  	if err != nil {
  1073  		return nil, err
  1074  	}
  1075  	srcLeaf = f.opt.Enc.FromStandardName(srcLeaf)
  1076  	_ = srcParentID
  1077  
  1078  	// Create temporary object
  1079  	dstObj, dstLeaf, dstParentID, err := f.createObject(ctx, remote, srcObj.modTime, srcObj.size)
  1080  	if err != nil {
  1081  		return nil, err
  1082  	}
  1083  	dstLeaf = f.opt.Enc.FromStandardName(dstLeaf)
  1084  
  1085  	sameName := strings.EqualFold(srcLeaf, dstLeaf)
  1086  	if sameName && srcParentID == dstParentID {
  1087  		return nil, fmt.Errorf("copy: can't copy to a file in the same directory whose name only differs in case: %q vs %q", srcLeaf, dstLeaf)
  1088  	}
  1089  
  1090  	// Discover whether we can just copy directly or not
  1091  	directCopy := false
  1092  	if sameName {
  1093  		// if copying to same name can copy directly
  1094  		directCopy = true
  1095  	} else {
  1096  		// if (dstParentID, srcLeaf) does not exist then can
  1097  		// Copy then Rename without fear of overwriting
  1098  		// something
  1099  		_, err := f.readMetaDataForIDPath(ctx, dstParentID, srcLeaf, false, false)
  1100  		if err == fs.ErrorObjectNotFound || err == fs.ErrorDirNotFound {
  1101  			directCopy = true
  1102  		} else if err != nil {
  1103  			return nil, fmt.Errorf("copy: failed to examine destination dir: %w", err)
  1104  			//} else {
  1105  			// otherwise need to copy via a temporary directory
  1106  		}
  1107  	}
  1108  
  1109  	// Copy direct to destination unless !directCopy in which case
  1110  	// copy via a temporary directory
  1111  	copyTargetDirID := dstParentID
  1112  	if !directCopy {
  1113  		// Create a temporary directory to copy the object in to
  1114  		tmpDir := "rclone-temp-dir-" + random.String(16)
  1115  		err = f.Mkdir(ctx, tmpDir)
  1116  		if err != nil {
  1117  			return nil, fmt.Errorf("copy: failed to make temp dir: %w", err)
  1118  		}
  1119  		defer func() {
  1120  			rmdirErr := f.Rmdir(ctx, tmpDir)
  1121  			if rmdirErr != nil && err == nil {
  1122  				err = fmt.Errorf("copy: failed to remove temp dir: %w", rmdirErr)
  1123  			}
  1124  		}()
  1125  		tmpDirID, err := f.dirCache.FindDir(ctx, tmpDir, false)
  1126  		if err != nil {
  1127  			return nil, fmt.Errorf("copy: failed to find temp dir: %w", err)
  1128  		}
  1129  		copyTargetDirID = tmpDirID
  1130  	}
  1131  
  1132  	// Copy the object
  1133  	opts := rest.Opts{
  1134  		Method: "POST",
  1135  		Path:   "/Items(" + srcObj.id + ")/Copy",
  1136  		Parameters: url.Values{
  1137  			"$select":   {api.ListRequestSelect},
  1138  			"overwrite": {"false"},
  1139  			"targetid":  {copyTargetDirID},
  1140  		},
  1141  	}
  1142  	var resp *http.Response
  1143  	var info *api.Item
  1144  	err = f.pacer.Call(func() (bool, error) {
  1145  		resp, err = f.srv.CallJSON(ctx, &opts, nil, &info)
  1146  		return shouldRetry(ctx, resp, err)
  1147  	})
  1148  	if err != nil {
  1149  		return nil, err
  1150  	}
  1151  
  1152  	// Rename into the correct name and directory if required and
  1153  	// set the modtime since the copy doesn't preserve it
  1154  	var updateParentID, updateLeaf string // only set these if necessary
  1155  	if srcLeaf != dstLeaf {
  1156  		updateLeaf = dstLeaf
  1157  	}
  1158  	if !directCopy {
  1159  		updateParentID = dstParentID
  1160  	}
  1161  	// set new modtime regardless
  1162  	info, err = f.updateItem(ctx, info.ID, updateLeaf, updateParentID, &srcObj.modTime)
  1163  	if err != nil {
  1164  		return nil, err
  1165  	}
  1166  	err = dstObj.setMetaData(info)
  1167  	if err != nil {
  1168  		return nil, err
  1169  	}
  1170  	return dstObj, nil
  1171  }
  1172  
  1173  // DirCacheFlush resets the directory cache - used in testing as an
  1174  // optional interface
  1175  func (f *Fs) DirCacheFlush() {
  1176  	f.dirCache.ResetRoot()
  1177  }
  1178  
  1179  // Shutdown shutdown the fs
  1180  func (f *Fs) Shutdown(ctx context.Context) error {
  1181  	f.tokenRenewer.Shutdown()
  1182  	return nil
  1183  }
  1184  
  1185  // Hashes returns the supported hash sets.
  1186  func (f *Fs) Hashes() hash.Set {
  1187  	return hash.Set(hash.MD5)
  1188  }
  1189  
  1190  // ------------------------------------------------------------
  1191  
  1192  // Fs returns the parent Fs
  1193  func (o *Object) Fs() fs.Info {
  1194  	return o.fs
  1195  }
  1196  
  1197  // Return a string version
  1198  func (o *Object) String() string {
  1199  	if o == nil {
  1200  		return "<nil>"
  1201  	}
  1202  	return o.remote
  1203  }
  1204  
  1205  // Remote returns the remote path
  1206  func (o *Object) Remote() string {
  1207  	return o.remote
  1208  }
  1209  
  1210  // Hash returns the SHA-1 of an object returning a lowercase hex string
  1211  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
  1212  	if t != hash.MD5 {
  1213  		return "", hash.ErrUnsupported
  1214  	}
  1215  	err := o.readMetaData(ctx)
  1216  	if err != nil {
  1217  		return "", err
  1218  	}
  1219  	return o.md5, nil
  1220  }
  1221  
  1222  // Size returns the size of an object in bytes
  1223  func (o *Object) Size() int64 {
  1224  	err := o.readMetaData(context.TODO())
  1225  	if err != nil {
  1226  		fs.Logf(o, "Failed to read metadata: %v", err)
  1227  		return 0
  1228  	}
  1229  	return o.size
  1230  }
  1231  
  1232  // setMetaData sets the metadata from info
  1233  func (o *Object) setMetaData(info *api.Item) (err error) {
  1234  	if info.Type != api.ItemTypeFile {
  1235  		return fmt.Errorf("%q is %q: %w", o.remote, info.Type, fs.ErrorNotAFile)
  1236  	}
  1237  	o.hasMetaData = true
  1238  	o.size = info.Size
  1239  	if !info.ModifiedAt.IsZero() {
  1240  		o.modTime = info.ModifiedAt
  1241  	} else {
  1242  		o.modTime = info.CreatedAt
  1243  	}
  1244  	o.id = info.ID
  1245  	o.md5 = info.Hash
  1246  	return nil
  1247  }
  1248  
  1249  // readMetaData gets the metadata if it hasn't already been fetched
  1250  //
  1251  // it also sets the info
  1252  func (o *Object) readMetaData(ctx context.Context) (err error) {
  1253  	if o.hasMetaData {
  1254  		return nil
  1255  	}
  1256  	var info *api.Item
  1257  	if o.id != "" {
  1258  		info, err = o.fs.readMetaDataForID(ctx, o.id, false, true)
  1259  	} else {
  1260  		info, err = o.fs.readMetaDataForPath(ctx, o.remote, false, true)
  1261  	}
  1262  	if err != nil {
  1263  		return err
  1264  	}
  1265  	return o.setMetaData(info)
  1266  }
  1267  
  1268  // ModTime returns the modification time of the object
  1269  //
  1270  // It attempts to read the objects mtime and if that isn't present the
  1271  // LastModified returned in the http headers
  1272  func (o *Object) ModTime(ctx context.Context) time.Time {
  1273  	err := o.readMetaData(ctx)
  1274  	if err != nil {
  1275  		fs.Logf(o, "Failed to read metadata: %v", err)
  1276  		return time.Now()
  1277  	}
  1278  	return o.modTime
  1279  }
  1280  
  1281  // SetModTime sets the modification time of the local fs object
  1282  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) (err error) {
  1283  	info, err := o.fs.updateItem(ctx, o.id, "", "", &modTime)
  1284  	if err != nil {
  1285  		return err
  1286  	}
  1287  	err = o.setMetaData(info)
  1288  	if err != nil {
  1289  		return err
  1290  	}
  1291  	return nil
  1292  }
  1293  
  1294  // Storable returns a boolean showing whether this object storable
  1295  func (o *Object) Storable() bool {
  1296  	return true
  1297  }
  1298  
  1299  // Open an object for read
  1300  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  1301  	opts := rest.Opts{
  1302  		Method: "GET",
  1303  		Path:   "/Items(" + o.id + ")/Download",
  1304  		Parameters: url.Values{
  1305  			"redirect": {"false"},
  1306  		},
  1307  	}
  1308  	var resp *http.Response
  1309  	var dl api.DownloadSpecification
  1310  	err = o.fs.pacer.Call(func() (bool, error) {
  1311  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl)
  1312  		return shouldRetry(ctx, resp, err)
  1313  	})
  1314  	if err != nil {
  1315  		return nil, fmt.Errorf("open: fetch download specification: %w", err)
  1316  	}
  1317  
  1318  	fs.FixRangeOption(options, o.size)
  1319  	opts = rest.Opts{
  1320  		Path:    "",
  1321  		RootURL: dl.URL,
  1322  		Method:  "GET",
  1323  		Options: options,
  1324  	}
  1325  	err = o.fs.pacer.Call(func() (bool, error) {
  1326  		resp, err = o.fs.srv.Call(ctx, &opts)
  1327  		return shouldRetry(ctx, resp, err)
  1328  	})
  1329  	if err != nil {
  1330  		return nil, fmt.Errorf("open: %w", err)
  1331  	}
  1332  	return resp.Body, err
  1333  }
  1334  
  1335  // Update the object with the contents of the io.Reader, modTime and size
  1336  //
  1337  // If existing is set then it updates the object rather than creating a new one.
  1338  //
  1339  // The new object may have been created if an error is returned
  1340  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
  1341  	remote := o.Remote()
  1342  	size := src.Size()
  1343  	modTime := src.ModTime(ctx)
  1344  	isLargeFile := size < 0 || size > int64(o.fs.opt.UploadCutoff)
  1345  
  1346  	// Create the directory for the object if it doesn't exist
  1347  	leaf, directoryID, err := o.fs.dirCache.FindPath(ctx, remote, true)
  1348  	if err != nil {
  1349  		return err
  1350  	}
  1351  	leaf = o.fs.opt.Enc.FromStandardName(leaf)
  1352  	var req = api.UploadRequest{
  1353  		Method:       "standard",
  1354  		Raw:          true,
  1355  		Filename:     leaf,
  1356  		Overwrite:    true,
  1357  		CreatedDate:  modTime,
  1358  		ModifiedDate: modTime,
  1359  		Tool:         o.fs.ci.UserAgent,
  1360  	}
  1361  
  1362  	if isLargeFile {
  1363  		if size < 0 {
  1364  			// For files of indeterminate size, use streamed
  1365  			req.Method = "streamed"
  1366  		} else {
  1367  			// otherwise use threaded which is more efficient
  1368  			req.Method = "threaded"
  1369  			req.ThreadCount = &o.fs.ci.Transfers
  1370  			req.Filesize = &size
  1371  		}
  1372  	}
  1373  
  1374  	var resp *http.Response
  1375  	var info api.UploadSpecification
  1376  	opts := rest.Opts{
  1377  		Method:  "POST",
  1378  		Path:    "/Items(" + directoryID + ")/Upload2",
  1379  		Options: options,
  1380  	}
  1381  	err = o.fs.pacer.Call(func() (bool, error) {
  1382  		resp, err = o.fs.srv.CallJSON(ctx, &opts, &req, &info)
  1383  		return shouldRetry(ctx, resp, err)
  1384  	})
  1385  	if err != nil {
  1386  		return fmt.Errorf("upload get specification: %w", err)
  1387  	}
  1388  
  1389  	// If file is large then upload in parts
  1390  	if isLargeFile {
  1391  		up, err := o.fs.newLargeUpload(ctx, o, in, src, &info)
  1392  		if err != nil {
  1393  			return err
  1394  		}
  1395  		return up.Upload(ctx)
  1396  	}
  1397  
  1398  	// Single part upload
  1399  	opts = rest.Opts{
  1400  		Method:        "POST",
  1401  		RootURL:       info.ChunkURI + "&fmt=json",
  1402  		Body:          in,
  1403  		ContentLength: &size,
  1404  	}
  1405  	var finish api.UploadFinishResponse
  1406  	err = o.fs.pacer.CallNoRetry(func() (bool, error) {
  1407  		resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &finish)
  1408  		return shouldRetry(ctx, resp, err)
  1409  	})
  1410  	if err != nil {
  1411  		return fmt.Errorf("upload file: %w", err)
  1412  	}
  1413  	return o.checkUploadResponse(ctx, &finish)
  1414  }
  1415  
  1416  // Check the upload response and update the metadata on the object
  1417  func (o *Object) checkUploadResponse(ctx context.Context, finish *api.UploadFinishResponse) (err error) {
  1418  	// Find returned ID
  1419  	id, err := finish.ID()
  1420  	if err != nil {
  1421  		return err
  1422  	}
  1423  
  1424  	// Read metadata
  1425  	o.id = id
  1426  	o.hasMetaData = false
  1427  	return o.readMetaData(ctx)
  1428  }
  1429  
  1430  // Remove an object by ID
  1431  func (f *Fs) remove(ctx context.Context, id string) (err error) {
  1432  	opts := rest.Opts{
  1433  		Method: "DELETE",
  1434  		Path:   "/Items(" + id + ")",
  1435  		Parameters: url.Values{
  1436  			"singleversion": {"false"},
  1437  			"forceSync":     {"true"},
  1438  		},
  1439  		NoResponse: true,
  1440  	}
  1441  	var resp *http.Response
  1442  	err = f.pacer.Call(func() (bool, error) {
  1443  		resp, err = f.srv.Call(ctx, &opts)
  1444  		return shouldRetry(ctx, resp, err)
  1445  	})
  1446  	if err != nil {
  1447  		return fmt.Errorf("remove: %w", err)
  1448  	}
  1449  	return nil
  1450  }
  1451  
  1452  // Remove an object
  1453  func (o *Object) Remove(ctx context.Context) error {
  1454  	err := o.readMetaData(ctx)
  1455  	if err != nil {
  1456  		return fmt.Errorf("Remove: Failed to read metadata: %w", err)
  1457  	}
  1458  	return o.fs.remove(ctx, o.id)
  1459  }
  1460  
  1461  // ID returns the ID of the Object if known, or "" if not
  1462  func (o *Object) ID() string {
  1463  	return o.id
  1464  }
  1465  
  1466  // Check the interfaces are satisfied
  1467  var (
  1468  	_ fs.Fs       = (*Fs)(nil)
  1469  	_ fs.Purger   = (*Fs)(nil)
  1470  	_ fs.Mover    = (*Fs)(nil)
  1471  	_ fs.DirMover = (*Fs)(nil)
  1472  	_ fs.Copier   = (*Fs)(nil)
  1473  	// _ fs.PutStreamer     = (*Fs)(nil)
  1474  	_ fs.DirCacheFlusher = (*Fs)(nil)
  1475  	_ fs.Shutdowner      = (*Fs)(nil)
  1476  	_ fs.Object          = (*Object)(nil)
  1477  	_ fs.IDer            = (*Object)(nil)
  1478  )