github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/jottacloud/jottacloud.go (about)

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