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