github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/ftp/ftp.go (about) 1 // Package ftp interfaces with FTP servers 2 package ftp 3 4 import ( 5 "context" 6 "crypto/tls" 7 "io" 8 "net/textproto" 9 "os" 10 "path" 11 "sync" 12 "time" 13 14 "github.com/jlaffaye/ftp" 15 "github.com/ncw/rclone/fs" 16 "github.com/ncw/rclone/fs/config/configmap" 17 "github.com/ncw/rclone/fs/config/configstruct" 18 "github.com/ncw/rclone/fs/config/obscure" 19 "github.com/ncw/rclone/fs/hash" 20 "github.com/ncw/rclone/lib/pacer" 21 "github.com/ncw/rclone/lib/readers" 22 "github.com/pkg/errors" 23 ) 24 25 // Register with Fs 26 func init() { 27 fs.Register(&fs.RegInfo{ 28 Name: "ftp", 29 Description: "FTP Connection", 30 NewFs: NewFs, 31 Options: []fs.Option{ 32 { 33 Name: "host", 34 Help: "FTP host to connect to", 35 Required: true, 36 Examples: []fs.OptionExample{{ 37 Value: "ftp.example.com", 38 Help: "Connect to ftp.example.com", 39 }}, 40 }, { 41 Name: "user", 42 Help: "FTP username, leave blank for current username, " + os.Getenv("USER"), 43 }, { 44 Name: "port", 45 Help: "FTP port, leave blank to use default (21)", 46 }, { 47 Name: "pass", 48 Help: "FTP password", 49 IsPassword: true, 50 Required: true, 51 }, { 52 Name: "tls", 53 Help: "Use FTP over TLS (Implicit)", 54 Default: false, 55 }, { 56 Name: "concurrency", 57 Help: "Maximum number of FTP simultaneous connections, 0 for unlimited", 58 Default: 0, 59 Advanced: true, 60 }, { 61 Name: "no_check_certificate", 62 Help: "Do not verify the TLS certificate of the server", 63 Default: false, 64 Advanced: true, 65 }, 66 }, 67 }) 68 } 69 70 // Options defines the configuration for this backend 71 type Options struct { 72 Host string `config:"host"` 73 User string `config:"user"` 74 Pass string `config:"pass"` 75 Port string `config:"port"` 76 TLS bool `config:"tls"` 77 Concurrency int `config:"concurrency"` 78 SkipVerifyTLSCert bool `config:"no_check_certificate"` 79 } 80 81 // Fs represents a remote FTP server 82 type Fs struct { 83 name string // name of this remote 84 root string // the path we are working on if any 85 opt Options // parsed options 86 features *fs.Features // optional features 87 url string 88 user string 89 pass string 90 dialAddr string 91 poolMu sync.Mutex 92 pool []*ftp.ServerConn 93 tokens *pacer.TokenDispenser 94 } 95 96 // Object describes an FTP file 97 type Object struct { 98 fs *Fs 99 remote string 100 info *FileInfo 101 } 102 103 // FileInfo is the metadata known about an FTP file 104 type FileInfo struct { 105 Name string 106 Size uint64 107 ModTime time.Time 108 IsDir bool 109 } 110 111 // ------------------------------------------------------------ 112 113 // Name of this fs 114 func (f *Fs) Name() string { 115 return f.name 116 } 117 118 // Root of the remote (as passed into NewFs) 119 func (f *Fs) Root() string { 120 return f.root 121 } 122 123 // String returns a description of the FS 124 func (f *Fs) String() string { 125 return f.url 126 } 127 128 // Features returns the optional features of this Fs 129 func (f *Fs) Features() *fs.Features { 130 return f.features 131 } 132 133 // Open a new connection to the FTP server. 134 func (f *Fs) ftpConnection() (*ftp.ServerConn, error) { 135 fs.Debugf(f, "Connecting to FTP server") 136 ftpConfig := []ftp.DialOption{ftp.DialWithTimeout(fs.Config.ConnectTimeout)} 137 if f.opt.TLS { 138 tlsConfig := &tls.Config{ 139 ServerName: f.opt.Host, 140 InsecureSkipVerify: f.opt.SkipVerifyTLSCert, 141 } 142 ftpConfig = append(ftpConfig, ftp.DialWithTLS(tlsConfig)) 143 } 144 c, err := ftp.Dial(f.dialAddr, ftpConfig...) 145 if err != nil { 146 fs.Errorf(f, "Error while Dialing %s: %s", f.dialAddr, err) 147 return nil, errors.Wrap(err, "ftpConnection Dial") 148 } 149 err = c.Login(f.user, f.pass) 150 if err != nil { 151 _ = c.Quit() 152 fs.Errorf(f, "Error while Logging in into %s: %s", f.dialAddr, err) 153 return nil, errors.Wrap(err, "ftpConnection Login") 154 } 155 return c, nil 156 } 157 158 // Get an FTP connection from the pool, or open a new one 159 func (f *Fs) getFtpConnection() (c *ftp.ServerConn, err error) { 160 if f.opt.Concurrency > 0 { 161 f.tokens.Get() 162 } 163 f.poolMu.Lock() 164 if len(f.pool) > 0 { 165 c = f.pool[0] 166 f.pool = f.pool[1:] 167 } 168 f.poolMu.Unlock() 169 if c != nil { 170 return c, nil 171 } 172 return f.ftpConnection() 173 } 174 175 // Return an FTP connection to the pool 176 // 177 // It nils the pointed to connection out so it can't be reused 178 // 179 // if err is not nil then it checks the connection is alive using a 180 // NOOP request 181 func (f *Fs) putFtpConnection(pc **ftp.ServerConn, err error) { 182 if f.opt.Concurrency > 0 { 183 defer f.tokens.Put() 184 } 185 c := *pc 186 *pc = nil 187 if err != nil { 188 // If not a regular FTP error code then check the connection 189 _, isRegularError := errors.Cause(err).(*textproto.Error) 190 if !isRegularError { 191 nopErr := c.NoOp() 192 if nopErr != nil { 193 fs.Debugf(f, "Connection failed, closing: %v", nopErr) 194 _ = c.Quit() 195 return 196 } 197 } 198 } 199 f.poolMu.Lock() 200 f.pool = append(f.pool, c) 201 f.poolMu.Unlock() 202 } 203 204 // NewFs constructs an Fs from the path, container:path 205 func NewFs(name, root string, m configmap.Mapper) (ff fs.Fs, err error) { 206 ctx := context.Background() 207 // defer fs.Trace(nil, "name=%q, root=%q", name, root)("fs=%v, err=%v", &ff, &err) 208 // Parse config into Options struct 209 opt := new(Options) 210 err = configstruct.Set(m, opt) 211 if err != nil { 212 return nil, err 213 } 214 pass, err := obscure.Reveal(opt.Pass) 215 if err != nil { 216 return nil, errors.Wrap(err, "NewFS decrypt password") 217 } 218 user := opt.User 219 if user == "" { 220 user = os.Getenv("USER") 221 } 222 port := opt.Port 223 if port == "" { 224 port = "21" 225 } 226 227 dialAddr := opt.Host + ":" + port 228 protocol := "ftp://" 229 if opt.TLS { 230 protocol = "ftps://" 231 } 232 u := protocol + path.Join(dialAddr+"/", root) 233 f := &Fs{ 234 name: name, 235 root: root, 236 opt: *opt, 237 url: u, 238 user: user, 239 pass: pass, 240 dialAddr: dialAddr, 241 tokens: pacer.NewTokenDispenser(opt.Concurrency), 242 } 243 f.features = (&fs.Features{ 244 CanHaveEmptyDirectories: true, 245 }).Fill(f) 246 // Make a connection and pool it to return errors early 247 c, err := f.getFtpConnection() 248 if err != nil { 249 return nil, errors.Wrap(err, "NewFs") 250 } 251 f.putFtpConnection(&c, nil) 252 if root != "" { 253 // Check to see if the root actually an existing file 254 remote := path.Base(root) 255 f.root = path.Dir(root) 256 if f.root == "." { 257 f.root = "" 258 } 259 _, err := f.NewObject(ctx, remote) 260 if err != nil { 261 if err == fs.ErrorObjectNotFound || errors.Cause(err) == fs.ErrorNotAFile { 262 // File doesn't exist so return old f 263 f.root = root 264 return f, nil 265 } 266 return nil, err 267 } 268 // return an error with an fs which points to the parent 269 return f, fs.ErrorIsFile 270 } 271 return f, err 272 } 273 274 // translateErrorFile turns FTP errors into rclone errors if possible for a file 275 func translateErrorFile(err error) error { 276 switch errX := err.(type) { 277 case *textproto.Error: 278 switch errX.Code { 279 case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored: 280 err = fs.ErrorObjectNotFound 281 } 282 } 283 return err 284 } 285 286 // translateErrorDir turns FTP errors into rclone errors if possible for a directory 287 func translateErrorDir(err error) error { 288 switch errX := err.(type) { 289 case *textproto.Error: 290 switch errX.Code { 291 case ftp.StatusFileUnavailable, ftp.StatusFileActionIgnored: 292 err = fs.ErrorDirNotFound 293 } 294 } 295 return err 296 } 297 298 // findItem finds a directory entry for the name in its parent directory 299 func (f *Fs) findItem(remote string) (entry *ftp.Entry, err error) { 300 // defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err) 301 fullPath := path.Join(f.root, remote) 302 dir := path.Dir(fullPath) 303 base := path.Base(fullPath) 304 305 c, err := f.getFtpConnection() 306 if err != nil { 307 return nil, errors.Wrap(err, "findItem") 308 } 309 files, err := c.List(dir) 310 f.putFtpConnection(&c, err) 311 if err != nil { 312 return nil, translateErrorFile(err) 313 } 314 for _, file := range files { 315 if file.Name == base { 316 return file, nil 317 } 318 } 319 return nil, nil 320 } 321 322 // NewObject finds the Object at remote. If it can't be found 323 // it returns the error fs.ErrorObjectNotFound. 324 func (f *Fs) NewObject(ctx context.Context, remote string) (o fs.Object, err error) { 325 // defer fs.Trace(remote, "")("o=%v, err=%v", &o, &err) 326 entry, err := f.findItem(remote) 327 if err != nil { 328 return nil, err 329 } 330 if entry != nil && entry.Type != ftp.EntryTypeFolder { 331 o := &Object{ 332 fs: f, 333 remote: remote, 334 } 335 info := &FileInfo{ 336 Name: remote, 337 Size: entry.Size, 338 ModTime: entry.Time, 339 } 340 o.info = info 341 342 return o, nil 343 } 344 return nil, fs.ErrorObjectNotFound 345 } 346 347 // dirExists checks the directory pointed to by remote exists or not 348 func (f *Fs) dirExists(remote string) (exists bool, err error) { 349 entry, err := f.findItem(remote) 350 if err != nil { 351 return false, errors.Wrap(err, "dirExists") 352 } 353 if entry != nil && entry.Type == ftp.EntryTypeFolder { 354 return true, nil 355 } 356 return false, nil 357 } 358 359 // List the objects and directories in dir into entries. The 360 // entries can be returned in any order but should be for a 361 // complete directory. 362 // 363 // dir should be "" to list the root, and should not have 364 // trailing slashes. 365 // 366 // This should return ErrDirNotFound if the directory isn't 367 // found. 368 func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 369 // defer fs.Trace(dir, "curlevel=%d", curlevel)("") 370 c, err := f.getFtpConnection() 371 if err != nil { 372 return nil, errors.Wrap(err, "list") 373 } 374 375 var listErr error 376 var files []*ftp.Entry 377 378 resultchan := make(chan []*ftp.Entry, 1) 379 errchan := make(chan error, 1) 380 go func() { 381 result, err := c.List(path.Join(f.root, dir)) 382 f.putFtpConnection(&c, err) 383 if err != nil { 384 errchan <- err 385 return 386 } 387 resultchan <- result 388 }() 389 390 // Wait for List for up to Timeout seconds 391 timer := time.NewTimer(fs.Config.Timeout) 392 select { 393 case listErr = <-errchan: 394 timer.Stop() 395 return nil, translateErrorDir(listErr) 396 case files = <-resultchan: 397 timer.Stop() 398 case <-timer.C: 399 // if timer fired assume no error but connection dead 400 fs.Errorf(f, "Timeout when waiting for List") 401 return nil, errors.New("Timeout when waiting for List") 402 } 403 404 // Annoyingly FTP returns success for a directory which 405 // doesn't exist, so check it really doesn't exist if no 406 // entries found. 407 if len(files) == 0 { 408 exists, err := f.dirExists(dir) 409 if err != nil { 410 return nil, errors.Wrap(err, "list") 411 } 412 if !exists { 413 return nil, fs.ErrorDirNotFound 414 } 415 } 416 for i := range files { 417 object := files[i] 418 newremote := path.Join(dir, object.Name) 419 switch object.Type { 420 case ftp.EntryTypeFolder: 421 if object.Name == "." || object.Name == ".." { 422 continue 423 } 424 d := fs.NewDir(newremote, object.Time) 425 entries = append(entries, d) 426 default: 427 o := &Object{ 428 fs: f, 429 remote: newremote, 430 } 431 info := &FileInfo{ 432 Name: newremote, 433 Size: object.Size, 434 ModTime: object.Time, 435 } 436 o.info = info 437 entries = append(entries, o) 438 } 439 } 440 return entries, nil 441 } 442 443 // Hashes are not supported 444 func (f *Fs) Hashes() hash.Set { 445 return 0 446 } 447 448 // Precision shows Modified Time not supported 449 func (f *Fs) Precision() time.Duration { 450 return fs.ModTimeNotSupported 451 } 452 453 // Put in to the remote path with the modTime given of the given size 454 // 455 // May create the object even if it returns an error - if so 456 // will return the object and the error, otherwise will return 457 // nil and the error 458 func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 459 // fs.Debugf(f, "Trying to put file %s", src.Remote()) 460 err := f.mkParentDir(src.Remote()) 461 if err != nil { 462 return nil, errors.Wrap(err, "Put mkParentDir failed") 463 } 464 o := &Object{ 465 fs: f, 466 remote: src.Remote(), 467 } 468 err = o.Update(ctx, in, src, options...) 469 return o, err 470 } 471 472 // PutStream uploads to the remote path with the modTime given of indeterminate size 473 func (f *Fs) PutStream(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { 474 return f.Put(ctx, in, src, options...) 475 } 476 477 // getInfo reads the FileInfo for a path 478 func (f *Fs) getInfo(remote string) (fi *FileInfo, err error) { 479 // defer fs.Trace(remote, "")("fi=%v, err=%v", &fi, &err) 480 dir := path.Dir(remote) 481 base := path.Base(remote) 482 483 c, err := f.getFtpConnection() 484 if err != nil { 485 return nil, errors.Wrap(err, "getInfo") 486 } 487 files, err := c.List(dir) 488 f.putFtpConnection(&c, err) 489 if err != nil { 490 return nil, translateErrorFile(err) 491 } 492 493 for i := range files { 494 if files[i].Name == base { 495 info := &FileInfo{ 496 Name: remote, 497 Size: files[i].Size, 498 ModTime: files[i].Time, 499 IsDir: files[i].Type == ftp.EntryTypeFolder, 500 } 501 return info, nil 502 } 503 } 504 return nil, fs.ErrorObjectNotFound 505 } 506 507 // mkdir makes the directory and parents using unrooted paths 508 func (f *Fs) mkdir(abspath string) error { 509 if abspath == "." || abspath == "/" { 510 return nil 511 } 512 fi, err := f.getInfo(abspath) 513 if err == nil { 514 if fi.IsDir { 515 return nil 516 } 517 return fs.ErrorIsFile 518 } else if err != fs.ErrorObjectNotFound { 519 return errors.Wrapf(err, "mkdir %q failed", abspath) 520 } 521 parent := path.Dir(abspath) 522 err = f.mkdir(parent) 523 if err != nil { 524 return err 525 } 526 c, connErr := f.getFtpConnection() 527 if connErr != nil { 528 return errors.Wrap(connErr, "mkdir") 529 } 530 err = c.MakeDir(abspath) 531 f.putFtpConnection(&c, err) 532 switch errX := err.(type) { 533 case *textproto.Error: 534 switch errX.Code { 535 case ftp.StatusFileUnavailable: // dir already exists: see issue #2181 536 err = nil 537 case 521: // dir already exists: error number according to RFC 959: issue #2363 538 err = nil 539 } 540 } 541 return err 542 } 543 544 // mkParentDir makes the parent of remote if necessary and any 545 // directories above that 546 func (f *Fs) mkParentDir(remote string) error { 547 parent := path.Dir(remote) 548 return f.mkdir(path.Join(f.root, parent)) 549 } 550 551 // Mkdir creates the directory if it doesn't exist 552 func (f *Fs) Mkdir(ctx context.Context, dir string) (err error) { 553 // defer fs.Trace(dir, "")("err=%v", &err) 554 root := path.Join(f.root, dir) 555 return f.mkdir(root) 556 } 557 558 // Rmdir removes the directory (container, bucket) if empty 559 // 560 // Return an error if it doesn't exist or isn't empty 561 func (f *Fs) Rmdir(ctx context.Context, dir string) error { 562 c, err := f.getFtpConnection() 563 if err != nil { 564 return errors.Wrap(translateErrorFile(err), "Rmdir") 565 } 566 err = c.RemoveDir(path.Join(f.root, dir)) 567 f.putFtpConnection(&c, err) 568 return translateErrorDir(err) 569 } 570 571 // Move renames a remote file object 572 func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 573 srcObj, ok := src.(*Object) 574 if !ok { 575 fs.Debugf(src, "Can't move - not same remote type") 576 return nil, fs.ErrorCantMove 577 } 578 err := f.mkParentDir(remote) 579 if err != nil { 580 return nil, errors.Wrap(err, "Move mkParentDir failed") 581 } 582 c, err := f.getFtpConnection() 583 if err != nil { 584 return nil, errors.Wrap(err, "Move") 585 } 586 err = c.Rename( 587 path.Join(srcObj.fs.root, srcObj.remote), 588 path.Join(f.root, remote), 589 ) 590 f.putFtpConnection(&c, err) 591 if err != nil { 592 return nil, errors.Wrap(err, "Move Rename failed") 593 } 594 dstObj, err := f.NewObject(ctx, remote) 595 if err != nil { 596 return nil, errors.Wrap(err, "Move NewObject failed") 597 } 598 return dstObj, nil 599 } 600 601 // DirMove moves src, srcRemote to this remote at dstRemote 602 // using server side move operations. 603 // 604 // Will only be called if src.Fs().Name() == f.Name() 605 // 606 // If it isn't possible then return fs.ErrorCantDirMove 607 // 608 // If destination exists then return fs.ErrorDirExists 609 func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string) error { 610 srcFs, ok := src.(*Fs) 611 if !ok { 612 fs.Debugf(srcFs, "Can't move directory - not same remote type") 613 return fs.ErrorCantDirMove 614 } 615 srcPath := path.Join(srcFs.root, srcRemote) 616 dstPath := path.Join(f.root, dstRemote) 617 618 // Check if destination exists 619 fi, err := f.getInfo(dstPath) 620 if err == nil { 621 if fi.IsDir { 622 return fs.ErrorDirExists 623 } 624 return fs.ErrorIsFile 625 } else if err != fs.ErrorObjectNotFound { 626 return errors.Wrapf(err, "DirMove getInfo failed") 627 } 628 629 // Make sure the parent directory exists 630 err = f.mkdir(path.Dir(dstPath)) 631 if err != nil { 632 return errors.Wrap(err, "DirMove mkParentDir dst failed") 633 } 634 635 // Do the move 636 c, err := f.getFtpConnection() 637 if err != nil { 638 return errors.Wrap(err, "DirMove") 639 } 640 err = c.Rename( 641 srcPath, 642 dstPath, 643 ) 644 f.putFtpConnection(&c, err) 645 if err != nil { 646 return errors.Wrapf(err, "DirMove Rename(%q,%q) failed", srcPath, dstPath) 647 } 648 return nil 649 } 650 651 // ------------------------------------------------------------ 652 653 // Fs returns the parent Fs 654 func (o *Object) Fs() fs.Info { 655 return o.fs 656 } 657 658 // String version of o 659 func (o *Object) String() string { 660 if o == nil { 661 return "<nil>" 662 } 663 return o.remote 664 } 665 666 // Remote returns the remote path 667 func (o *Object) Remote() string { 668 return o.remote 669 } 670 671 // Hash returns the hash of an object returning a lowercase hex string 672 func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) { 673 return "", hash.ErrUnsupported 674 } 675 676 // Size returns the size of an object in bytes 677 func (o *Object) Size() int64 { 678 return int64(o.info.Size) 679 } 680 681 // ModTime returns the modification time of the object 682 func (o *Object) ModTime(ctx context.Context) time.Time { 683 return o.info.ModTime 684 } 685 686 // SetModTime sets the modification time of the object 687 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { 688 return nil 689 } 690 691 // Storable returns a boolean as to whether this object is storable 692 func (o *Object) Storable() bool { 693 return true 694 } 695 696 // ftpReadCloser implements io.ReadCloser for FTP objects. 697 type ftpReadCloser struct { 698 rc io.ReadCloser 699 c *ftp.ServerConn 700 f *Fs 701 err error // errors found during read 702 } 703 704 // Read bytes into p 705 func (f *ftpReadCloser) Read(p []byte) (n int, err error) { 706 n, err = f.rc.Read(p) 707 if err != nil && err != io.EOF { 708 f.err = err // store any errors for Close to examine 709 } 710 return 711 } 712 713 // Close the FTP reader and return the connection to the pool 714 func (f *ftpReadCloser) Close() error { 715 var err error 716 errchan := make(chan error, 1) 717 go func() { 718 errchan <- f.rc.Close() 719 }() 720 // Wait for Close for up to 60 seconds 721 timer := time.NewTimer(60 * time.Second) 722 select { 723 case err = <-errchan: 724 timer.Stop() 725 case <-timer.C: 726 // if timer fired assume no error but connection dead 727 fs.Errorf(f.f, "Timeout when waiting for connection Close") 728 return nil 729 } 730 // if errors while reading or closing, dump the connection 731 if err != nil || f.err != nil { 732 _ = f.c.Quit() 733 } else { 734 f.f.putFtpConnection(&f.c, nil) 735 } 736 // mask the error if it was caused by a premature close 737 switch errX := err.(type) { 738 case *textproto.Error: 739 switch errX.Code { 740 case ftp.StatusTransfertAborted, ftp.StatusFileUnavailable: 741 err = nil 742 } 743 } 744 return err 745 } 746 747 // Open an object for read 748 func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.ReadCloser, err error) { 749 // defer fs.Trace(o, "")("rc=%v, err=%v", &rc, &err) 750 path := path.Join(o.fs.root, o.remote) 751 var offset, limit int64 = 0, -1 752 for _, option := range options { 753 switch x := option.(type) { 754 case *fs.SeekOption: 755 offset = x.Offset 756 case *fs.RangeOption: 757 offset, limit = x.Decode(o.Size()) 758 default: 759 if option.Mandatory() { 760 fs.Logf(o, "Unsupported mandatory option: %v", option) 761 } 762 } 763 } 764 c, err := o.fs.getFtpConnection() 765 if err != nil { 766 return nil, errors.Wrap(err, "open") 767 } 768 fd, err := c.RetrFrom(path, uint64(offset)) 769 if err != nil { 770 o.fs.putFtpConnection(&c, err) 771 return nil, errors.Wrap(err, "open") 772 } 773 rc = &ftpReadCloser{rc: readers.NewLimitedReadCloser(fd, limit), c: c, f: o.fs} 774 return rc, nil 775 } 776 777 // Update the already existing object 778 // 779 // Copy the reader into the object updating modTime and size 780 // 781 // The new object may have been created if an error is returned 782 func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (err error) { 783 // defer fs.Trace(o, "src=%v", src)("err=%v", &err) 784 path := path.Join(o.fs.root, o.remote) 785 // remove the file if upload failed 786 remove := func() { 787 // Give the FTP server a chance to get its internal state in order after the error. 788 // The error may have been local in which case we closed the connection. The server 789 // may still be dealing with it for a moment. A sleep isn't ideal but I haven't been 790 // able to think of a better method to find out if the server has finished - ncw 791 time.Sleep(1 * time.Second) 792 removeErr := o.Remove(ctx) 793 if removeErr != nil { 794 fs.Debugf(o, "Failed to remove: %v", removeErr) 795 } else { 796 fs.Debugf(o, "Removed after failed upload: %v", err) 797 } 798 } 799 c, err := o.fs.getFtpConnection() 800 if err != nil { 801 return errors.Wrap(err, "Update") 802 } 803 err = c.Stor(path, in) 804 if err != nil { 805 _ = c.Quit() // toss this connection to avoid sync errors 806 remove() 807 return errors.Wrap(err, "update stor") 808 } 809 o.fs.putFtpConnection(&c, nil) 810 o.info, err = o.fs.getInfo(path) 811 if err != nil { 812 return errors.Wrap(err, "update getinfo") 813 } 814 return nil 815 } 816 817 // Remove an object 818 func (o *Object) Remove(ctx context.Context) (err error) { 819 // defer fs.Trace(o, "")("err=%v", &err) 820 path := path.Join(o.fs.root, o.remote) 821 // Check if it's a directory or a file 822 info, err := o.fs.getInfo(path) 823 if err != nil { 824 return err 825 } 826 if info.IsDir { 827 err = o.fs.Rmdir(ctx, o.remote) 828 } else { 829 c, err := o.fs.getFtpConnection() 830 if err != nil { 831 return errors.Wrap(err, "Remove") 832 } 833 err = c.Delete(path) 834 o.fs.putFtpConnection(&c, err) 835 } 836 return err 837 } 838 839 // Check the interfaces are satisfied 840 var ( 841 _ fs.Fs = &Fs{} 842 _ fs.Mover = &Fs{} 843 _ fs.DirMover = &Fs{} 844 _ fs.PutStreamer = &Fs{} 845 _ fs.Object = &Object{} 846 )