github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/backend/jottacloud/jottacloud.go (about)

     1  package jottacloud
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/md5"
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"log"
    14  	"net/http"
    15  	"net/url"
    16  	"os"
    17  	"path"
    18  	"strconv"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/pkg/errors"
    23  	"github.com/rclone/rclone/backend/jottacloud/api"
    24  	"github.com/rclone/rclone/fs"
    25  	"github.com/rclone/rclone/fs/accounting"
    26  	"github.com/rclone/rclone/fs/config"
    27  	"github.com/rclone/rclone/fs/config/configmap"
    28  	"github.com/rclone/rclone/fs/config/configstruct"
    29  	"github.com/rclone/rclone/fs/fserrors"
    30  	"github.com/rclone/rclone/fs/fshttp"
    31  	"github.com/rclone/rclone/fs/hash"
    32  	"github.com/rclone/rclone/fs/walk"
    33  	"github.com/rclone/rclone/lib/encoder"
    34  	"github.com/rclone/rclone/lib/oauthutil"
    35  	"github.com/rclone/rclone/lib/pacer"
    36  	"github.com/rclone/rclone/lib/rest"
    37  	"golang.org/x/oauth2"
    38  )
    39  
    40  // Globals
    41  const (
    42  	minSleep          = 10 * time.Millisecond
    43  	maxSleep          = 2 * time.Second
    44  	decayConstant     = 2 // bigger for slower decay, exponential
    45  	defaultDevice     = "Jotta"
    46  	defaultMountpoint = "Archive"
    47  	rootURL           = "https://www.jottacloud.com/jfs/"
    48  	apiURL            = "https://api.jottacloud.com/"
    49  	baseURL           = "https://www.jottacloud.com/"
    50  	defaultTokenURL   = "https://id.jottacloud.com/auth/realms/jottacloud/protocol/openid-connect/token"
    51  	cachePrefix       = "rclone-jcmd5-"
    52  	configDevice      = "device"
    53  	configMountpoint  = "mountpoint"
    54  	configTokenURL    = "tokenURL"
    55  	configVersion     = 1
    56  )
    57  
    58  var (
    59  	// Description of how to auth for this app for a personal account
    60  	oauthConfig = &oauth2.Config{
    61  		ClientID: "jottacli",
    62  		Endpoint: oauth2.Endpoint{
    63  			AuthURL:  defaultTokenURL,
    64  			TokenURL: defaultTokenURL,
    65  		},
    66  		RedirectURL: oauthutil.RedirectLocalhostURL,
    67  	}
    68  )
    69  
    70  // Register with Fs
    71  func init() {
    72  	// needs to be done early so we can use oauth during config
    73  	fs.Register(&fs.RegInfo{
    74  		Name:        "jottacloud",
    75  		Description: "JottaCloud",
    76  		NewFs:       NewFs,
    77  		Config: func(name string, m configmap.Mapper) {
    78  			ctx := context.TODO()
    79  
    80  			refresh := false
    81  			if version, ok := m.Get("configVersion"); ok {
    82  				ver, err := strconv.Atoi(version)
    83  				if err != nil {
    84  					log.Fatalf("Failed to parse config version - corrupted config")
    85  				}
    86  				refresh = ver != configVersion
    87  			}
    88  
    89  			if refresh {
    90  				fmt.Printf("Config outdated - refreshing\n")
    91  			} else {
    92  				tokenString, ok := m.Get("token")
    93  				if ok && tokenString != "" {
    94  					fmt.Printf("Already have a token - refresh?\n")
    95  					if !config.Confirm(false) {
    96  						return
    97  					}
    98  				}
    99  			}
   100  
   101  			clientConfig := *fs.Config
   102  			clientConfig.UserAgent = "JottaCli 0.6.18626 windows-amd64"
   103  			srv := rest.NewClient(fshttp.NewClient(&clientConfig))
   104  
   105  			fmt.Printf("Generate a personal login token here: https://www.jottacloud.com/web/secure\n")
   106  			fmt.Printf("Login Token> ")
   107  			loginToken := config.ReadLine()
   108  
   109  			token, err := doAuth(ctx, srv, loginToken, m)
   110  			if err != nil {
   111  				log.Fatalf("Failed to get oauth token: %s", err)
   112  			}
   113  			err = oauthutil.PutToken(name, m, &token, true)
   114  			if err != nil {
   115  				log.Fatalf("Error while saving token: %s", err)
   116  			}
   117  
   118  			fmt.Printf("\nDo you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?\n\n")
   119  			if config.Confirm(false) {
   120  				oAuthClient, _, err := oauthutil.NewClient(name, m, oauthConfig)
   121  				if err != nil {
   122  					log.Fatalf("Failed to load oAuthClient: %s", err)
   123  				}
   124  
   125  				srv = rest.NewClient(oAuthClient).SetRoot(rootURL)
   126  				apiSrv := rest.NewClient(oAuthClient).SetRoot(apiURL)
   127  
   128  				device, mountpoint, err := setupMountpoint(ctx, srv, apiSrv)
   129  				if err != nil {
   130  					log.Fatalf("Failed to setup mountpoint: %s", err)
   131  				}
   132  				m.Set(configDevice, device)
   133  				m.Set(configMountpoint, mountpoint)
   134  			}
   135  
   136  			m.Set("configVersion", strconv.Itoa(configVersion))
   137  		},
   138  		Options: []fs.Option{{
   139  			Name:     "md5_memory_limit",
   140  			Help:     "Files bigger than this will be cached on disk to calculate the MD5 if required.",
   141  			Default:  fs.SizeSuffix(10 * 1024 * 1024),
   142  			Advanced: true,
   143  		}, {
   144  			Name:     "hard_delete",
   145  			Help:     "Delete files permanently rather than putting them into the trash.",
   146  			Default:  false,
   147  			Advanced: true,
   148  		}, {
   149  			Name:     "unlink",
   150  			Help:     "Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.",
   151  			Default:  false,
   152  			Advanced: true,
   153  		}, {
   154  			Name:     "upload_resume_limit",
   155  			Help:     "Files bigger than this can be resumed if the upload fail's.",
   156  			Default:  fs.SizeSuffix(10 * 1024 * 1024),
   157  			Advanced: true,
   158  		}, {
   159  			Name:     config.ConfigEncoding,
   160  			Help:     config.ConfigEncodingHelp,
   161  			Advanced: true,
   162  			// Encode invalid UTF-8 bytes as xml doesn't handle them properly.
   163  			//
   164  			// Also: '*', '/', ':', '<', '>', '?', '\"', '\x00', '|'
   165  			Default: (encoder.Display |
   166  				encoder.EncodeWin | // :?"*<>|
   167  				encoder.EncodeInvalidUtf8),
   168  		}},
   169  	})
   170  }
   171  
   172  // Options defines the configuration for this backend
   173  type Options struct {
   174  	Device             string               `config:"device"`
   175  	Mountpoint         string               `config:"mountpoint"`
   176  	MD5MemoryThreshold fs.SizeSuffix        `config:"md5_memory_limit"`
   177  	HardDelete         bool                 `config:"hard_delete"`
   178  	Unlink             bool                 `config:"unlink"`
   179  	UploadThreshold    fs.SizeSuffix        `config:"upload_resume_limit"`
   180  	Enc                encoder.MultiEncoder `config:"encoding"`
   181  }
   182  
   183  // Fs represents a remote jottacloud
   184  type Fs struct {
   185  	name         string
   186  	root         string
   187  	user         string
   188  	opt          Options
   189  	features     *fs.Features
   190  	endpointURL  string
   191  	srv          *rest.Client
   192  	apiSrv       *rest.Client
   193  	pacer        *fs.Pacer
   194  	tokenRenewer *oauthutil.Renew // renew the token on expiry
   195  }
   196  
   197  // Object describes a jottacloud object
   198  //
   199  // Will definitely have info but maybe not meta
   200  type Object struct {
   201  	fs          *Fs
   202  	remote      string
   203  	hasMetaData bool
   204  	size        int64
   205  	modTime     time.Time
   206  	md5         string
   207  	mimeType    string
   208  }
   209  
   210  // ------------------------------------------------------------
   211  
   212  // Name of the remote (as passed into NewFs)
   213  func (f *Fs) Name() string {
   214  	return f.name
   215  }
   216  
   217  // Root of the remote (as passed into NewFs)
   218  func (f *Fs) Root() string {
   219  	return f.root
   220  }
   221  
   222  // String converts this Fs to a string
   223  func (f *Fs) String() string {
   224  	return fmt.Sprintf("jottacloud root '%s'", f.root)
   225  }
   226  
   227  // Features returns the optional features of this Fs
   228  func (f *Fs) Features() *fs.Features {
   229  	return f.features
   230  }
   231  
   232  // parsePath parses an box 'url'
   233  func parsePath(path string) (root string) {
   234  	root = strings.Trim(path, "/")
   235  	return
   236  }
   237  
   238  // retryErrorCodes is a slice of error codes that we will retry
   239  var retryErrorCodes = []int{
   240  	429, // Too Many Requests.
   241  	500, // Internal Server Error
   242  	502, // Bad Gateway
   243  	503, // Service Unavailable
   244  	504, // Gateway Timeout
   245  	509, // Bandwidth Limit Exceeded
   246  }
   247  
   248  // shouldRetry returns a boolean as to whether this resp and err
   249  // deserve to be retried.  It returns the err as a convenience
   250  func shouldRetry(resp *http.Response, err error) (bool, error) {
   251  	return fserrors.ShouldRetry(err) || fserrors.ShouldRetryHTTP(resp, retryErrorCodes), err
   252  }
   253  
   254  // doAuth runs the actual token request
   255  func doAuth(ctx context.Context, srv *rest.Client, loginTokenBase64 string, m configmap.Mapper) (token oauth2.Token, err error) {
   256  	loginTokenBytes, err := base64.StdEncoding.DecodeString(loginTokenBase64)
   257  	if err != nil {
   258  		return token, err
   259  	}
   260  
   261  	// decode login token
   262  	var loginToken api.LoginToken
   263  	decoder := json.NewDecoder(bytes.NewReader(loginTokenBytes))
   264  	err = decoder.Decode(&loginToken)
   265  	if err != nil {
   266  		return token, err
   267  	}
   268  
   269  	// retrieve endpoint urls
   270  	opts := rest.Opts{
   271  		Method:  "GET",
   272  		RootURL: loginToken.WellKnownLink,
   273  	}
   274  	var wellKnown api.WellKnown
   275  	_, err = srv.CallJSON(ctx, &opts, nil, &wellKnown)
   276  	if err != nil {
   277  		return token, err
   278  	}
   279  
   280  	// save the tokenurl
   281  	oauthConfig.Endpoint.AuthURL = wellKnown.TokenEndpoint
   282  	oauthConfig.Endpoint.TokenURL = wellKnown.TokenEndpoint
   283  	m.Set(configTokenURL, wellKnown.TokenEndpoint)
   284  
   285  	// prepare out token request with username and password
   286  	values := url.Values{}
   287  	values.Set("client_id", "jottacli")
   288  	values.Set("grant_type", "password")
   289  	values.Set("password", loginToken.AuthToken)
   290  	values.Set("scope", "offline_access+openid")
   291  	values.Set("username", loginToken.Username)
   292  	values.Encode()
   293  	opts = rest.Opts{
   294  		Method:      "POST",
   295  		RootURL:     oauthConfig.Endpoint.AuthURL,
   296  		ContentType: "application/x-www-form-urlencoded",
   297  		Body:        strings.NewReader(values.Encode()),
   298  	}
   299  
   300  	// do the first request
   301  	var jsonToken api.TokenJSON
   302  	_, err = srv.CallJSON(ctx, &opts, nil, &jsonToken)
   303  	if err != nil {
   304  		return token, err
   305  	}
   306  
   307  	token.AccessToken = jsonToken.AccessToken
   308  	token.RefreshToken = jsonToken.RefreshToken
   309  	token.TokenType = jsonToken.TokenType
   310  	token.Expiry = time.Now().Add(time.Duration(jsonToken.ExpiresIn) * time.Second)
   311  	return token, err
   312  }
   313  
   314  // setupMountpoint sets up a custom device and mountpoint if desired by the user
   315  func setupMountpoint(ctx context.Context, srv *rest.Client, apiSrv *rest.Client) (device, mountpoint string, err error) {
   316  	cust, err := getCustomerInfo(ctx, apiSrv)
   317  	if err != nil {
   318  		return "", "", err
   319  	}
   320  
   321  	acc, err := getDriveInfo(ctx, srv, cust.Username)
   322  	if err != nil {
   323  		return "", "", err
   324  	}
   325  	var deviceNames []string
   326  	for i := range acc.Devices {
   327  		deviceNames = append(deviceNames, acc.Devices[i].Name)
   328  	}
   329  	fmt.Printf("Please select the device to use. Normally this will be Jotta\n")
   330  	device = config.Choose("Devices", deviceNames, nil, false)
   331  
   332  	dev, err := getDeviceInfo(ctx, srv, path.Join(cust.Username, device))
   333  	if err != nil {
   334  		return "", "", err
   335  	}
   336  	if len(dev.MountPoints) == 0 {
   337  		return "", "", errors.New("no mountpoints for selected device")
   338  	}
   339  	var mountpointNames []string
   340  	for i := range dev.MountPoints {
   341  		mountpointNames = append(mountpointNames, dev.MountPoints[i].Name)
   342  	}
   343  	fmt.Printf("Please select the mountpoint to user. Normally this will be Archive\n")
   344  	mountpoint = config.Choose("Mountpoints", mountpointNames, nil, false)
   345  
   346  	return device, mountpoint, err
   347  }
   348  
   349  // getCustomerInfo queries general information about the account
   350  func getCustomerInfo(ctx context.Context, srv *rest.Client) (info *api.CustomerInfo, err error) {
   351  	opts := rest.Opts{
   352  		Method: "GET",
   353  		Path:   "account/v1/customer",
   354  	}
   355  
   356  	_, err = srv.CallJSON(ctx, &opts, nil, &info)
   357  	if err != nil {
   358  		return nil, errors.Wrap(err, "couldn't get customer info")
   359  	}
   360  
   361  	return info, nil
   362  }
   363  
   364  // getDriveInfo queries general information about the account and the available devices and mountpoints.
   365  func getDriveInfo(ctx context.Context, srv *rest.Client, username string) (info *api.DriveInfo, err error) {
   366  	opts := rest.Opts{
   367  		Method: "GET",
   368  		Path:   username,
   369  	}
   370  
   371  	_, err = srv.CallXML(ctx, &opts, nil, &info)
   372  	if err != nil {
   373  		return nil, errors.Wrap(err, "couldn't get drive info")
   374  	}
   375  
   376  	return info, nil
   377  }
   378  
   379  // getDeviceInfo queries Information about a jottacloud device
   380  func getDeviceInfo(ctx context.Context, srv *rest.Client, path string) (info *api.JottaDevice, err error) {
   381  	opts := rest.Opts{
   382  		Method: "GET",
   383  		Path:   urlPathEscape(path),
   384  	}
   385  
   386  	_, err = srv.CallXML(ctx, &opts, nil, &info)
   387  	if err != nil {
   388  		return nil, errors.Wrap(err, "couldn't get device info")
   389  	}
   390  
   391  	return info, nil
   392  }
   393  
   394  // setEndpointURL generates the API endpoint URL
   395  func (f *Fs) setEndpointURL() {
   396  	if f.opt.Device == "" {
   397  		f.opt.Device = defaultDevice
   398  	}
   399  	if f.opt.Mountpoint == "" {
   400  		f.opt.Mountpoint = defaultMountpoint
   401  	}
   402  	f.endpointURL = urlPathEscape(path.Join(f.user, f.opt.Device, f.opt.Mountpoint))
   403  }
   404  
   405  // readMetaDataForPath reads the metadata from the path
   406  func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.JottaFile, err error) {
   407  	opts := rest.Opts{
   408  		Method: "GET",
   409  		Path:   f.filePath(path),
   410  	}
   411  	var result api.JottaFile
   412  	var resp *http.Response
   413  	err = f.pacer.Call(func() (bool, error) {
   414  		resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
   415  		return shouldRetry(resp, err)
   416  	})
   417  
   418  	if apiErr, ok := err.(*api.Error); ok {
   419  		// does not exist
   420  		if apiErr.StatusCode == http.StatusNotFound {
   421  			return nil, fs.ErrorObjectNotFound
   422  		}
   423  	}
   424  
   425  	if err != nil {
   426  		return nil, errors.Wrap(err, "read metadata failed")
   427  	}
   428  	if result.XMLName.Local != "file" {
   429  		return nil, fs.ErrorNotAFile
   430  	}
   431  	return &result, nil
   432  }
   433  
   434  // errorHandler parses a non 2xx error response into an error
   435  func errorHandler(resp *http.Response) error {
   436  	// Decode error response
   437  	errResponse := new(api.Error)
   438  	err := rest.DecodeXML(resp, &errResponse)
   439  	if err != nil {
   440  		fs.Debugf(nil, "Couldn't decode error response: %v", err)
   441  	}
   442  	if errResponse.Message == "" {
   443  		errResponse.Message = resp.Status
   444  	}
   445  	if errResponse.StatusCode == 0 {
   446  		errResponse.StatusCode = resp.StatusCode
   447  	}
   448  	return errResponse
   449  }
   450  
   451  // Jottacloud want's '+' to be URL encoded even though the RFC states it's not reserved
   452  func urlPathEscape(in string) string {
   453  	return strings.Replace(rest.URLPathEscape(in), "+", "%2B", -1)
   454  }
   455  
   456  // filePathRaw returns an unescaped file path (f.root, file)
   457  func (f *Fs) filePathRaw(file string) string {
   458  	return path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
   459  }
   460  
   461  // filePath returns a escaped file path (f.root, file)
   462  func (f *Fs) filePath(file string) string {
   463  	return urlPathEscape(f.filePathRaw(file))
   464  }
   465  
   466  // NewFs constructs an Fs from the path, container:path
   467  func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
   468  	ctx := context.TODO()
   469  	// Parse config into Options struct
   470  	opt := new(Options)
   471  	err := configstruct.Set(m, opt)
   472  	if err != nil {
   473  		return nil, err
   474  	}
   475  
   476  	// Check config version
   477  	var ok bool
   478  	var version string
   479  	if version, ok = m.Get("configVersion"); ok {
   480  		ver, err := strconv.Atoi(version)
   481  		if err != nil {
   482  			return nil, errors.New("Failed to parse config version")
   483  		}
   484  		ok = ver == configVersion
   485  	}
   486  	if !ok {
   487  		return nil, errors.New("Outdated config - please reconfigure this backend")
   488  	}
   489  
   490  	// if custome endpoints are set use them else stick with defaults
   491  	if tokenURL, ok := m.Get(configTokenURL); ok {
   492  		oauthConfig.Endpoint.TokenURL = tokenURL
   493  		// jottacloud is weird. we need to use the tokenURL as authURL
   494  		oauthConfig.Endpoint.AuthURL = tokenURL
   495  	}
   496  
   497  	// Create OAuth Client
   498  	baseClient := fshttp.NewClient(fs.Config)
   499  	oAuthClient, ts, err := oauthutil.NewClientWithBaseClient(name, m, oauthConfig, baseClient)
   500  	if err != nil {
   501  		return nil, errors.Wrap(err, "Failed to configure Jottacloud oauth client")
   502  	}
   503  
   504  	rootIsDir := strings.HasSuffix(root, "/")
   505  	root = parsePath(root)
   506  
   507  	f := &Fs{
   508  		name:   name,
   509  		root:   root,
   510  		opt:    *opt,
   511  		srv:    rest.NewClient(oAuthClient).SetRoot(rootURL),
   512  		apiSrv: rest.NewClient(oAuthClient).SetRoot(apiURL),
   513  		pacer:  fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
   514  	}
   515  	f.features = (&fs.Features{
   516  		CaseInsensitive:         true,
   517  		CanHaveEmptyDirectories: true,
   518  		ReadMimeType:            true,
   519  		WriteMimeType:           true,
   520  	}).Fill(f)
   521  	f.srv.SetErrorHandler(errorHandler)
   522  
   523  	// Renew the token in the background
   524  	f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
   525  		_, err := f.readMetaDataForPath(ctx, "")
   526  		return err
   527  	})
   528  
   529  	cust, err := getCustomerInfo(ctx, f.apiSrv)
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  	f.user = cust.Username
   534  	f.setEndpointURL()
   535  
   536  	if root != "" && !rootIsDir {
   537  		// Check to see if the root actually an existing file
   538  		remote := path.Base(root)
   539  		f.root = path.Dir(root)
   540  		if f.root == "." {
   541  			f.root = ""
   542  		}
   543  		_, err := f.NewObject(context.TODO(), remote)
   544  		if err != nil {
   545  			if errors.Cause(err) == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile {
   546  				// File doesn't exist so return old f
   547  				f.root = root
   548  				return f, nil
   549  			}
   550  			return nil, err
   551  		}
   552  		// return an error with an fs which points to the parent
   553  		return f, fs.ErrorIsFile
   554  	}
   555  	return f, nil
   556  }
   557  
   558  // Return an Object from a path
   559  //
   560  // If it can't be found it returns the error fs.ErrorObjectNotFound.
   561  func (f *Fs) newObjectWithInfo(ctx context.Context, remote string, info *api.JottaFile) (fs.Object, error) {
   562  	o := &Object{
   563  		fs:     f,
   564  		remote: remote,
   565  	}
   566  	var err error
   567  	if info != nil {
   568  		// Set info
   569  		err = o.setMetaData(info)
   570  	} else {
   571  		err = o.readMetaData(ctx, false) // reads info and meta, returning an error
   572  	}
   573  	if err != nil {
   574  		return nil, err
   575  	}
   576  	return o, nil
   577  }
   578  
   579  // NewObject finds the Object at remote.  If it can't be found
   580  // it returns the error fs.ErrorObjectNotFound.
   581  func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
   582  	return f.newObjectWithInfo(ctx, remote, nil)
   583  }
   584  
   585  // CreateDir makes a directory
   586  func (f *Fs) CreateDir(ctx context.Context, path string) (jf *api.JottaFolder, err error) {
   587  	// fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, leaf)
   588  	var resp *http.Response
   589  	opts := rest.Opts{
   590  		Method:     "POST",
   591  		Path:       f.filePath(path),
   592  		Parameters: url.Values{},
   593  	}
   594  
   595  	opts.Parameters.Set("mkDir", "true")
   596  
   597  	err = f.pacer.Call(func() (bool, error) {
   598  		resp, err = f.srv.CallXML(ctx, &opts, nil, &jf)
   599  		return shouldRetry(resp, err)
   600  	})
   601  	if err != nil {
   602  		//fmt.Printf("...Error %v\n", err)
   603  		return nil, err
   604  	}
   605  	// fmt.Printf("...Id %q\n", *info.Id)
   606  	return jf, nil
   607  }
   608  
   609  // List the objects and directories in dir into entries.  The
   610  // entries can be returned in any order but should be for a
   611  // complete directory.
   612  //
   613  // dir should be "" to list the root, and should not have
   614  // trailing slashes.
   615  //
   616  // This should return ErrDirNotFound if the directory isn't
   617  // found.
   618  func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
   619  	opts := rest.Opts{
   620  		Method: "GET",
   621  		Path:   f.filePath(dir),
   622  	}
   623  
   624  	var resp *http.Response
   625  	var result api.JottaFolder
   626  	err = f.pacer.Call(func() (bool, error) {
   627  		resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
   628  		return shouldRetry(resp, err)
   629  	})
   630  
   631  	if err != nil {
   632  		if apiErr, ok := err.(*api.Error); ok {
   633  			// does not exist
   634  			if apiErr.StatusCode == http.StatusNotFound {
   635  				return nil, fs.ErrorDirNotFound
   636  			}
   637  		}
   638  		return nil, errors.Wrap(err, "couldn't list files")
   639  	}
   640  
   641  	if result.Deleted {
   642  		return nil, fs.ErrorDirNotFound
   643  	}
   644  
   645  	for i := range result.Folders {
   646  		item := &result.Folders[i]
   647  		if item.Deleted {
   648  			continue
   649  		}
   650  		remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
   651  		d := fs.NewDir(remote, time.Time(item.ModifiedAt))
   652  		entries = append(entries, d)
   653  	}
   654  
   655  	for i := range result.Files {
   656  		item := &result.Files[i]
   657  		if item.Deleted || item.State != "COMPLETED" {
   658  			continue
   659  		}
   660  		remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
   661  		o, err := f.newObjectWithInfo(ctx, remote, item)
   662  		if err != nil {
   663  			continue
   664  		}
   665  		entries = append(entries, o)
   666  	}
   667  	return entries, nil
   668  }
   669  
   670  // listFileDirFn is called from listFileDir to handle an object.
   671  type listFileDirFn func(fs.DirEntry) error
   672  
   673  // List the objects and directories into entries, from a
   674  // special kind of JottaFolder representing a FileDirLis
   675  func (f *Fs) listFileDir(ctx context.Context, remoteStartPath string, startFolder *api.JottaFolder, fn listFileDirFn) error {
   676  	pathPrefix := "/" + f.filePathRaw("") // Non-escaped prefix of API paths to be cut off, to be left with the remote path including the remoteStartPath
   677  	pathPrefixLength := len(pathPrefix)
   678  	startPath := path.Join(pathPrefix, remoteStartPath) // Non-escaped API path up to and including remoteStartPath, to decide if it should be created as a new dir object
   679  	startPathLength := len(startPath)
   680  	for i := range startFolder.Folders {
   681  		folder := &startFolder.Folders[i]
   682  		if folder.Deleted {
   683  			return nil
   684  		}
   685  		folderPath := f.opt.Enc.ToStandardPath(path.Join(folder.Path, folder.Name))
   686  		folderPathLength := len(folderPath)
   687  		var remoteDir string
   688  		if folderPathLength > pathPrefixLength {
   689  			remoteDir = folderPath[pathPrefixLength+1:]
   690  			if folderPathLength > startPathLength {
   691  				d := fs.NewDir(remoteDir, time.Time(folder.ModifiedAt))
   692  				err := fn(d)
   693  				if err != nil {
   694  					return err
   695  				}
   696  			}
   697  		}
   698  		for i := range folder.Files {
   699  			file := &folder.Files[i]
   700  			if file.Deleted || file.State != "COMPLETED" {
   701  				continue
   702  			}
   703  			remoteFile := path.Join(remoteDir, f.opt.Enc.ToStandardName(file.Name))
   704  			o, err := f.newObjectWithInfo(ctx, remoteFile, file)
   705  			if err != nil {
   706  				return err
   707  			}
   708  			err = fn(o)
   709  			if err != nil {
   710  				return err
   711  			}
   712  		}
   713  	}
   714  	return nil
   715  }
   716  
   717  // ListR lists the objects and directories of the Fs starting
   718  // from dir recursively into out.
   719  //
   720  // dir should be "" to start from the root, and should not
   721  // have trailing slashes.
   722  func (f *Fs) ListR(ctx context.Context, dir string, callback fs.ListRCallback) (err error) {
   723  	opts := rest.Opts{
   724  		Method:     "GET",
   725  		Path:       f.filePath(dir),
   726  		Parameters: url.Values{},
   727  	}
   728  	opts.Parameters.Set("mode", "list")
   729  
   730  	var resp *http.Response
   731  	var result api.JottaFolder // Could be JottaFileDirList, but JottaFolder is close enough
   732  	err = f.pacer.Call(func() (bool, error) {
   733  		resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
   734  		return shouldRetry(resp, err)
   735  	})
   736  	if err != nil {
   737  		if apiErr, ok := err.(*api.Error); ok {
   738  			// does not exist
   739  			if apiErr.StatusCode == http.StatusNotFound {
   740  				return fs.ErrorDirNotFound
   741  			}
   742  		}
   743  		return errors.Wrap(err, "couldn't list files")
   744  	}
   745  	list := walk.NewListRHelper(callback)
   746  	err = f.listFileDir(ctx, dir, &result, func(entry fs.DirEntry) error {
   747  		return list.Add(entry)
   748  	})
   749  	if err != nil {
   750  		return err
   751  	}
   752  	return list.Flush()
   753  }
   754  
   755  // Creates from the parameters passed in a half finished Object which
   756  // must have setMetaData called on it
   757  //
   758  // Used to create new objects
   759  func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Object) {
   760  	// Temporary Object under construction
   761  	o = &Object{
   762  		fs:      f,
   763  		remote:  remote,
   764  		size:    size,
   765  		modTime: modTime,
   766  	}
   767  	return o
   768  }
   769  
   770  // Put the object
   771  //
   772  // Copy the reader in to the new object which is returned
   773  //
   774  // The new object may have been created if an error is returned
   775  func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
   776  	if f.opt.Device != "Jotta" {
   777  		return nil, errors.New("upload not supported for devices other than Jotta")
   778  	}
   779  	o := f.createObject(src.Remote(), src.ModTime(ctx), src.Size())
   780  	return o, o.Update(ctx, in, src, options...)
   781  }
   782  
   783  // mkParentDir makes the parent of the native path dirPath if
   784  // necessary and any directories above that
   785  func (f *Fs) mkParentDir(ctx context.Context, dirPath string) error {
   786  	// defer log.Trace(dirPath, "")("")
   787  	// chop off trailing / if it exists
   788  	if strings.HasSuffix(dirPath, "/") {
   789  		dirPath = dirPath[:len(dirPath)-1]
   790  	}
   791  	parent := path.Dir(dirPath)
   792  	if parent == "." {
   793  		parent = ""
   794  	}
   795  	return f.Mkdir(ctx, parent)
   796  }
   797  
   798  // Mkdir creates the container if it doesn't exist
   799  func (f *Fs) Mkdir(ctx context.Context, dir string) error {
   800  	_, err := f.CreateDir(ctx, dir)
   801  	return err
   802  }
   803  
   804  // purgeCheck removes the root directory, if check is set then it
   805  // refuses to do so if it has anything in
   806  func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) (err error) {
   807  	root := path.Join(f.root, dir)
   808  	if root == "" {
   809  		return errors.New("can't purge root directory")
   810  	}
   811  
   812  	// check that the directory exists
   813  	entries, err := f.List(ctx, dir)
   814  	if err != nil {
   815  		return err
   816  	}
   817  
   818  	if check {
   819  		if len(entries) != 0 {
   820  			return fs.ErrorDirectoryNotEmpty
   821  		}
   822  	}
   823  
   824  	opts := rest.Opts{
   825  		Method:     "POST",
   826  		Path:       f.filePath(dir),
   827  		Parameters: url.Values{},
   828  		NoResponse: true,
   829  	}
   830  
   831  	if f.opt.HardDelete {
   832  		opts.Parameters.Set("rmDir", "true")
   833  	} else {
   834  		opts.Parameters.Set("dlDir", "true")
   835  	}
   836  
   837  	var resp *http.Response
   838  	err = f.pacer.Call(func() (bool, error) {
   839  		resp, err = f.srv.Call(ctx, &opts)
   840  		return shouldRetry(resp, err)
   841  	})
   842  	if err != nil {
   843  		return errors.Wrap(err, "couldn't purge directory")
   844  	}
   845  
   846  	return nil
   847  }
   848  
   849  // Rmdir deletes the root folder
   850  //
   851  // Returns an error if it isn't empty
   852  func (f *Fs) Rmdir(ctx context.Context, dir string) error {
   853  	return f.purgeCheck(ctx, dir, true)
   854  }
   855  
   856  // Precision return the precision of this Fs
   857  func (f *Fs) Precision() time.Duration {
   858  	return time.Second
   859  }
   860  
   861  // Purge deletes all the files and the container
   862  func (f *Fs) Purge(ctx context.Context) error {
   863  	return f.purgeCheck(ctx, "", false)
   864  }
   865  
   866  // copyOrMoves copies or moves directories or files depending on the method parameter
   867  func (f *Fs) copyOrMove(ctx context.Context, method, src, dest string) (info *api.JottaFile, err error) {
   868  	opts := rest.Opts{
   869  		Method:     "POST",
   870  		Path:       src,
   871  		Parameters: url.Values{},
   872  	}
   873  
   874  	opts.Parameters.Set(method, "/"+path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, dest))))
   875  
   876  	var resp *http.Response
   877  	err = f.pacer.Call(func() (bool, error) {
   878  		resp, err = f.srv.CallXML(ctx, &opts, nil, &info)
   879  		return shouldRetry(resp, err)
   880  	})
   881  	if err != nil {
   882  		return nil, err
   883  	}
   884  	return info, nil
   885  }
   886  
   887  // Copy src to this remote using server side copy operations.
   888  //
   889  // This is stored with the remote path given
   890  //
   891  // It returns the destination Object and a possible error
   892  //
   893  // Will only be called if src.Fs().Name() == f.Name()
   894  //
   895  // If it isn't possible then return fs.ErrorCantCopy
   896  func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   897  	srcObj, ok := src.(*Object)
   898  	if !ok {
   899  		fs.Debugf(src, "Can't copy - not same remote type")
   900  		return nil, fs.ErrorCantMove
   901  	}
   902  
   903  	err := f.mkParentDir(ctx, remote)
   904  	if err != nil {
   905  		return nil, err
   906  	}
   907  	info, err := f.copyOrMove(ctx, "cp", srcObj.filePath(), remote)
   908  
   909  	if err != nil {
   910  		return nil, errors.Wrap(err, "couldn't copy file")
   911  	}
   912  
   913  	return f.newObjectWithInfo(ctx, remote, info)
   914  	//return f.newObjectWithInfo(remote, &result)
   915  }
   916  
   917  // Move src to this remote using server side move operations.
   918  //
   919  // This is stored with the remote path given
   920  //
   921  // It returns the destination Object and a possible error
   922  //
   923  // Will only be called if src.Fs().Name() == f.Name()
   924  //
   925  // If it isn't possible then return fs.ErrorCantMove
   926  func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) {
   927  	srcObj, ok := src.(*Object)
   928  	if !ok {
   929  		fs.Debugf(src, "Can't move - not same remote type")
   930  		return nil, fs.ErrorCantMove
   931  	}
   932  
   933  	err := f.mkParentDir(ctx, remote)
   934  	if err != nil {
   935  		return nil, err
   936  	}
   937  	info, err := f.copyOrMove(ctx, "mv", srcObj.filePath(), remote)
   938  
   939  	if err != nil {
   940  		return nil, errors.Wrap(err, "couldn't move file")
   941  	}
   942  
   943  	return f.newObjectWithInfo(ctx, remote, info)
   944  	//return f.newObjectWithInfo(remote, result)
   945  }
   946  
   947  // DirMove moves src, srcRemote to this remote at dstRemote
   948  // using server side move operations.
   949  //
   950  // Will only be called if src.Fs().Name() == f.Name()
   951  //
   952  // If it isn't possible then return fs.ErrorCantDirMove
   953  //
   954  // If destination exists then return fs.ErrorDirExists
   955  func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error {
   956  	srcFs, ok := src.(*Fs)
   957  	if !ok {
   958  		fs.Debugf(srcFs, "Can't move directory - not same remote type")
   959  		return fs.ErrorCantDirMove
   960  	}
   961  	srcPath := path.Join(srcFs.root, srcRemote)
   962  	dstPath := path.Join(f.root, dstRemote)
   963  
   964  	// Refuse to move to or from the root
   965  	if srcPath == "" || dstPath == "" {
   966  		fs.Debugf(src, "DirMove error: Can't move root")
   967  		return errors.New("can't move root directory")
   968  	}
   969  	//fmt.Printf("Move src: %s (FullPath %s), dst: %s (FullPath: %s)\n", srcRemote, srcPath, dstRemote, dstPath)
   970  
   971  	var err error
   972  	_, err = f.List(ctx, dstRemote)
   973  	if err == fs.ErrorDirNotFound {
   974  		// OK
   975  	} else if err != nil {
   976  		return err
   977  	} else {
   978  		return fs.ErrorDirExists
   979  	}
   980  
   981  	_, err = f.copyOrMove(ctx, "mvDir", path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(srcPath))+"/", dstRemote)
   982  
   983  	if err != nil {
   984  		return errors.Wrap(err, "couldn't move directory")
   985  	}
   986  	return nil
   987  }
   988  
   989  // PublicLink generates a public link to the remote path (usually readable by anyone)
   990  func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err error) {
   991  	opts := rest.Opts{
   992  		Method:     "GET",
   993  		Path:       f.filePath(remote),
   994  		Parameters: url.Values{},
   995  	}
   996  
   997  	if f.opt.Unlink {
   998  		opts.Parameters.Set("mode", "disableShare")
   999  	} else {
  1000  		opts.Parameters.Set("mode", "enableShare")
  1001  	}
  1002  
  1003  	var resp *http.Response
  1004  	var result api.JottaFile
  1005  	err = f.pacer.Call(func() (bool, error) {
  1006  		resp, err = f.srv.CallXML(ctx, &opts, nil, &result)
  1007  		return shouldRetry(resp, err)
  1008  	})
  1009  
  1010  	if apiErr, ok := err.(*api.Error); ok {
  1011  		// does not exist
  1012  		if apiErr.StatusCode == http.StatusNotFound {
  1013  			return "", fs.ErrorObjectNotFound
  1014  		}
  1015  	}
  1016  	if err != nil {
  1017  		if f.opt.Unlink {
  1018  			return "", errors.Wrap(err, "couldn't remove public link")
  1019  		}
  1020  		return "", errors.Wrap(err, "couldn't create public link")
  1021  	}
  1022  	if f.opt.Unlink {
  1023  		if result.PublicSharePath != "" {
  1024  			return "", errors.Errorf("couldn't remove public link - %q", result.PublicSharePath)
  1025  		}
  1026  		return "", nil
  1027  	}
  1028  	if result.PublicSharePath == "" {
  1029  		return "", errors.New("couldn't create public link - no link path received")
  1030  	}
  1031  	link = path.Join(baseURL, result.PublicSharePath)
  1032  	return link, nil
  1033  }
  1034  
  1035  // About gets quota information
  1036  func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
  1037  	info, err := getDriveInfo(ctx, f.srv, f.user)
  1038  	if err != nil {
  1039  		return nil, err
  1040  	}
  1041  
  1042  	usage := &fs.Usage{
  1043  		Used: fs.NewUsageValue(info.Usage),
  1044  	}
  1045  	if info.Capacity > 0 {
  1046  		usage.Total = fs.NewUsageValue(info.Capacity)
  1047  		usage.Free = fs.NewUsageValue(info.Capacity - info.Usage)
  1048  	}
  1049  	return usage, nil
  1050  }
  1051  
  1052  // Hashes returns the supported hash sets.
  1053  func (f *Fs) Hashes() hash.Set {
  1054  	return hash.Set(hash.MD5)
  1055  }
  1056  
  1057  // ---------------------------------------------
  1058  
  1059  // Fs returns the parent Fs
  1060  func (o *Object) Fs() fs.Info {
  1061  	return o.fs
  1062  }
  1063  
  1064  // Return a string version
  1065  func (o *Object) String() string {
  1066  	if o == nil {
  1067  		return "<nil>"
  1068  	}
  1069  	return o.remote
  1070  }
  1071  
  1072  // Remote returns the remote path
  1073  func (o *Object) Remote() string {
  1074  	return o.remote
  1075  }
  1076  
  1077  // filePath returns a escaped file path (f.root, remote)
  1078  func (o *Object) filePath() string {
  1079  	return o.fs.filePath(o.remote)
  1080  }
  1081  
  1082  // Hash returns the MD5 of an object returning a lowercase hex string
  1083  func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
  1084  	if t != hash.MD5 {
  1085  		return "", hash.ErrUnsupported
  1086  	}
  1087  	return o.md5, nil
  1088  }
  1089  
  1090  // Size returns the size of an object in bytes
  1091  func (o *Object) Size() int64 {
  1092  	ctx := context.TODO()
  1093  	err := o.readMetaData(ctx, false)
  1094  	if err != nil {
  1095  		fs.Logf(o, "Failed to read metadata: %v", err)
  1096  		return 0
  1097  	}
  1098  	return o.size
  1099  }
  1100  
  1101  // MimeType of an Object if known, "" otherwise
  1102  func (o *Object) MimeType(ctx context.Context) string {
  1103  	return o.mimeType
  1104  }
  1105  
  1106  // setMetaData sets the metadata from info
  1107  func (o *Object) setMetaData(info *api.JottaFile) (err error) {
  1108  	o.hasMetaData = true
  1109  	o.size = info.Size
  1110  	o.md5 = info.MD5
  1111  	o.mimeType = info.MimeType
  1112  	o.modTime = time.Time(info.ModifiedAt)
  1113  	return nil
  1114  }
  1115  
  1116  // readMetaData reads and updates the metadata for an object
  1117  func (o *Object) readMetaData(ctx context.Context, force bool) (err error) {
  1118  	if o.hasMetaData && !force {
  1119  		return nil
  1120  	}
  1121  	info, err := o.fs.readMetaDataForPath(ctx, o.remote)
  1122  	if err != nil {
  1123  		return err
  1124  	}
  1125  	if info.Deleted {
  1126  		return fs.ErrorObjectNotFound
  1127  	}
  1128  	return o.setMetaData(info)
  1129  }
  1130  
  1131  // ModTime returns the modification time of the object
  1132  //
  1133  // It attempts to read the objects mtime and if that isn't present the
  1134  // LastModified returned in the http headers
  1135  func (o *Object) ModTime(ctx context.Context) time.Time {
  1136  	err := o.readMetaData(ctx, false)
  1137  	if err != nil {
  1138  		fs.Logf(o, "Failed to read metadata: %v", err)
  1139  		return time.Now()
  1140  	}
  1141  	return o.modTime
  1142  }
  1143  
  1144  // SetModTime sets the modification time of the local fs object
  1145  func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
  1146  	return fs.ErrorCantSetModTime
  1147  }
  1148  
  1149  // Storable returns a boolean showing whether this object storable
  1150  func (o *Object) Storable() bool {
  1151  	return true
  1152  }
  1153  
  1154  // Open an object for read
  1155  func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.ReadCloser, err error) {
  1156  	fs.FixRangeOption(options, o.size)
  1157  	var resp *http.Response
  1158  	opts := rest.Opts{
  1159  		Method:     "GET",
  1160  		Path:       o.filePath(),
  1161  		Parameters: url.Values{},
  1162  		Options:    options,
  1163  	}
  1164  
  1165  	opts.Parameters.Set("mode", "bin")
  1166  
  1167  	err = o.fs.pacer.Call(func() (bool, error) {
  1168  		resp, err = o.fs.srv.Call(ctx, &opts)
  1169  		return shouldRetry(resp, err)
  1170  	})
  1171  	if err != nil {
  1172  		return nil, err
  1173  	}
  1174  	return resp.Body, err
  1175  }
  1176  
  1177  // Read the md5 of in returning a reader which will read the same contents
  1178  //
  1179  // The cleanup function should be called when out is finished with
  1180  // regardless of whether this function returned an error or not.
  1181  func readMD5(in io.Reader, size, threshold int64) (md5sum string, out io.Reader, cleanup func(), err error) {
  1182  	// we need a MD5
  1183  	md5Hasher := md5.New()
  1184  	// use the teeReader to write to the local file AND calculate the MD5 while doing so
  1185  	teeReader := io.TeeReader(in, md5Hasher)
  1186  
  1187  	// nothing to clean up by default
  1188  	cleanup = func() {}
  1189  
  1190  	// don't cache small files on disk to reduce wear of the disk
  1191  	if size > threshold {
  1192  		var tempFile *os.File
  1193  
  1194  		// create the cache file
  1195  		tempFile, err = ioutil.TempFile("", cachePrefix)
  1196  		if err != nil {
  1197  			return
  1198  		}
  1199  
  1200  		_ = os.Remove(tempFile.Name()) // Delete the file - may not work on Windows
  1201  
  1202  		// clean up the file after we are done downloading
  1203  		cleanup = func() {
  1204  			// the file should normally already be close, but just to make sure
  1205  			_ = tempFile.Close()
  1206  			_ = os.Remove(tempFile.Name()) // delete the cache file after we are done - may be deleted already
  1207  		}
  1208  
  1209  		// copy the ENTIRE file to disc and calculate the MD5 in the process
  1210  		if _, err = io.Copy(tempFile, teeReader); err != nil {
  1211  			return
  1212  		}
  1213  		// jump to the start of the local file so we can pass it along
  1214  		if _, err = tempFile.Seek(0, 0); err != nil {
  1215  			return
  1216  		}
  1217  
  1218  		// replace the already read source with a reader of our cached file
  1219  		out = tempFile
  1220  	} else {
  1221  		// that's a small file, just read it into memory
  1222  		var inData []byte
  1223  		inData, err = ioutil.ReadAll(teeReader)
  1224  		if err != nil {
  1225  			return
  1226  		}
  1227  
  1228  		// set the reader to our read memory block
  1229  		out = bytes.NewReader(inData)
  1230  	}
  1231  	return hex.EncodeToString(md5Hasher.Sum(nil)), out, cleanup, nil
  1232  }
  1233  
  1234  // Update the object with the contents of the io.Reader, modTime and size
  1235  //
  1236  // If existing is set then it updates the object rather than creating a new one
  1237  //
  1238  // The new object may have been created if an error is returned
  1239  func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) {
  1240  	size := src.Size()
  1241  	md5String, err := src.Hash(ctx, hash.MD5)
  1242  	if err != nil || md5String == "" {
  1243  		// unwrap the accounting from the input, we use wrap to put it
  1244  		// back on after the buffering
  1245  		var wrap accounting.WrapFn
  1246  		in, wrap = accounting.UnWrap(in)
  1247  		var cleanup func()
  1248  		md5String, in, cleanup, err = readMD5(in, size, int64(o.fs.opt.MD5MemoryThreshold))
  1249  		defer cleanup()
  1250  		if err != nil {
  1251  			return errors.Wrap(err, "failed to calculate MD5")
  1252  		}
  1253  		// Wrap the accounting back onto the stream
  1254  		in = wrap(in)
  1255  	}
  1256  
  1257  	// use the api to allocate the file first and get resume / deduplication info
  1258  	var resp *http.Response
  1259  	opts := rest.Opts{
  1260  		Method:       "POST",
  1261  		Path:         "files/v1/allocate",
  1262  		ExtraHeaders: make(map[string]string),
  1263  	}
  1264  	fileDate := api.Time(src.ModTime(ctx)).APIString()
  1265  
  1266  	// the allocate request
  1267  	var request = api.AllocateFileRequest{
  1268  		Bytes:    size,
  1269  		Created:  fileDate,
  1270  		Modified: fileDate,
  1271  		Md5:      md5String,
  1272  		Path:     path.Join(o.fs.opt.Mountpoint, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
  1273  	}
  1274  
  1275  	// send it
  1276  	var response api.AllocateFileResponse
  1277  	err = o.fs.pacer.CallNoRetry(func() (bool, error) {
  1278  		resp, err = o.fs.apiSrv.CallJSON(ctx, &opts, &request, &response)
  1279  		return shouldRetry(resp, err)
  1280  	})
  1281  	if err != nil {
  1282  		return err
  1283  	}
  1284  
  1285  	// If the file state is INCOMPLETE and CORRPUT, try to upload a then
  1286  	if response.State != "COMPLETED" {
  1287  		// how much do we still have to upload?
  1288  		remainingBytes := size - response.ResumePos
  1289  		opts = rest.Opts{
  1290  			Method:        "POST",
  1291  			RootURL:       response.UploadURL,
  1292  			ContentLength: &remainingBytes,
  1293  			ContentType:   "application/octet-stream",
  1294  			Body:          in,
  1295  			ExtraHeaders:  make(map[string]string),
  1296  		}
  1297  		if response.ResumePos != 0 {
  1298  			opts.ExtraHeaders["Range"] = "bytes=" + strconv.FormatInt(response.ResumePos, 10) + "-" + strconv.FormatInt(size-1, 10)
  1299  		}
  1300  
  1301  		// copy the already uploaded bytes into the trash :)
  1302  		var result api.UploadResponse
  1303  		_, err = io.CopyN(ioutil.Discard, in, response.ResumePos)
  1304  		if err != nil {
  1305  			return err
  1306  		}
  1307  
  1308  		// send the remaining bytes
  1309  		resp, err = o.fs.apiSrv.CallJSON(ctx, &opts, nil, &result)
  1310  		if err != nil {
  1311  			return err
  1312  		}
  1313  
  1314  		// finally update the meta data
  1315  		o.hasMetaData = true
  1316  		o.size = result.Bytes
  1317  		o.md5 = result.Md5
  1318  		o.modTime = time.Unix(result.Modified/1000, 0)
  1319  	} else {
  1320  		// If the file state is COMPLETE we don't need to upload it because the file was already found but we still ned to update our metadata
  1321  		return o.readMetaData(ctx, true)
  1322  	}
  1323  
  1324  	return nil
  1325  }
  1326  
  1327  // Remove an object
  1328  func (o *Object) Remove(ctx context.Context) error {
  1329  	opts := rest.Opts{
  1330  		Method:     "POST",
  1331  		Path:       o.filePath(),
  1332  		Parameters: url.Values{},
  1333  		NoResponse: true,
  1334  	}
  1335  
  1336  	if o.fs.opt.HardDelete {
  1337  		opts.Parameters.Set("rm", "true")
  1338  	} else {
  1339  		opts.Parameters.Set("dl", "true")
  1340  	}
  1341  
  1342  	return o.fs.pacer.Call(func() (bool, error) {
  1343  		resp, err := o.fs.srv.CallXML(ctx, &opts, nil, nil)
  1344  		return shouldRetry(resp, err)
  1345  	})
  1346  }
  1347  
  1348  // Check the interfaces are satisfied
  1349  var (
  1350  	_ fs.Fs           = (*Fs)(nil)
  1351  	_ fs.Purger       = (*Fs)(nil)
  1352  	_ fs.Copier       = (*Fs)(nil)
  1353  	_ fs.Mover        = (*Fs)(nil)
  1354  	_ fs.DirMover     = (*Fs)(nil)
  1355  	_ fs.ListRer      = (*Fs)(nil)
  1356  	_ fs.PublicLinker = (*Fs)(nil)
  1357  	_ fs.Abouter      = (*Fs)(nil)
  1358  	_ fs.Object       = (*Object)(nil)
  1359  	_ fs.MimeTyper    = (*Object)(nil)
  1360  )