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 }