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 }