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