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