github.com/cs3org/reva/v2@v2.27.7/pkg/datatx/manager/rclone/rclone.go (about)

     1  // Copyright 2018-2020 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package rclone
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"net/http"
    28  	"net/url"
    29  	"os"
    30  	"path"
    31  	"strconv"
    32  	"sync"
    33  	"time"
    34  
    35  	datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
    36  	typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    37  	"github.com/cs3org/reva/v2/pkg/appctx"
    38  	txdriver "github.com/cs3org/reva/v2/pkg/datatx"
    39  	registry "github.com/cs3org/reva/v2/pkg/datatx/manager/registry"
    40  	"github.com/cs3org/reva/v2/pkg/rhttp"
    41  	"github.com/google/uuid"
    42  	"github.com/mitchellh/mapstructure"
    43  	"github.com/pkg/errors"
    44  )
    45  
    46  func init() {
    47  	registry.Register("rclone", New)
    48  }
    49  
    50  func (c *config) init(m map[string]interface{}) {
    51  	// set sane defaults
    52  	if c.File == "" {
    53  		c.File = "/var/tmp/reva/datatx-transfers.json"
    54  	}
    55  	if c.JobStatusCheckInterval == 0 {
    56  		c.JobStatusCheckInterval = 2000
    57  	}
    58  	if c.JobTimeout == 0 {
    59  		c.JobTimeout = 50000
    60  	}
    61  }
    62  
    63  type config struct {
    64  	Endpoint               string `mapstructure:"endpoint"`
    65  	AuthUser               string `mapstructure:"auth_user"` // rclone basicauth user
    66  	AuthPass               string `mapstructure:"auth_pass"` // rclone basicauth pass
    67  	File                   string `mapstructure:"file"`
    68  	JobStatusCheckInterval int    `mapstructure:"job_status_check_interval"`
    69  	JobTimeout             int    `mapstructure:"job_timeout"`
    70  }
    71  
    72  type rclone struct {
    73  	config  *config
    74  	client  *http.Client
    75  	pDriver *pDriver
    76  }
    77  
    78  type rcloneHTTPErrorRes struct {
    79  	Error  string                 `json:"error"`
    80  	Input  map[string]interface{} `json:"input"`
    81  	Path   string                 `json:"path"`
    82  	Status int                    `json:"status"`
    83  }
    84  
    85  type transferModel struct {
    86  	File      string
    87  	Transfers map[string]*transfer `json:"transfers"`
    88  }
    89  
    90  // persistency driver
    91  type pDriver struct {
    92  	sync.Mutex // concurrent access to the file
    93  	model      *transferModel
    94  }
    95  
    96  type transfer struct {
    97  	TransferID     string
    98  	JobID          int64
    99  	TransferStatus datatx.Status
   100  	SrcToken       string
   101  	SrcRemote      string
   102  	SrcPath        string
   103  	DestToken      string
   104  	DestRemote     string
   105  	DestPath       string
   106  	Ctime          string
   107  }
   108  
   109  // txEndStatuses final statuses that cannot be changed anymore
   110  var txEndStatuses = map[string]int32{
   111  	"STATUS_INVALID":                0,
   112  	"STATUS_DESTINATION_NOT_FOUND":  1,
   113  	"STATUS_TRANSFER_COMPLETE":      6,
   114  	"STATUS_TRANSFER_FAILED":        7,
   115  	"STATUS_TRANSFER_CANCELLED":     8,
   116  	"STATUS_TRANSFER_CANCEL_FAILED": 9,
   117  	"STATUS_TRANSFER_EXPIRED":       10,
   118  }
   119  
   120  // New returns a new rclone driver
   121  func New(m map[string]interface{}) (txdriver.Manager, error) {
   122  	c, err := parseConfig(m)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	c.init(m)
   127  
   128  	// TODO insecure should be configurable
   129  	client := rhttp.GetHTTPClient(rhttp.Insecure(true))
   130  
   131  	// The persistency driver
   132  	// Load or create 'db'
   133  	model, err := loadOrCreate(c.File)
   134  	if err != nil {
   135  		err = errors.Wrap(err, "error loading the file containing the transfers")
   136  		return nil, err
   137  	}
   138  	pDriver := &pDriver{
   139  		model: model,
   140  	}
   141  
   142  	return &rclone{
   143  		config:  c,
   144  		client:  client,
   145  		pDriver: pDriver,
   146  	}, nil
   147  }
   148  
   149  func parseConfig(m map[string]interface{}) (*config, error) {
   150  	c := &config{}
   151  	if err := mapstructure.Decode(m, c); err != nil {
   152  		err = errors.Wrap(err, "error decoding conf")
   153  		return nil, err
   154  	}
   155  	return c, nil
   156  }
   157  
   158  func loadOrCreate(file string) (*transferModel, error) {
   159  	_, err := os.Stat(file)
   160  	if os.IsNotExist(err) {
   161  		if err := os.WriteFile(file, []byte("{}"), 0700); err != nil {
   162  			err = errors.Wrap(err, "error creating the transfers storage file: "+file)
   163  			return nil, err
   164  		}
   165  	}
   166  
   167  	fd, err := os.OpenFile(file, os.O_CREATE, 0644)
   168  	if err != nil {
   169  		err = errors.Wrap(err, "error opening the transfers storage file: "+file)
   170  		return nil, err
   171  	}
   172  	defer fd.Close()
   173  
   174  	data, err := io.ReadAll(fd)
   175  	if err != nil {
   176  		err = errors.Wrap(err, "error reading the data")
   177  		return nil, err
   178  	}
   179  
   180  	model := &transferModel{}
   181  	if err := json.Unmarshal(data, model); err != nil {
   182  		err = errors.Wrap(err, "error decoding transfers data to json")
   183  		return nil, err
   184  	}
   185  
   186  	if model.Transfers == nil {
   187  		model.Transfers = make(map[string]*transfer)
   188  	}
   189  
   190  	model.File = file
   191  	return model, nil
   192  }
   193  
   194  // saveTransfer saves the transfer. If an error is specified than that error will be returned, possibly wrapped with additional errors.
   195  func (m *transferModel) saveTransfer(e error) error {
   196  	data, err := json.Marshal(m)
   197  	if err != nil {
   198  		e = errors.Wrap(err, "error encoding transfer data to json")
   199  		return e
   200  	}
   201  
   202  	if err := os.WriteFile(m.File, data, 0644); err != nil {
   203  		e = errors.Wrap(err, "error writing transfer data to file: "+m.File)
   204  		return e
   205  	}
   206  
   207  	return e
   208  }
   209  
   210  // StartTransfer initiates a transfer job and returns a TxInfo object that includes a unique transfer id.
   211  func (driver *rclone) StartTransfer(ctx context.Context, srcRemote string, srcPath string, srcToken string, destRemote string, destPath string, destToken string) (*datatx.TxInfo, error) {
   212  	return driver.startJob(ctx, "", srcRemote, srcPath, srcToken, destRemote, destPath, destToken)
   213  }
   214  
   215  // startJob starts a transfer job. Retries a previous job if transferID is specified.
   216  func (driver *rclone) startJob(ctx context.Context, transferID string, srcRemote string, srcPath string, srcToken string, destRemote string, destPath string, destToken string) (*datatx.TxInfo, error) {
   217  	logger := appctx.GetLogger(ctx)
   218  
   219  	driver.pDriver.Lock()
   220  	defer driver.pDriver.Unlock()
   221  
   222  	var txID string
   223  	var cTime *typespb.Timestamp
   224  
   225  	if transferID == "" {
   226  		txID = uuid.New().String()
   227  		cTime = &typespb.Timestamp{Seconds: uint64(time.Now().Unix())}
   228  	} else { // restart existing transfer if transferID is specified
   229  		logger.Debug().Msgf("Restarting transfer (txID: %s)", transferID)
   230  		txID = transferID
   231  		transfer, err := driver.pDriver.model.getTransfer(txID)
   232  		if err != nil {
   233  			err = errors.Wrap(err, "rclone: error retrying transfer (transferID:  "+txID+")")
   234  			return &datatx.TxInfo{
   235  				Id:     &datatx.TxId{OpaqueId: txID},
   236  				Status: datatx.Status_STATUS_INVALID,
   237  				Ctime:  nil,
   238  			}, err
   239  		}
   240  		seconds, _ := strconv.ParseInt(transfer.Ctime, 10, 64)
   241  		cTime = &typespb.Timestamp{Seconds: uint64(seconds)}
   242  		_, endStatusFound := txEndStatuses[transfer.TransferStatus.String()]
   243  		if !endStatusFound {
   244  			err := errors.New("rclone: transfer still running, unable to restart")
   245  			return &datatx.TxInfo{
   246  				Id:     &datatx.TxId{OpaqueId: txID},
   247  				Status: transfer.TransferStatus,
   248  				Ctime:  cTime,
   249  			}, err
   250  		}
   251  		srcToken = transfer.SrcToken
   252  		srcRemote = transfer.SrcRemote
   253  		srcPath = transfer.SrcPath
   254  		destToken = transfer.DestToken
   255  		destRemote = transfer.DestRemote
   256  		destPath = transfer.DestPath
   257  		delete(driver.pDriver.model.Transfers, txID)
   258  	}
   259  
   260  	transferStatus := datatx.Status_STATUS_TRANSFER_NEW
   261  
   262  	transfer := &transfer{
   263  		TransferID:     txID,
   264  		JobID:          int64(-1),
   265  		TransferStatus: transferStatus,
   266  		SrcToken:       srcToken,
   267  		SrcRemote:      srcRemote,
   268  		SrcPath:        srcPath,
   269  		DestToken:      destToken,
   270  		DestRemote:     destRemote,
   271  		DestPath:       destPath,
   272  		Ctime:          fmt.Sprint(cTime.Seconds), // TODO do we need nanos here?
   273  	}
   274  
   275  	driver.pDriver.model.Transfers[txID] = transfer
   276  
   277  	type rcloneAsyncReqJSON struct {
   278  		SrcFs string `json:"srcFs"`
   279  		// SrcToken string `json:"srcToken"`
   280  		DstFs string `json:"dstFs"`
   281  		// DstToken string `json:"destToken"`
   282  		Async bool `json:"_async"`
   283  	}
   284  	srcFs := fmt.Sprintf(":webdav,headers=\"x-access-token,%v\",url=\"%v\":%v", srcToken, srcRemote, srcPath)
   285  	dstFs := fmt.Sprintf(":webdav,headers=\"x-access-token,%v\",url=\"%v\":%v", destToken, destRemote, destPath)
   286  	rcloneReq := &rcloneAsyncReqJSON{
   287  		SrcFs: srcFs,
   288  		DstFs: dstFs,
   289  		Async: true,
   290  	}
   291  	data, err := json.Marshal(rcloneReq)
   292  	if err != nil {
   293  		err = errors.Wrap(err, "rclone: error pulling transfer: error marshalling rclone req data")
   294  		transfer.TransferStatus = datatx.Status_STATUS_INVALID
   295  		return &datatx.TxInfo{
   296  			Id:     &datatx.TxId{OpaqueId: txID},
   297  			Status: datatx.Status_STATUS_INVALID,
   298  			Ctime:  cTime,
   299  		}, driver.pDriver.model.saveTransfer(err)
   300  	}
   301  
   302  	transferFileMethod := "/sync/copy"
   303  	remotePathIsFolder, err := driver.remotePathIsFolder(srcRemote, srcPath, srcToken)
   304  	if err != nil {
   305  		err = errors.Wrap(err, "rclone: error pulling transfer: error stating src path")
   306  		transfer.TransferStatus = datatx.Status_STATUS_INVALID
   307  		return &datatx.TxInfo{
   308  			Id:     &datatx.TxId{OpaqueId: txID},
   309  			Status: datatx.Status_STATUS_INVALID,
   310  			Ctime:  cTime,
   311  		}, driver.pDriver.model.saveTransfer(err)
   312  	}
   313  	if !remotePathIsFolder {
   314  		err = errors.Wrap(err, "rclone: error pulling transfer: path is a file, only folder transfer is implemented")
   315  		transfer.TransferStatus = datatx.Status_STATUS_INVALID
   316  		return &datatx.TxInfo{
   317  			Id:     &datatx.TxId{OpaqueId: txID},
   318  			Status: datatx.Status_STATUS_INVALID,
   319  			Ctime:  cTime,
   320  		}, driver.pDriver.model.saveTransfer(err)
   321  	}
   322  
   323  	u, err := url.Parse(driver.config.Endpoint)
   324  	if err != nil {
   325  		err = errors.Wrap(err, "rclone: error pulling transfer: error parsing driver endpoint")
   326  		transfer.TransferStatus = datatx.Status_STATUS_INVALID
   327  		return &datatx.TxInfo{
   328  			Id:     &datatx.TxId{OpaqueId: txID},
   329  			Status: datatx.Status_STATUS_INVALID,
   330  			Ctime:  cTime,
   331  		}, driver.pDriver.model.saveTransfer(err)
   332  	}
   333  	u.Path = path.Join(u.Path, transferFileMethod)
   334  	requestURL := u.String()
   335  	req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
   336  	if err != nil {
   337  		err = errors.Wrap(err, "rclone: error pulling transfer: error framing post request")
   338  		transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   339  		return &datatx.TxInfo{
   340  			Id:     &datatx.TxId{OpaqueId: txID},
   341  			Status: transfer.TransferStatus,
   342  			Ctime:  cTime,
   343  		}, driver.pDriver.model.saveTransfer(err)
   344  	}
   345  
   346  	req.Header.Set("Content-Type", "application/json")
   347  	req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass)
   348  	res, err := driver.client.Do(req)
   349  	if err != nil {
   350  		err = errors.Wrap(err, "rclone: error pulling transfer: error sending post request")
   351  		transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   352  		return &datatx.TxInfo{
   353  			Id:     &datatx.TxId{OpaqueId: txID},
   354  			Status: transfer.TransferStatus,
   355  			Ctime:  cTime,
   356  		}, driver.pDriver.model.saveTransfer(err)
   357  	}
   358  
   359  	defer res.Body.Close()
   360  
   361  	if res.StatusCode != http.StatusOK {
   362  		var errorResData rcloneHTTPErrorRes
   363  		if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil {
   364  			err = errors.Wrap(err, "rclone driver: error decoding response data")
   365  			transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   366  			return &datatx.TxInfo{
   367  				Id:     &datatx.TxId{OpaqueId: txID},
   368  				Status: transfer.TransferStatus,
   369  				Ctime:  cTime,
   370  			}, driver.pDriver.model.saveTransfer(err)
   371  		}
   372  		e := errors.New("rclone: rclone request responded with error, " + fmt.Sprintf(" status: %v, error: %v", errorResData.Status, errorResData.Error))
   373  		transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   374  		return &datatx.TxInfo{
   375  			Id:     &datatx.TxId{OpaqueId: txID},
   376  			Status: transfer.TransferStatus,
   377  			Ctime:  cTime,
   378  		}, driver.pDriver.model.saveTransfer(e)
   379  	}
   380  
   381  	type rcloneAsyncResJSON struct {
   382  		JobID int64 `json:"jobid"`
   383  	}
   384  	var resData rcloneAsyncResJSON
   385  	if err = json.NewDecoder(res.Body).Decode(&resData); err != nil {
   386  		err = errors.Wrap(err, "rclone: error decoding response data")
   387  		transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   388  		return &datatx.TxInfo{
   389  			Id:     &datatx.TxId{OpaqueId: txID},
   390  			Status: transfer.TransferStatus,
   391  			Ctime:  cTime,
   392  		}, driver.pDriver.model.saveTransfer(err)
   393  	}
   394  
   395  	transfer.JobID = resData.JobID
   396  
   397  	if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   398  		err = errors.Wrap(err, "rclone: error pulling transfer")
   399  		return &datatx.TxInfo{
   400  			Id:     &datatx.TxId{OpaqueId: txID},
   401  			Status: datatx.Status_STATUS_INVALID,
   402  			Ctime:  cTime,
   403  		}, err
   404  	}
   405  
   406  	// start separate dedicated process to periodically check the transfer progress
   407  	go func() {
   408  		// runs for as long as no end state or time out has been reached
   409  		startTimeMs := time.Now().Nanosecond() / 1000
   410  		timeout := driver.config.JobTimeout
   411  
   412  		driver.pDriver.Lock()
   413  		defer driver.pDriver.Unlock()
   414  
   415  		for {
   416  			transfer, err := driver.pDriver.model.getTransfer(txID)
   417  			if err != nil {
   418  				transfer.TransferStatus = datatx.Status_STATUS_INVALID
   419  				err = driver.pDriver.model.saveTransfer(err)
   420  				logger.Error().Err(err).Msgf("rclone driver: unable to retrieve transfer with id: %v", txID)
   421  				break
   422  			}
   423  
   424  			// check for end status first
   425  			_, endStatusFound := txEndStatuses[transfer.TransferStatus.String()]
   426  			if endStatusFound {
   427  				logger.Info().Msgf("rclone driver: transfer endstatus reached: %v", transfer.TransferStatus)
   428  				break
   429  			}
   430  
   431  			// check for possible timeout and if true were done
   432  			currentTimeMs := time.Now().Nanosecond() / 1000
   433  			timePastMs := currentTimeMs - startTimeMs
   434  
   435  			if timePastMs > timeout {
   436  				logger.Info().Msgf("rclone driver: transfer timed out: %vms (timeout = %v)", timePastMs, timeout)
   437  				// set status to EXPIRED and save
   438  				transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_EXPIRED
   439  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   440  					logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err)
   441  				}
   442  				break
   443  			}
   444  
   445  			jobID := transfer.JobID
   446  			type rcloneStatusReqJSON struct {
   447  				JobID int64 `json:"jobid"`
   448  			}
   449  			rcloneStatusReq := &rcloneStatusReqJSON{
   450  				JobID: jobID,
   451  			}
   452  
   453  			data, err := json.Marshal(rcloneStatusReq)
   454  			if err != nil {
   455  				logger.Error().Err(err).Msgf("rclone driver: marshalling request failed: %v", err)
   456  				transfer.TransferStatus = datatx.Status_STATUS_INVALID
   457  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   458  					logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err)
   459  				}
   460  				break
   461  			}
   462  
   463  			transferFileMethod := "/job/status"
   464  
   465  			u, err := url.Parse(driver.config.Endpoint)
   466  			if err != nil {
   467  				logger.Error().Err(err).Msgf("rclone driver: could not parse driver endpoint: %v", err)
   468  				transfer.TransferStatus = datatx.Status_STATUS_INVALID
   469  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   470  					logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err)
   471  				}
   472  				break
   473  			}
   474  			u.Path = path.Join(u.Path, transferFileMethod)
   475  			requestURL := u.String()
   476  
   477  			req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
   478  			if err != nil {
   479  				logger.Error().Err(err).Msgf("rclone driver: error framing post request: %v", err)
   480  				transfer.TransferStatus = datatx.Status_STATUS_INVALID
   481  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   482  					logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err)
   483  				}
   484  				break
   485  			}
   486  			req.Header.Set("Content-Type", "application/json")
   487  			req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass)
   488  			res, err := driver.client.Do(req)
   489  			if err != nil {
   490  				logger.Error().Err(err).Msgf("rclone driver: error sending post request: %v", err)
   491  				transfer.TransferStatus = datatx.Status_STATUS_INVALID
   492  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   493  					logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err)
   494  				}
   495  				break
   496  			}
   497  
   498  			defer res.Body.Close()
   499  
   500  			if res.StatusCode != http.StatusOK {
   501  				var errorResData rcloneHTTPErrorRes
   502  				if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil {
   503  					err = errors.Wrap(err, "rclone driver: error decoding response data")
   504  					logger.Error().Err(err).Msgf("rclone driver: error reading response body: %v", err)
   505  				}
   506  				logger.Error().Err(err).Msgf("rclone driver: rclone request responded with error, status: %v, error: %v", errorResData.Status, errorResData.Error)
   507  				transfer.TransferStatus = datatx.Status_STATUS_INVALID
   508  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   509  					logger.Error().Err(err).Msgf("rclone driver: save transfer failed: %v", err)
   510  				}
   511  				break
   512  			}
   513  
   514  			type rcloneStatusResJSON struct {
   515  				Finished  bool    `json:"finished"`
   516  				Success   bool    `json:"success"`
   517  				ID        int64   `json:"id"`
   518  				Error     string  `json:"error"`
   519  				Group     string  `json:"group"`
   520  				StartTime string  `json:"startTime"`
   521  				EndTime   string  `json:"endTime"`
   522  				Duration  float64 `json:"duration"`
   523  				// think we don't need this
   524  				// "output": {} // output of the job as would have been returned if called synchronously
   525  			}
   526  			var resData rcloneStatusResJSON
   527  			if err = json.NewDecoder(res.Body).Decode(&resData); err != nil {
   528  				logger.Error().Err(err).Msgf("rclone driver: error decoding response data: %v", err)
   529  				break
   530  			}
   531  
   532  			if resData.Error != "" {
   533  				logger.Error().Err(err).Msgf("rclone driver: rclone responded with error: %v", resData.Error)
   534  				transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   535  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   536  					logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err)
   537  					break
   538  				}
   539  				break
   540  			}
   541  
   542  			// transfer complete
   543  			if resData.Finished && resData.Success {
   544  				logger.Info().Msg("rclone driver: transfer job finished")
   545  				transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_COMPLETE
   546  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   547  					logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err)
   548  					break
   549  				}
   550  				break
   551  			}
   552  
   553  			// transfer completed unsuccessfully without error
   554  			if resData.Finished && !resData.Success {
   555  				logger.Info().Msgf("rclone driver: transfer job failed")
   556  				transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_FAILED
   557  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   558  					logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err)
   559  					break
   560  				}
   561  				break
   562  			}
   563  
   564  			// transfer not yet finished: continue
   565  			if !resData.Finished {
   566  				logger.Info().Msgf("rclone driver: transfer job in progress")
   567  				transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_IN_PROGRESS
   568  				if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   569  					logger.Error().Err(err).Msgf("rclone driver: error saving transfer: %v", err)
   570  					break
   571  				}
   572  			}
   573  
   574  			<-time.After(time.Millisecond * time.Duration(driver.config.JobStatusCheckInterval))
   575  		}
   576  	}()
   577  
   578  	return &datatx.TxInfo{
   579  		Id:     &datatx.TxId{OpaqueId: txID},
   580  		Status: transferStatus,
   581  		Ctime:  cTime,
   582  	}, nil
   583  }
   584  
   585  // GetTransferStatus returns the status of the transfer with the specified job id
   586  func (driver *rclone) GetTransferStatus(ctx context.Context, transferID string) (*datatx.TxInfo, error) {
   587  	transfer, err := driver.pDriver.model.getTransfer(transferID)
   588  	if err != nil {
   589  		return &datatx.TxInfo{
   590  			Id:     &datatx.TxId{OpaqueId: transferID},
   591  			Status: datatx.Status_STATUS_INVALID,
   592  			Ctime:  nil,
   593  		}, err
   594  	}
   595  	cTime, _ := strconv.ParseInt(transfer.Ctime, 10, 64)
   596  	return &datatx.TxInfo{
   597  		Id:     &datatx.TxId{OpaqueId: transferID},
   598  		Status: transfer.TransferStatus,
   599  		Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   600  	}, nil
   601  }
   602  
   603  // CancelTransfer cancels the transfer with the specified transfer id
   604  func (driver *rclone) CancelTransfer(ctx context.Context, transferID string) (*datatx.TxInfo, error) {
   605  	transfer, err := driver.pDriver.model.getTransfer(transferID)
   606  	if err != nil {
   607  		return &datatx.TxInfo{
   608  			Id:     &datatx.TxId{OpaqueId: transferID},
   609  			Status: datatx.Status_STATUS_INVALID,
   610  			Ctime:  nil,
   611  		}, err
   612  	}
   613  	cTime, _ := strconv.ParseInt(transfer.Ctime, 10, 64)
   614  	_, endStatusFound := txEndStatuses[transfer.TransferStatus.String()]
   615  	if endStatusFound {
   616  		err := errors.New("rclone driver: transfer already in end state")
   617  		return &datatx.TxInfo{
   618  			Id:     &datatx.TxId{OpaqueId: transferID},
   619  			Status: datatx.Status_STATUS_INVALID,
   620  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   621  		}, err
   622  	}
   623  
   624  	// rcloneStop the rclone job/stop method json request
   625  	type rcloneStopRequest struct {
   626  		JobID int64 `json:"jobid"`
   627  	}
   628  	rcloneCancelTransferReq := &rcloneStopRequest{
   629  		JobID: transfer.JobID,
   630  	}
   631  
   632  	data, err := json.Marshal(rcloneCancelTransferReq)
   633  	if err != nil {
   634  		err = errors.Wrap(err, "rclone driver: error marshalling rclone req data")
   635  		return &datatx.TxInfo{
   636  			Id:     &datatx.TxId{OpaqueId: transferID},
   637  			Status: datatx.Status_STATUS_INVALID,
   638  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   639  		}, err
   640  	}
   641  
   642  	transferFileMethod := "/job/stop"
   643  
   644  	u, err := url.Parse(driver.config.Endpoint)
   645  	if err != nil {
   646  		err = errors.Wrap(err, "rclone driver: error parsing driver endpoint")
   647  		return &datatx.TxInfo{
   648  			Id:     &datatx.TxId{OpaqueId: transferID},
   649  			Status: datatx.Status_STATUS_INVALID,
   650  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   651  		}, err
   652  	}
   653  	u.Path = path.Join(u.Path, transferFileMethod)
   654  	requestURL := u.String()
   655  
   656  	req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
   657  	if err != nil {
   658  		err = errors.Wrap(err, "rclone driver: error framing post request")
   659  		return &datatx.TxInfo{
   660  			Id:     &datatx.TxId{OpaqueId: transferID},
   661  			Status: datatx.Status_STATUS_INVALID,
   662  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   663  		}, err
   664  	}
   665  	req.Header.Set("Content-Type", "application/json")
   666  
   667  	req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass)
   668  
   669  	res, err := driver.client.Do(req)
   670  	if err != nil {
   671  		err = errors.Wrap(err, "rclone driver: error sending post request")
   672  		return &datatx.TxInfo{
   673  			Id:     &datatx.TxId{OpaqueId: transferID},
   674  			Status: datatx.Status_STATUS_INVALID,
   675  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   676  		}, err
   677  	}
   678  
   679  	defer res.Body.Close()
   680  
   681  	if res.StatusCode != http.StatusOK {
   682  		var errorResData rcloneHTTPErrorRes
   683  		if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil {
   684  			err = errors.Wrap(err, "rclone driver: error decoding response data")
   685  			return &datatx.TxInfo{
   686  				Id:     &datatx.TxId{OpaqueId: transferID},
   687  				Status: datatx.Status_STATUS_INVALID,
   688  				Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   689  			}, err
   690  		}
   691  		err = errors.Wrap(errors.Errorf("status: %v, error: %v", errorResData.Status, errorResData.Error), "rclone driver: rclone request responded with error")
   692  		return &datatx.TxInfo{
   693  			Id:     &datatx.TxId{OpaqueId: transferID},
   694  			Status: datatx.Status_STATUS_INVALID,
   695  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   696  		}, err
   697  	}
   698  
   699  	type rcloneCancelTransferResJSON struct {
   700  		Finished  bool    `json:"finished"`
   701  		Success   bool    `json:"success"`
   702  		ID        int64   `json:"id"`
   703  		Error     string  `json:"error"`
   704  		Group     string  `json:"group"`
   705  		StartTime string  `json:"startTime"`
   706  		EndTime   string  `json:"endTime"`
   707  		Duration  float64 `json:"duration"`
   708  		// think we don't need this
   709  		// "output": {} // output of the job as would have been returned if called synchronously
   710  	}
   711  	var resData rcloneCancelTransferResJSON
   712  	if err = json.NewDecoder(res.Body).Decode(&resData); err != nil {
   713  		err = errors.Wrap(err, "rclone driver: error decoding response data")
   714  		return &datatx.TxInfo{
   715  			Id:     &datatx.TxId{OpaqueId: transferID},
   716  			Status: datatx.Status_STATUS_INVALID,
   717  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   718  		}, err
   719  	}
   720  
   721  	if resData.Error != "" {
   722  		return &datatx.TxInfo{
   723  			Id:     &datatx.TxId{OpaqueId: transferID},
   724  			Status: datatx.Status_STATUS_TRANSFER_CANCEL_FAILED,
   725  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   726  		}, errors.New(resData.Error)
   727  	}
   728  
   729  	transfer.TransferStatus = datatx.Status_STATUS_TRANSFER_CANCELLED
   730  	if err := driver.pDriver.model.saveTransfer(nil); err != nil {
   731  		return &datatx.TxInfo{
   732  			Id:     &datatx.TxId{OpaqueId: transferID},
   733  			Status: datatx.Status_STATUS_INVALID,
   734  			Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   735  		}, err
   736  	}
   737  
   738  	return &datatx.TxInfo{
   739  		Id:     &datatx.TxId{OpaqueId: transferID},
   740  		Status: datatx.Status_STATUS_TRANSFER_CANCELLED,
   741  		Ctime:  &typespb.Timestamp{Seconds: uint64(cTime)},
   742  	}, nil
   743  }
   744  
   745  // RetryTransfer retries the transfer with the specified transfer ID.
   746  // Note that tokens must still be valid.
   747  func (driver *rclone) RetryTransfer(ctx context.Context, transferID string) (*datatx.TxInfo, error) {
   748  	return driver.startJob(ctx, transferID, "", "", "", "", "", "")
   749  }
   750  
   751  // getTransfer returns the transfer with the specified transfer ID
   752  func (m *transferModel) getTransfer(transferID string) (*transfer, error) {
   753  	transfer, ok := m.Transfers[transferID]
   754  	if !ok {
   755  		return nil, errors.New("rclone driver: invalid transfer ID")
   756  	}
   757  	return transfer, nil
   758  }
   759  
   760  func (driver *rclone) remotePathIsFolder(remote string, remotePath string, remoteToken string) (bool, error) {
   761  	type rcloneListReqJSON struct {
   762  		Fs     string `json:"fs"`
   763  		Remote string `json:"remote"`
   764  	}
   765  	fs := fmt.Sprintf(":webdav,headers=\"x-access-token,%v\",url=\"%v\":", remoteToken, remote)
   766  	rcloneReq := &rcloneListReqJSON{
   767  		Fs:     fs,
   768  		Remote: remotePath,
   769  	}
   770  	data, err := json.Marshal(rcloneReq)
   771  	if err != nil {
   772  		return false, errors.Wrap(err, "rclone: error marshalling rclone req data")
   773  	}
   774  
   775  	listMethod := "/operations/list"
   776  
   777  	u, err := url.Parse(driver.config.Endpoint)
   778  	if err != nil {
   779  		return false, errors.Wrap(err, "rclone driver: error parsing driver endpoint")
   780  	}
   781  	u.Path = path.Join(u.Path, listMethod)
   782  	requestURL := u.String()
   783  
   784  	req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
   785  	if err != nil {
   786  		return false, errors.Wrap(err, "rclone driver: error framing post request")
   787  	}
   788  	req.Header.Set("Content-Type", "application/json")
   789  
   790  	req.SetBasicAuth(driver.config.AuthUser, driver.config.AuthPass)
   791  
   792  	res, err := driver.client.Do(req)
   793  	if err != nil {
   794  		return false, errors.Wrap(err, "rclone driver: error sending post request")
   795  	}
   796  
   797  	defer res.Body.Close()
   798  
   799  	if res.StatusCode != http.StatusOK {
   800  		var errorResData rcloneHTTPErrorRes
   801  		if err = json.NewDecoder(res.Body).Decode(&errorResData); err != nil {
   802  			return false, errors.Wrap(err, "rclone driver: error decoding response data")
   803  		}
   804  		return false, errors.Wrap(errors.Errorf("status: %v, error: %v", errorResData.Status, errorResData.Error), "rclone driver: rclone request responded with error")
   805  	}
   806  
   807  	type item struct {
   808  		Path     string `json:"Path"`
   809  		Name     string `json:"Name"`
   810  		Size     int64  `json:"Size"`
   811  		MimeType string `json:"MimeType"`
   812  		ModTime  string `json:"ModTime"`
   813  		IsDir    bool   `json:"IsDir"`
   814  	}
   815  	type rcloneListResJSON struct {
   816  		List []*item `json:"list"`
   817  	}
   818  
   819  	var resData rcloneListResJSON
   820  	if err = json.NewDecoder(res.Body).Decode(&resData); err != nil {
   821  		return false, errors.Wrap(err, "rclone driver: error decoding response data")
   822  	}
   823  
   824  	// a file will return one single item, the file, with path being the remote path and IsDir will be false
   825  	if len(resData.List) == 1 && resData.List[0].Path == remotePath && !resData.List[0].IsDir {
   826  		return false, nil
   827  	}
   828  
   829  	// in all other cases the remote path is a directory
   830  	return true, nil
   831  }