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