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