1 /******************************************************************************* 2 copyright: Copyright (c) 2007-2008 Tango. All rights reserved 3 4 license: BSD style: $(LICENSE) 5 6 version: August 2008: Initial version 7 8 author: Lester L. Martin II 9 *******************************************************************************/ 10 11 module tango.io.vfs.FtpFolder; 12 13 14 private { 15 import tango.net.ftp.FtpClient; 16 import tango.io.vfs.model.Vfs; 17 import tango.io.vfs.FileFolder; 18 import tango.io.device.Conduit; 19 import tango.text.Util; 20 import Time = tango.time.Time; 21 } 22 23 private const(char)[] fixName(const(char)[] toFix) { 24 if (containsPattern(toFix, "/")) 25 toFix = toFix[(locatePrior(toFix, '/') + 1) .. $]; 26 return toFix; 27 } 28 29 private const(char)[] checkFirst(const(char)[] toFix) { 30 for(; toFix.length>0 && toFix[$-1] == '/';) 31 toFix = toFix[0 .. ($-1)]; 32 return toFix; 33 } 34 35 private const(char)[] checkLast(const(char)[] toFix) { 36 for(;toFix.length>1 && toFix[0] == '/' && toFix[1] == '/' ;) 37 toFix = toFix[1 .. $]; 38 if(toFix.length && toFix[0] != '/') 39 toFix = '/' ~ toFix; 40 return toFix; 41 } 42 43 private const(char)[] checkCat(const(char)[] first, const(char)[] last) { 44 return checkFirst(first) ~ checkLast(last); 45 } 46 47 private FtpFileInfo[] getEntries(FTPConnection ftp, const(char)[] path = "") { 48 FtpFileInfo[] orig = ftp.ls(path); 49 FtpFileInfo[] temp2; 50 FtpFileInfo[] use; 51 FtpFileInfo[] temp; 52 foreach(FtpFileInfo inf; orig) { 53 if(inf.type == FtpFileType.dir) { 54 temp ~= inf; 55 } 56 } 57 foreach(FtpFileInfo inf; temp) { 58 temp2 ~= getEntries((ftp.cd(inf.name) , ftp)); 59 //wasn't here at the beginning 60 foreach(inf2; temp2) { 61 inf2.name = checkCat(inf.name, inf2.name); 62 use ~= inf2; 63 } 64 orig ~= use; 65 //end wasn't here at the beginning 66 ftp.cdup(); 67 } 68 return orig; 69 } 70 71 private FtpFileInfo[] getFiles(FTPConnection ftp, const(char)[] path = "") { 72 FtpFileInfo[] infos = getEntries(ftp, path); 73 FtpFileInfo[] return_; 74 foreach(FtpFileInfo info; infos) { 75 if(info.type == FtpFileType.file || info.type == FtpFileType.other || info.type == FtpFileType.unknown) 76 return_ ~= info; 77 } 78 return return_; 79 } 80 81 private FtpFileInfo[] getFolders(FTPConnection ftp, const(char)[] path = "") { 82 FtpFileInfo[] infos = getEntries(ftp, path); 83 FtpFileInfo[] return_; 84 foreach(FtpFileInfo info; infos) { 85 if(info.type == FtpFileType.dir || info.type == FtpFileType.cdir || info.type == FtpFileType.pdir) 86 return_ ~= info; 87 } 88 return return_; 89 } 90 91 /****************************************************************************** 92 Defines a folder over FTP that has yet to be opened, may not exist, and 93 may be created. 94 ******************************************************************************/ 95 96 class FtpFolderEntry: VfsFolderEntry { 97 98 const(char)[] toString_, name_, username_, password_; 99 uint port_; 100 101 public this(const(char)[] server, const(char)[] path, const(char)[] username = "", 102 const(char)[] password = "", uint port = 21) 103 in { 104 assert(server.length > 0); 105 } 106 body { 107 toString_ = checkFirst(server); 108 name_ = checkLast(path); 109 username_ = username; 110 password_ = password; 111 port_ = port; 112 } 113 114 /*********************************************************************** 115 Open a folder 116 ***********************************************************************/ 117 118 final VfsFolder open() { 119 return new FtpFolder(toString_, name_, username_, password_, port_); 120 } 121 122 /*********************************************************************** 123 Create a new folder 124 ***********************************************************************/ 125 126 final VfsFolder create() { 127 FTPConnection conn; 128 129 scope(failure) { 130 if(conn !is null) 131 conn.close(); 132 } 133 134 scope(exit) { 135 if(conn !is null) 136 conn.close(); 137 } 138 139 conn = new FTPConnection(toString_, username_, password_, port_); 140 conn.mkdir(name_); 141 142 return new FtpFolder(toString_, name_, username_, password_, port_); 143 } 144 145 /*********************************************************************** 146 Test to see if a folder exists 147 ***********************************************************************/ 148 149 @property final bool exists() { 150 FTPConnection conn; 151 152 153 154 scope(exit) { 155 if(conn !is null) 156 conn.close(); 157 } 158 159 bool return_; 160 if(name_ == "") { 161 try { 162 conn = new FTPConnection(toString_, username_, password_, port_); 163 return_ = true; 164 } catch(Exception e) { 165 return false; 166 } 167 } else { 168 try { 169 conn = new FTPConnection(toString_, username_, password_, port_); 170 try { 171 conn.cd(name_); 172 return_ = true; 173 } catch(Exception e) { 174 if(conn.exist(name_) == 2) 175 return_ = true; 176 else 177 return_ = false; 178 } 179 } catch(Exception e) { 180 return_ = false; 181 } 182 } 183 184 return return_; 185 } 186 } 187 188 /****************************************************************************** 189 Represents a FTP Folder in full, allowing one to address 190 specific folders of an FTP File system. 191 ******************************************************************************/ 192 193 class FtpFolder: VfsFolder { 194 195 const(char)[] toString_, name_, username_, password_; 196 uint port_; 197 198 public this(const(char)[] server, const(char)[] path, const(char)[] username = "", 199 const(char)[] password = "", uint port = 21) 200 in { 201 assert(server.length > 0); 202 } 203 body { 204 toString_ = checkFirst(server); 205 name_ = checkLast(path); 206 username_ = username; 207 password_ = password; 208 port_ = port; 209 } 210 211 /*********************************************************************** 212 Return a short name 213 ***********************************************************************/ 214 215 @property final const(char)[] name() { 216 return fixName(name_); 217 } 218 219 /*********************************************************************** 220 Return a long name 221 ***********************************************************************/ 222 223 override final string toString() { 224 return checkCat(toString_, name_).idup; 225 } 226 227 /*********************************************************************** 228 Return a contained file representation 229 ***********************************************************************/ 230 231 @property final VfsFile file(const(char)[] path) { 232 return new FtpFile(toString_, checkLast(checkCat(name_, path)), username_, password_, 233 port_); 234 } 235 236 /*********************************************************************** 237 Return a contained folder representation 238 ***********************************************************************/ 239 240 @property final VfsFolderEntry folder(const(char)[] path) { 241 return new FtpFolderEntry(toString_, checkLast(checkCat(name_, path)), username_, 242 password_, port_); 243 } 244 245 /*********************************************************************** 246 Returns a folder set containing only this one. Statistics 247 are inclusive of entries within this folder only 248 ***********************************************************************/ 249 250 @property final VfsFolders self() { 251 FTPConnection conn; 252 253 scope(exit) { 254 if(conn !is null) 255 conn.close(); 256 } 257 258 const(char)[] connect = toString_; 259 260 if(connect[$ - 1] == '/') { 261 connect = connect[0 .. ($ - 1)]; 262 } 263 264 conn = new FTPConnection(connect, username_, password_, port_); 265 266 if(name_ != "") 267 conn.cd(name_); 268 269 return new FtpFolders(toString_, name_, username_, password_, port_, 270 getFiles(conn), true); 271 } 272 273 /*********************************************************************** 274 Returns a subtree of folders. Statistics are inclusive of 275 files within this folder and all others within the tree 276 ***********************************************************************/ 277 278 @property final VfsFolders tree() { 279 FTPConnection conn; 280 281 282 283 scope(exit) { 284 if(conn !is null) 285 conn.close(); 286 } 287 288 const(char)[] connect = toString_; 289 290 if(connect[$ - 1] == '/') { 291 connect = connect[0 .. ($ - 1)]; 292 } 293 294 conn = new FTPConnection(connect, username_, password_, port_); 295 296 if(name_ != "") 297 conn.cd(name_); 298 299 return new FtpFolders(toString_, name_, username_, password_, port_, 300 getEntries(conn), false); 301 } 302 303 /*********************************************************************** 304 Iterate over the set of immediate child folders. This is 305 useful for reflecting the hierarchy 306 ***********************************************************************/ 307 308 final int opApply(scope int delegate(ref VfsFolder) dg) { 309 FTPConnection conn; 310 311 312 313 scope(exit) { 314 if(conn !is null) 315 conn.close(); 316 } 317 318 const(char)[] connect = toString_; 319 320 if(connect[$ - 1] == '/') { 321 connect = connect[0 .. ($ - 1)]; 322 } 323 324 conn = new FTPConnection(connect, username_, password_, port_); 325 326 if(name_ != "") 327 conn.cd(name_); 328 329 FtpFileInfo[] info = getFolders(conn); 330 331 int result; 332 333 foreach(FtpFileInfo fi; info) { 334 VfsFolder x = new FtpFolder(toString_, checkLast(checkCat(name_, fi.name)), username_, 335 password_, port_); 336 if((result = dg(x)) != 0) 337 break; 338 } 339 340 return result; 341 } 342 343 /*********************************************************************** 344 Clear all content from this folder and subordinates 345 ***********************************************************************/ 346 347 final VfsFolder clear() { 348 FTPConnection conn; 349 350 scope(exit) { 351 if(conn !is null) 352 conn.close(); 353 } 354 355 const(char)[] connect = toString_; 356 357 if(connect[$ - 1] == '/') { 358 connect = connect[0 .. ($ - 1)]; 359 } 360 361 conn = new FTPConnection(connect, username_, password_, port_); 362 363 if(name_ != "") 364 conn.cd(name_); 365 366 conn = new FTPConnection(connect, username_, password_, port_); 367 368 conn.cd(name_); 369 370 FtpFileInfo[] reverse(FtpFileInfo[] infos) { 371 FtpFileInfo[] reversed; 372 for(sizediff_t i = infos.length - 1; i >= 0; i--) { 373 reversed ~= infos[i]; 374 } 375 return reversed; 376 } 377 378 foreach(VfsFolder f; tree.subset(null)) 379 conn.rm(f.name); 380 381 foreach(FtpFileInfo entries; getEntries(conn)) 382 conn.del(entries.name); 383 384 //foreach(VfsFolder f; tree.subset(null)) 385 // conn.rm(f.name); 386 387 return this; 388 } 389 390 /*********************************************************************** 391 Is folder writable? 392 ***********************************************************************/ 393 394 @property final bool writable() { 395 try { 396 FTPConnection conn; 397 398 scope(failure) { 399 if(conn !is null) 400 conn.close(); 401 } 402 403 scope(exit) { 404 if(conn !is null) 405 conn.close(); 406 } 407 408 const(char)[] connect = toString_; 409 410 if(connect[$ - 1] == '/') { 411 connect = connect[0 .. ($ - 1)]; 412 } 413 414 conn = new FTPConnection(connect, username_, password_, port_); 415 416 if(name_ != "") 417 conn.cd(name_); 418 419 conn.mkdir("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); 420 conn.rm("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); 421 return true; 422 423 } catch(Exception e) { 424 return false; 425 } 426 } 427 428 /*********************************************************************** 429 Close and/or synchronize changes made to this folder. Each 430 driver should take advantage of this as appropriate, perhaps 431 combining multiple files together, or possibly copying to a 432 remote location 433 ***********************************************************************/ 434 435 VfsFolder close(bool commit = true) { 436 return this; 437 } 438 439 /*********************************************************************** 440 A folder is being added or removed from the hierarchy. Use 441 this to test for validity (or whatever) and throw exceptions 442 as necessary 443 ***********************************************************************/ 444 445 void verify(VfsFolder folder, bool mounting) { 446 return; 447 } 448 } 449 450 /****************************************************************************** 451 A set of folders within an FTP file system as was selected by the 452 Adapter or as was selected at initialization. 453 ******************************************************************************/ 454 455 class FtpFolders: VfsFolders { 456 457 const(char)[] toString_, name_, username_, password_; 458 uint port_; 459 bool flat_; 460 FtpFileInfo[] infos_; 461 462 package this(const(char)[] server, const(char)[] path, const(char)[] username = "", 463 const(char)[] password = "", uint port = 21, FtpFileInfo[] infos = null, 464 bool flat = false) 465 in { 466 assert(server.length > 0); 467 } 468 body { 469 toString_ = checkFirst(server); 470 name_ = checkLast(path); 471 username_ = username; 472 password_ = password; 473 port_ = port; 474 infos_ = infos; 475 flat_ = flat; 476 } 477 478 public this(const(char)[] server, const(char)[] path, const(char)[] username = "", 479 const(char)[] password = "", uint port = 21, bool flat = false) 480 in { 481 assert(server.length > 0); 482 } 483 body { 484 toString_ = checkFirst(server); 485 name_ = checkLast(path); 486 username_ = username; 487 password_ = password; 488 port_ = port; 489 flat_ = flat; 490 491 FTPConnection conn; 492 493 scope(exit) { 494 if(conn !is null) 495 conn.close(); 496 } 497 498 const(char)[] connect = toString_; 499 500 if(connect[$ - 1] == '/') { 501 connect = connect[0 .. ($ - 1)]; 502 } 503 504 conn = new FTPConnection(connect, username_, password_, port_); 505 506 if(name_ != "") 507 conn.cd(name_); 508 509 if(!flat_) 510 infos_ = getEntries(conn); 511 else 512 infos_ = getFiles(conn); 513 } 514 515 /*********************************************************************** 516 Iterate over the set of contained VfsFolder instances 517 ***********************************************************************/ 518 519 final int opApply(scope int delegate(ref VfsFolder) dg) { 520 FTPConnection conn; 521 522 scope(exit) { 523 if(conn !is null) 524 conn.close(); 525 } 526 527 const(char)[] connect = toString_; 528 529 if(connect[$ - 1] == '/') { 530 connect = connect[0 .. ($ - 1)]; 531 } 532 533 conn = new FTPConnection(connect, username_, password_, port_); 534 535 if(name_ != "") 536 conn.cd(name_); 537 538 FtpFileInfo[] info = getFolders(conn); 539 540 int result; 541 542 foreach(FtpFileInfo fi; info) { 543 VfsFolder x = new FtpFolder(toString_, checkLast(checkCat(name_, fi.name)), 544 username_, password_, port_); 545 546 // was 547 // VfsFolder x = new FtpFolder(toString_ ~ "/" ~ name_, fi.name, 548 // username_, password_, port_); 549 if((result = dg(x)) != 0) 550 break; 551 } 552 553 return result; 554 } 555 556 /*********************************************************************** 557 Return the number of files 558 ***********************************************************************/ 559 560 @property final size_t files() { 561 FTPConnection conn; 562 563 scope(exit) { 564 if(conn !is null) 565 conn.close(); 566 } 567 568 const(char)[] connect = toString_; 569 570 if(connect[$ - 1] == '/') { 571 connect = connect[0 .. ($ - 1)]; 572 } 573 574 conn = new FTPConnection(connect, username_, password_, port_); 575 576 if(name_ != "") 577 conn.cd(name_); 578 579 return getFiles(conn).length; 580 } 581 582 /*********************************************************************** 583 Return the number of folders 584 ***********************************************************************/ 585 586 @property final size_t folders() { 587 FTPConnection conn; 588 589 scope(exit) { 590 if(conn !is null) 591 conn.close(); 592 } 593 594 const(char)[] connect = toString_; 595 596 if(connect[$ - 1] == '/') { 597 connect = connect[0 .. ($ - 1)]; 598 } 599 600 conn = new FTPConnection(connect, username_, password_, port_); 601 602 if(name_ != "") 603 conn.cd(name_); 604 605 return getFolders(conn).length; 606 } 607 608 /*********************************************************************** 609 Return the total number of entries (files + folders) 610 ***********************************************************************/ 611 612 @property final size_t entries() { 613 FTPConnection conn; 614 615 scope(exit) { 616 if(conn !is null) 617 conn.close(); 618 } 619 620 const(char)[] connect = toString_; 621 622 if(connect[$ - 1] == '/') { 623 connect = connect[0 .. ($ - 1)]; 624 } 625 626 conn = new FTPConnection(connect, username_, password_, port_); 627 628 if(name_ != "") 629 conn.cd(name_); 630 631 return getEntries(conn).length; 632 } 633 634 /*********************************************************************** 635 Return the total size of contained files 636 ***********************************************************************/ 637 638 @property final ulong bytes() { 639 FTPConnection conn; 640 641 scope(exit) { 642 if(conn !is null) 643 conn.close(); 644 } 645 646 const(char)[] connect = toString_; 647 648 if(connect[$ - 1] == '/') { 649 connect = connect[0 .. ($ - 1)]; 650 } 651 652 conn = new FTPConnection(connect, username_, password_, port_); 653 654 if(name_ != "") 655 conn.cd(name_); 656 657 ulong return_; 658 659 foreach(FtpFileInfo inf; getEntries(conn)) { 660 return_ += inf.size; 661 } 662 663 return return_; 664 } 665 666 /*********************************************************************** 667 Return a subset of folders matching the given pattern 668 ***********************************************************************/ 669 670 final VfsFolders subset(const(char)[] pattern) { 671 FTPConnection conn; 672 673 scope(exit) { 674 if(conn !is null) 675 conn.close(); 676 } 677 678 const(char)[] connect = toString_; 679 680 if(connect[$ - 1] == '/') { 681 connect = connect[0 .. ($ - 1)]; 682 } 683 684 conn = new FTPConnection(connect, username_, password_, port_); 685 686 if(name_ != "") 687 conn.cd(name_); 688 689 FtpFileInfo[] return__; 690 691 if(pattern !is null) 692 foreach(FtpFileInfo inf; getFolders(conn)) { 693 if(containsPattern(inf.name, pattern)) 694 return__ ~= inf; 695 } 696 else 697 return__ = getFolders(conn); 698 699 return new FtpFolders(toString_, name_, username_, password_, port_, 700 return__); 701 } 702 703 /*********************************************************************** 704 Return a set of files matching the given pattern 705 ***********************************************************************/ 706 707 @property final VfsFiles catalog(const(char)[] pattern) { 708 FTPConnection conn; 709 710 scope(exit) { 711 if(conn !is null) 712 conn.close(); 713 } 714 715 const(char)[] connect = toString_; 716 717 if(connect[$ - 1] == '/') { 718 connect = connect[0 .. ($ - 1)]; 719 } 720 721 conn = new FTPConnection(connect, username_, password_, port_); 722 723 if(name_ != "") 724 conn.cd(name_); 725 726 FtpFileInfo[] return__; 727 728 if(pattern !is null) { 729 foreach(FtpFileInfo inf; getFiles(conn)) { 730 if(containsPattern(inf.name, pattern)) { 731 return__ ~= inf; 732 } 733 } 734 } else { 735 return__ = getFiles(conn); 736 } 737 738 return new FtpFiles(toString_, name_, username_, password_, port_, 739 return__); 740 } 741 742 /*********************************************************************** 743 Return a set of files matching the given filter 744 ***********************************************************************/ 745 746 @property final VfsFiles catalog(VfsFilter filter = null) { 747 FTPConnection conn; 748 749 scope(exit) { 750 if(conn !is null) 751 conn.close(); 752 } 753 754 const(char)[] connect = toString_; 755 756 if(connect[$ - 1] == '/') { 757 connect = connect[0 .. ($ - 1)]; 758 } 759 760 conn = new FTPConnection(connect, username_, password_, port_); 761 762 if(name_ != "") 763 conn.cd(name_); 764 765 FtpFileInfo[] return__; 766 767 if(filter !is null) 768 foreach(FtpFileInfo inf; getFiles(conn)) { 769 VfsFilterInfo vinf; 770 vinf.bytes = inf.size; 771 vinf.name = inf.name; 772 vinf.folder = false; 773 vinf.path = checkCat(checkFirst(toString_), checkCat(name_ ,inf.name)); 774 if(filter(&vinf)) 775 return__ ~= inf; 776 } 777 else 778 return__ = getFiles(conn); 779 780 return new FtpFiles(toString_, name_, username_, password_, port_, 781 return__); 782 } 783 } 784 785 /******************************************************************************* 786 Represents a file over a FTP file system. 787 *******************************************************************************/ 788 789 class FtpFile: VfsFile { 790 791 const(char)[] toString_, name_, username_, password_; 792 uint port_; 793 bool conOpen; 794 FTPConnection conn; 795 796 public this(const(char)[] server, const(char)[] path, const(char)[] username = "", 797 const(char)[] password = "", uint port = 21) 798 in { 799 assert(server.length > 0); 800 } 801 body { 802 toString_ = checkFirst(server); 803 name_ = checkLast(path); 804 username_ = username; 805 password_ = password; 806 port_ = port; 807 } 808 809 /*********************************************************************** 810 Return a short name 811 ***********************************************************************/ 812 813 @property final const(char)[] name() { 814 return fixName(name_); 815 } 816 817 /*********************************************************************** 818 Return a long name 819 ***********************************************************************/ 820 821 override final string toString() { 822 return checkCat(toString_, name_).idup; 823 } 824 825 /*********************************************************************** 826 Does this file exist? 827 ***********************************************************************/ 828 829 @property final bool exists() { 830 scope(failure) { 831 if(!conOpen) 832 if(conn !is null) 833 conn.close(); 834 } 835 836 scope(exit) { 837 if(!conOpen) 838 if(conn !is null) 839 conn.close(); 840 } 841 842 bool return_; 843 844 const(char)[] connect = toString_; 845 846 if(connect[$ - 1] == '/') { 847 connect = connect[0 .. ($ - 1)]; 848 } 849 850 if(!conOpen) { 851 conn = new FTPConnection(connect, username_, password_, port_); 852 } 853 854 if(conn.exist(name_) == 1) { 855 return_ = true; 856 } else { 857 return_ = false; 858 } 859 860 return return_; 861 } 862 863 /*********************************************************************** 864 Return the file size 865 ***********************************************************************/ 866 867 @property final ulong size() { 868 scope(failure) { 869 if(!conOpen) 870 if(conn !is null) 871 conn.close(); 872 } 873 874 scope(exit) { 875 if(!conOpen) 876 if(conn !is null) 877 conn.close(); 878 } 879 880 const(char)[] connect = toString_; 881 882 if(connect[$ - 1] == '/') { 883 connect = connect[0 .. ($ - 1)]; 884 } 885 886 if(!conOpen) { 887 conn = new FTPConnection(connect, username_, password_, port_); 888 } 889 890 return conn.size(name_); 891 } 892 893 /*********************************************************************** 894 Create and copy the given source 895 ***********************************************************************/ 896 897 final VfsFile copy(VfsFile source) { 898 output.copy(source.input); 899 return this; 900 } 901 902 /*********************************************************************** 903 Create and copy the given source, and remove the source 904 ***********************************************************************/ 905 906 final VfsFile move(VfsFile source) { 907 copy(source); 908 source.remove(); 909 return this; 910 } 911 912 /*********************************************************************** 913 Create a new file instance 914 ***********************************************************************/ 915 916 final VfsFile create() { 917 char[1] a = "0"; 918 output.write(a); 919 return this; 920 } 921 922 /*********************************************************************** 923 Create a new file instance and populate with stream 924 ***********************************************************************/ 925 926 final VfsFile create(InputStream stream) { 927 output.copy(stream); 928 return this; 929 } 930 931 /*********************************************************************** 932 Remove this file 933 ***********************************************************************/ 934 935 final VfsFile remove() { 936 937 conn.close(); 938 939 conOpen = false; 940 941 scope(failure) { 942 if(!conOpen) 943 if(conn !is null) 944 conn.close(); 945 } 946 947 scope(exit) { 948 if(!conOpen) 949 if(conn !is null) 950 conn.close(); 951 } 952 953 const(char)[] connect = toString_; 954 955 if(connect[$ - 1] == '/') { 956 connect = connect[0 .. ($ - 1)]; 957 } 958 959 if(!conOpen) { 960 conn = new FTPConnection(connect, username_, password_, port_); 961 } 962 963 conn.del(name_); 964 965 return this; 966 } 967 968 /*********************************************************************** 969 Return the input stream. Don't forget to close it 970 ***********************************************************************/ 971 972 @property final InputStream input() { 973 974 scope(failure) { 975 if(!conOpen) 976 if(conn !is null) 977 conn.close(); 978 } 979 980 const(char)[] connect = toString_; 981 982 if(connect[$ - 1] == '/') { 983 connect = connect[0 .. ($ - 1)]; 984 } 985 986 if(!conOpen) { 987 conn = new FTPConnection(connect, username_, password_, port_); 988 } 989 990 conOpen = true; 991 992 return conn.input(name_); 993 } 994 995 /*********************************************************************** 996 Return the output stream. Don't forget to close it 997 ***********************************************************************/ 998 999 @property final OutputStream output() { 1000 1001 scope(failure) { 1002 if(!conOpen) 1003 if(conn !is null) 1004 conn.close(); 1005 } 1006 1007 const(char)[] connect = toString_; 1008 1009 if(connect[$ - 1] == '/') { 1010 connect = connect[0 .. ($ - 1)]; 1011 } 1012 1013 if(!conOpen) { 1014 conn = new FTPConnection(connect, username_, password_, port_); 1015 } 1016 1017 conOpen = true; 1018 1019 return conn.output(name_); 1020 } 1021 1022 /*********************************************************************** 1023 Duplicate this entry 1024 ***********************************************************************/ 1025 1026 @property final VfsFile dup() { 1027 return new FtpFile(toString_, name_, username_, password_, port_); 1028 } 1029 1030 /*********************************************************************** 1031 Time modified 1032 ***********************************************************************/ 1033 1034 @property final Time.Time mtime() { 1035 conn.close(); 1036 1037 conOpen = false; 1038 1039 scope(failure) { 1040 if(!conOpen) 1041 if(conn !is null) 1042 conn.close(); 1043 } 1044 1045 scope(exit) { 1046 if(!conOpen) 1047 if(conn !is null) 1048 conn.close(); 1049 } 1050 1051 const(char)[] connect = toString_; 1052 1053 if(connect[$ - 1] == '/') { 1054 connect = connect[0 .. ($ - 1)]; 1055 } 1056 1057 if(!conOpen) { 1058 conn = new FTPConnection(connect, username_, password_, port_); 1059 } 1060 1061 return conn.getFileInfo(name_).modify; 1062 } 1063 1064 /*********************************************************************** 1065 Time created 1066 ***********************************************************************/ 1067 1068 @property final Time.Time ctime() { 1069 conn.close(); 1070 1071 conOpen = false; 1072 1073 scope(failure) { 1074 if(!conOpen) 1075 if(conn !is null) 1076 conn.close(); 1077 } 1078 1079 scope(exit) { 1080 if(!conOpen) 1081 if(conn !is null) 1082 conn.close(); 1083 } 1084 1085 const(char)[] connect = toString_; 1086 1087 if(connect[$ - 1] == '/') { 1088 connect = connect[0 .. ($ - 1)]; 1089 } 1090 1091 if(!conOpen) { 1092 conn = new FTPConnection(connect, username_, password_, port_); 1093 } 1094 1095 return conn.getFileInfo(name_).create; 1096 } 1097 1098 @property final Time.Time atime() { 1099 conn.close(); 1100 1101 conOpen = false; 1102 1103 scope(failure) { 1104 if(!conOpen) 1105 if(conn !is null) 1106 conn.close(); 1107 } 1108 1109 scope(exit) { 1110 if(!conOpen) 1111 if(conn !is null) 1112 conn.close(); 1113 } 1114 1115 const(char)[] connect = toString_; 1116 1117 if(connect[$ - 1] == '/') { 1118 connect = connect[0 .. ($ - 1)]; 1119 } 1120 1121 if(!conOpen) { 1122 conn = new FTPConnection(connect, username_, password_, port_); 1123 } 1124 1125 return conn.getFileInfo(name_).modify; 1126 } 1127 1128 /*********************************************************************** 1129 1130 Modified time of the file 1131 1132 ***********************************************************************/ 1133 1134 @property final Time.Time modified () 1135 { 1136 return mtime (); 1137 } 1138 } 1139 1140 /****************************************************************************** 1141 Represents a selection of Files. 1142 ******************************************************************************/ 1143 1144 class FtpFiles: VfsFiles { 1145 1146 const(char)[] toString_, name_, username_, password_; 1147 uint port_; 1148 FtpFileInfo[] infos_; 1149 1150 public this(const(char)[] server, const(char)[] path, const(char)[] username = "", 1151 const(char)[] password = "", uint port = 21, FtpFileInfo[] infos = null) 1152 in { 1153 assert(server.length > 0); 1154 } 1155 body { 1156 toString_ = checkFirst(server); 1157 name_ = checkLast(path); 1158 username_ = username; 1159 password_ = password; 1160 port_ = port; 1161 if(infos !is null) 1162 infos_ = infos; 1163 else 1164 fillInfos(); 1165 } 1166 1167 final void fillInfos() { 1168 1169 FTPConnection conn; 1170 1171 1172 1173 scope(exit) { 1174 if(conn !is null) 1175 conn.close(); 1176 } 1177 1178 const(char)[] connect = toString_; 1179 1180 if(connect[$ - 1] == '/') { 1181 connect = connect[0 .. ($ - 1)]; 1182 } 1183 1184 conn = new FTPConnection(connect, username_, password_, port_); 1185 1186 if(name_ != "") 1187 conn.cd(name_); 1188 1189 infos_ = getFiles(conn); 1190 } 1191 1192 /*********************************************************************** 1193 Iterate over the set of contained VfsFile instances 1194 ***********************************************************************/ 1195 1196 final int opApply(scope int delegate(ref VfsFile) dg) { 1197 int result = 0; 1198 1199 foreach(FtpFileInfo inf; infos_) { 1200 VfsFile x = new FtpFile(toString_, checkLast(checkCat(name_, inf.name)), 1201 username_, password_, port_); 1202 if((result = dg(x)) != 0) 1203 break; 1204 } 1205 1206 return result; 1207 } 1208 1209 /*********************************************************************** 1210 Return the total number of entries 1211 ***********************************************************************/ 1212 1213 @property final size_t files() { 1214 return infos_.length; 1215 } 1216 1217 /*********************************************************************** 1218 Return the total size of all files 1219 ***********************************************************************/ 1220 1221 @property final ulong bytes() { 1222 ulong return_; 1223 1224 foreach(FtpFileInfo inf; infos_) { 1225 return_ += inf.size; 1226 } 1227 1228 return return_; 1229 } 1230 } 1231