1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2007 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Oct 2007: Initial version
8 
9         author:         Kris
10 
11 *******************************************************************************/
12 
13 module tango.io.vfs.FileFolder;
14 
15 private import tango.io.device.File;
16 
17 private import Path = tango.io.Path;
18 
19 private import tango.core.Exception;
20 
21 public import tango.io.vfs.model.Vfs;
22 
23 private import tango.io.model.IConduit;
24 
25 private import tango.time.Time : Time;
26 
27 /*******************************************************************************
28 
29         Represents a physical folder in a file system. Use one of these
30         to address specific paths (sub-trees) within the file system.
31 
32 *******************************************************************************/
33 
34 class FileFolder : VfsFolder
35 {
36         private const(char)[]   path;
37         private VfsStats        stats;
38 
39         /***********************************************************************
40 
41                 Create a file folder with the given path.
42 
43                 Option 'create' will create the path when set true, 
44                 or reference an existing path otherwise
45 
46         ***********************************************************************/
47 
48         this (const(char)[] path, bool create=false)
49         {
50                 this.path = open (Path.standard(path.dup), create);
51         }
52 
53         /***********************************************************************
54 
55                 create a FileFolder as a Group member
56 
57         ***********************************************************************/
58 
59         private this (const(char)[] path, const(char)[] name)
60         {
61                 this.path = Path.join (path, name);
62         }
63 
64         /***********************************************************************
65 
66                 explicitly create() or open() a named folder
67 
68         ***********************************************************************/
69 
70         private this (FileFolder parent, const(char)[] name, bool create=false)
71         {
72                 assert (parent);
73                 this.path = open (Path.join(parent.path, name), create);
74         }
75 
76         /***********************************************************************
77 
78                 Return a short name
79 
80         ***********************************************************************/
81 
82         @property final const(char)[] name ()
83         {
84                 return Path.parse(path).name;
85         }
86 
87         /***********************************************************************
88 
89                 Return a long name
90 
91         ***********************************************************************/
92 
93         override final string toString ()
94         {
95                 return path.idup;
96         }
97 
98         /***********************************************************************
99 
100                 A folder is being added or removed from the hierarchy. Use 
101                 this to test for validity (or whatever) and throw exceptions 
102                 as necessary
103 
104                 Here we test for folder overlap, and bail-out when found.
105 
106         ***********************************************************************/
107 
108         final void verify (VfsFolder folder, bool mounting)
109         {
110                 if (mounting && cast(FileFolder) folder)
111                    {
112                    auto src = Path.FS.padded (this.toString());
113                    auto dst = Path.FS.padded (folder.toString());
114 
115                    auto len = src.length;
116                    if (len > dst.length)
117                        len = dst.length;
118 
119                    if (src[0..len] == dst[0..len])
120                        error ("folders '"~dst.idup~"' and '"~src.idup~"' overlap");
121                    }
122         }
123 
124         /***********************************************************************
125 
126                 Return a contained file representation 
127 
128         ***********************************************************************/
129 
130         @property final VfsFile file (const(char)[] name)
131         {
132                 return new FileHost (Path.join (path, name));
133         }
134 
135         /***********************************************************************
136 
137                 Return a contained folder representation 
138 
139         ***********************************************************************/
140 
141         @property final VfsFolderEntry folder (const(char)[] path)
142         {
143                 return new FolderHost (this, path);
144         }
145 
146         /***********************************************************************
147 
148                 Remove the folder subtree. Use with care!
149 
150         ***********************************************************************/
151 
152         final VfsFolder clear ()
153         {
154                 Path.remove (Path.collate(path, "*", true));
155                 return this;
156         }
157 
158         /***********************************************************************
159 
160                 Is folder writable?
161 
162         ***********************************************************************/
163 
164         @property final bool writable ()
165         {
166                 return Path.isWritable (path);
167         }
168 
169         /***********************************************************************
170 
171                 Returns content information about this folder
172 
173         ***********************************************************************/
174 
175         @property final VfsFolders self ()
176         {
177                 return new FolderGroup (this, false);
178         }
179 
180         /***********************************************************************
181 
182                 Returns a subtree of folders matching the given name
183 
184         ***********************************************************************/
185 
186         @property final VfsFolders tree ()
187         {
188                 return new FolderGroup (this, true);
189         }
190 
191         /***********************************************************************
192 
193                 Iterate over the set of immediate child folders. This is 
194                 useful for reflecting the hierarchy
195 
196         ***********************************************************************/
197 
198         final int opApply (scope int delegate(ref VfsFolder) dg)
199         {
200                 int result;
201 
202                 foreach (folder; folders(true))
203                         {
204                         VfsFolder x = folder;
205                         if ((result = dg(x)) != 0)
206                              break;
207                         }
208                 return result;
209         }
210 
211         /***********************************************************************
212 
213                 Close and/or synchronize changes made to this folder. Each
214                 driver should take advantage of this as appropriate, perhaps
215                 combining multiple files together, or possibly copying to a 
216                 remote location
217 
218         ***********************************************************************/
219 
220         VfsFolder close (bool commit = true)
221         {
222                 return this;
223         }
224 
225         /***********************************************************************
226 
227                 Sweep owned folders 
228 
229         ***********************************************************************/
230 
231         @property private FileFolder[] folders (bool collect)
232         {
233                 FileFolder[] folders;
234 
235                 stats = stats.init;
236                 foreach (info; Path.children (path))
237                          if (info.folder)
238                             {
239                             if (collect)
240                                 folders ~= new FileFolder (info.path, info.name);
241                             ++stats.folders;
242                             }
243                          else
244                             {
245                             stats.bytes += info.bytes;
246                            ++stats.files;
247                             }
248 
249                 return folders;
250         }
251 
252         /***********************************************************************
253 
254                 Sweep owned files
255 
256         ***********************************************************************/
257 
258         private char[][] files (ref VfsStats stats, VfsFilter filter = null)
259         {
260                 char[][] files;
261 
262                 foreach (info; Path.children (path))
263                          if (info.folder is false)
264                              if (filter is null || filter(&info))
265                                 {
266                                 files ~= Path.join (info.path, info.name);
267                                 stats.bytes += info.bytes; 
268                                 ++stats.files;
269                                 }
270 
271                 return files;         
272         }
273 
274         /***********************************************************************
275 
276                 Throw an exception
277 
278         ***********************************************************************/
279 
280         private const(char)[] error (string msg)
281         {
282                 throw new VfsException (msg);
283         }
284 
285         /***********************************************************************
286 
287                 Create or open the given path, and detect path errors
288 
289         ***********************************************************************/
290 
291         private const(char)[] open (const(char)[] path, bool create)
292         {
293                 if (Path.exists (path))
294                    {
295                    if (! Path.isFolder (path))
296                        error ("FileFolder.open :: path exists but not as a folder: "~path.idup);
297                    }
298                 else
299                    if (create)
300                        Path.createPath (path);
301                    else
302                       error ("FileFolder.open :: path does not exist: "~path.idup);
303                 return path;
304         }
305 }
306 
307 
308 /*******************************************************************************
309 
310         Represents a group of files (need this declared here to avoid
311         a bunch of bizarre compiler warnings)
312 
313 *******************************************************************************/
314 
315 class FileGroup : VfsFiles
316 {
317         private const(char)[][] group;          // set of filtered filenames
318         private const(char)[][] hosts;          // set of containing folders
319         private VfsStats        stats;          // stats for contained files
320 
321         /***********************************************************************
322 
323         ***********************************************************************/
324 
325         this (FolderGroup host, VfsFilter filter)
326         {
327                 foreach (folder; host.members)
328                         {
329                         auto files = folder.files (stats, filter);
330                         if (files.length)
331                            {
332                            group ~= files;
333                            //hosts ~= folder.toString();
334                            }
335                         }
336         }
337 
338         /***********************************************************************
339 
340                 Iterate over the set of contained VfsFile instances
341 
342         ***********************************************************************/
343 
344         final int opApply (scope int delegate(ref VfsFile) dg)
345         {
346                 int  result;
347                 auto host = new FileHost;
348 
349                 foreach (file; group)
350                         {
351                         VfsFile x = host;
352                         host.path = Path.parse(file);
353                         if ((result = dg(x)) != 0)
354                              break;
355                         }
356                 return result;
357         }
358 
359         /***********************************************************************
360 
361                 Return the total number of entries 
362 
363         ***********************************************************************/
364 
365         @property final size_t files ()
366         {
367                 return group.length;
368         }
369 
370         /***********************************************************************
371 
372                 Return the total size of all files 
373 
374         ***********************************************************************/
375 
376         @property final ulong bytes ()
377         {
378                 return stats.bytes;
379         }
380 }
381 
382 
383 /*******************************************************************************
384 
385         A set of folders representing a selection. This is where file 
386         selection is made, and pattern-matched folder subsets can be
387         extracted. You need one of these to expose statistics (such as
388         file or folder count) of a selected folder group 
389 
390 *******************************************************************************/
391 
392 private class FolderGroup : VfsFolders
393 {
394         private FileFolder[] members;           // folders in group
395 
396         /***********************************************************************
397 
398                 Create a subset group
399 
400         ***********************************************************************/
401 
402         private this () {}
403 
404         /***********************************************************************
405 
406                 Create a folder group including the provided folder and
407                 (optionally) all child folders
408 
409         ***********************************************************************/
410 
411         private this (FileFolder root, bool recurse)
412         {
413                 members = root ~ scan (root, recurse);
414         }
415 
416         /***********************************************************************
417 
418                 Iterate over the set of contained VfsFolder instances
419 
420         ***********************************************************************/
421 
422         final int opApply (scope int delegate(ref VfsFolder) dg)
423         {
424                 int  result;
425 
426                 foreach (folder; members)
427                         {
428                         VfsFolder x = folder;
429                         if ((result = dg(x)) != 0)
430                              break;
431                         }
432                 return result;
433         }
434 
435         /***********************************************************************
436 
437                 Return the number of files in this group
438 
439         ***********************************************************************/
440 
441         @property final size_t files ()
442         {
443                 size_t files;
444                 foreach (folder; members)
445                          files += folder.stats.files;
446                 return files;
447         }
448 
449         /***********************************************************************
450 
451                 Return the total size of all files in this group
452 
453         ***********************************************************************/
454 
455         @property final ulong bytes ()
456         {
457                 ulong bytes;
458 
459                 foreach (folder; members)
460                          bytes += folder.stats.bytes;
461                 return bytes;
462         }
463 
464         /***********************************************************************
465 
466                 Return the number of folders in this group
467 
468         ***********************************************************************/
469 
470         @property final size_t folders ()
471         {
472                 if (members.length is 1)
473                     return members[0].stats.folders;
474                 return members.length;
475         }
476 
477         /***********************************************************************
478 
479                 Return the total number of entries in this group
480 
481         ***********************************************************************/
482 
483         @property final size_t entries ()
484         {
485                 return files + folders;
486         }
487 
488         /***********************************************************************
489 
490                 Return a subset of folders matching the given pattern
491 
492         ***********************************************************************/
493 
494         final VfsFolders subset (const(char)[] pattern)
495         {  
496                 auto set = new FolderGroup;
497 
498                 foreach (folder; members)
499                          if (Path.patternMatch (Path.PathParser!(const(char))(folder.path).name, pattern))
500                              set.members ~= folder;
501                 return set;
502         }
503 
504         /***********************************************************************
505 
506                 Return a set of files matching the given pattern
507 
508         ***********************************************************************/
509 
510         @property final FileGroup catalog (const(char)[] pattern)
511         {
512                 bool foo (VfsInfo info)
513                 {
514                         return Path.patternMatch (info.name, pattern);
515                 }
516 
517                 return catalog (&foo);
518         }
519 
520         /***********************************************************************
521 
522                 Returns a set of files conforming to the given filter
523 
524         ***********************************************************************/
525 
526         @property final FileGroup catalog (VfsFilter filter = null)
527         {
528                 return new FileGroup (this, filter);
529         }
530 
531         /***********************************************************************
532 
533                 Internal routine to traverse the folder tree
534 
535         ***********************************************************************/
536 
537         private final FileFolder[] scan (FileFolder root, bool recurse)
538         {
539                 auto folders = root.folders (recurse);
540                 if (recurse)
541                     foreach (child; folders)
542                              folders ~= scan (child, recurse);
543                 return folders;
544         }
545 }
546 
547 
548 /*******************************************************************************
549 
550         A host for folders, currently used to harbor create() and open() 
551         methods only
552 
553 *******************************************************************************/
554 
555 private class FolderHost : VfsFolderEntry
556 {       
557         private const(char)[]   path;
558         private FileFolder      parent;
559 
560         /***********************************************************************
561 
562         ***********************************************************************/
563 
564         private this (FileFolder parent, const(char)[] path)
565         {
566                 this.path = path;
567                 this.parent = parent;
568         }
569 
570         /***********************************************************************
571 
572         ***********************************************************************/
573 
574         final VfsFolder create ()
575         {
576                 return new FileFolder (parent, path, true);
577         }
578 
579         /***********************************************************************
580 
581         ***********************************************************************/
582 
583         final VfsFolder open ()
584         {
585                 return new FileFolder (parent, path, false);
586         }
587 
588         /***********************************************************************
589 
590                 Test to see if a folder exists
591 
592         ***********************************************************************/
593 
594         @property bool exists ()
595         {
596                 try {
597                     open();
598                     return true;
599                     } catch (IOException x) {}
600                 return false;
601         }
602 }
603 
604 
605 /*******************************************************************************
606 
607         Represents things you can do with a file 
608 
609 *******************************************************************************/
610 
611 private class FileHost : VfsFile
612 {
613         private Path.PathParser!(const(char)) path;
614 
615         /***********************************************************************
616 
617         ***********************************************************************/
618 
619         this (const(char)[] path = null)
620         {
621                 this.path = Path.PathParser!(const(char))(path);
622         }
623 
624         /***********************************************************************
625 
626                 Return a short name
627 
628         ***********************************************************************/
629 
630         @property final const(char)[] name()
631         {
632                 return path.file;
633         }
634 
635         /***********************************************************************
636 
637                 Return a long name
638 
639         ***********************************************************************/
640 
641         override final string toString ()
642         {
643                 return path.toString().idup;
644         }
645 
646         /***********************************************************************
647 
648                 Does this file exist?
649 
650         ***********************************************************************/
651 
652         @property final bool exists()
653         {
654                 return Path.exists (path.toString());
655         }
656 
657         /***********************************************************************
658 
659                 Return the file size
660 
661         ***********************************************************************/
662 
663         @property final ulong size()
664         {
665                 return Path.fileSize(path.toString());
666         }
667 
668         /***********************************************************************
669 
670                 Create a new file instance
671 
672         ***********************************************************************/
673 
674         final VfsFile create ()
675         {
676                 Path.createFile(path.toString());
677                 return this;
678         }
679 
680         /***********************************************************************
681 
682                 Create a new file instance and populate with stream
683 
684         ***********************************************************************/
685 
686         final VfsFile create (InputStream input)
687         {
688                 create().output.copy(input).close();
689                 return this;
690         }
691 
692         /***********************************************************************
693 
694                 Create and copy the given source
695 
696         ***********************************************************************/
697 
698         VfsFile copy (VfsFile source)
699         {
700                 auto input = source.input;
701                 scope (exit) input.close();
702                 return create (input);
703         }
704 
705         /***********************************************************************
706 
707                 Create and copy the given source, and remove the source
708 
709         ***********************************************************************/
710 
711         final VfsFile move (VfsFile source)
712         {
713                 copy (source);
714                 source.remove();
715                 return this;
716         }
717 
718         /***********************************************************************
719 
720                 Return the input stream. Don't forget to close it
721 
722         ***********************************************************************/
723 
724         @property final InputStream input ()
725         {
726                 return new File (path.toString());
727         }
728 
729         /***********************************************************************
730 
731                 Return the output stream. Don't forget to close it
732 
733         ***********************************************************************/
734 
735         @property final OutputStream output ()
736         {
737                 return new File (path.toString(), File.WriteExisting);
738         }
739 
740         /***********************************************************************
741 
742                 Remove this file
743 
744         ***********************************************************************/
745 
746         final VfsFile remove ()
747         {
748                 Path.remove (path.toString());
749                 return this;
750         }
751 
752         /***********************************************************************
753 
754                 Duplicate this entry
755 
756         ***********************************************************************/
757 
758         @property final VfsFile dup()
759         {
760                 auto ret = new FileHost;
761                 ret.path = path.dup;
762                 return ret;
763         }
764         
765         /***********************************************************************
766 
767                 Modified time of the file
768 
769         ***********************************************************************/
770 
771         @property final Time modified ()
772         {
773                 return Path.timeStamps(path.toString()).modified;
774         }
775 }
776 
777 
778 debug (FileFolder)
779 {
780 
781 /*******************************************************************************
782 
783 *******************************************************************************/
784 
785 import tango.io.Stdout;
786 import tango.io.device.Array;
787 
788 void main()
789 {
790         auto root = new FileFolder ("d:/d/import/temp", true);
791         root.folder("test").create;
792         root.file("test.txt").create(new Array("hello"));
793         Stdout.formatln ("test.txt.length = {}", root.file("test.txt").size);
794 
795         root = new FileFolder ("c:/");
796         auto set = root.self;
797 
798         Stdout.formatln ("self.files = {}", set.files);
799         Stdout.formatln ("self.bytes = {}", set.bytes);
800         Stdout.formatln ("self.folders = {}", set.folders);
801         Stdout.formatln ("self.entries = {}", set.entries);
802 /+
803         set = root.tree;
804         Stdout.formatln ("tree.files = {}", set.files);
805         Stdout.formatln ("tree.bytes = {}", set.bytes);
806         Stdout.formatln ("tree.folders = {}", set.folders);
807         Stdout.formatln ("tree.entries = {}", set.entries);
808 
809         //foreach (folder; set)
810         //Stdout.formatln ("tree.folder '{}' has {} files", folder.name, folder.self.files);
811 
812         auto cat = set.catalog ("s*");
813         Stdout.formatln ("cat.files = {}", cat.files);
814         Stdout.formatln ("cat.bytes = {}", cat.bytes);
815 +/
816         //foreach (file; cat)
817         //         Stdout.formatln ("cat.name '{}' '{}'", file.name, file.toString());
818 }
819 }