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.VirtualFolder;
19 
20 import ocean.meta.types.Qualifiers;
21 
22 import ocean.core.Verify;
23 
24 //import std.string;
25 
26 import ocean.core.ExceptionDefinitions;
27 
28 import ocean.io.model.IFile;
29 
30 import ocean.io.vfs.model.Vfs;
31 
32 import ocean.io.Path : patternMatch;
33 
34 import ocean.text.Util : head, locatePrior;
35 
36 /*******************************************************************************
37 
38         Virtual folders play host to other folder types, including both
39         concrete folder instances and subordinate virtual folders. You
40         can build a (singly rooted) tree from a set of virtual and non-
41         virtual folders, and treat them as though they were a combined
42         or single entity. For example, listing the contents of such a
43         tree is no different than listing the contents of a non-virtual
44         tree - there's just potentially more nodes to traverse.
45 
46 *******************************************************************************/
47 
48 class VirtualFolder : VfsHost
49 {
50         private istring                  name_;
51         private VfsFile[istring]         files;
52         private VfsFolder[istring]       mounts;
53         private VfsFolderEntry[istring]  folders;
54         private VirtualFolder           parent;
55 
56         /***********************************************************************
57 
58                 All folder must have a name. No '.' or '/' chars are
59                 permitted.
60 
61         ***********************************************************************/
62 
63         this (istring name)
64         {
65                 validate (this.name_ = name);
66         }
67 
68         /***********************************************************************
69 
70                 Return the (short) name of this folder.
71 
72         ***********************************************************************/
73 
74         final istring name()
75         {
76                 return name_;
77         }
78 
79         /***********************************************************************
80 
81                 Return the (long) name of this folder. Virtual folders
82                 do not have long names, since they don't relate directly
83                 to a concrete folder instance.
84 
85         ***********************************************************************/
86 
87         final override istring toString()
88         {
89                 return name;
90         }
91 
92         /***********************************************************************
93 
94                 Add a child folder. The child cannot 'overlap' with others
95                 in the tree of the same type. Circular references across a
96                 tree of virtual folders are detected and trapped.
97 
98                 The second argument represents an optional name that the
99                 mount should be known as, instead of the name exposed by
100                 the provided folder (it is not an alias).
101 
102         ***********************************************************************/
103 
104         VfsHost mount (VfsFolder folder, istring name = null)
105         {
106                 .verify(folder !is null);
107                 if (name.length is 0)
108                     name = folder.name;
109 
110                 // link virtual children to us
111                 auto child = cast(VirtualFolder) folder;
112                 if (child)
113                 {
114                     if (child.parent)
115                         error ("folder '"~name~"' belongs to another host");
116                     else
117                        child.parent = this;
118                 }
119 
120                 // reach up to the root, and initiate tree sweep
121                 auto root = this;
122                 while (root.parent)
123                        if (root is this)
124                            error ("circular reference detected at '"~this.name~"' while mounting '"~name~"'");
125                        else
126                           root = root.parent;
127                 root.verify (folder, true);
128 
129                 // all clear, so add the new folder
130                 mounts [name] = folder;
131                 return this;
132         }
133 
134         /***********************************************************************
135 
136                 Add a set of child folders. The children cannot 'overlap'
137                 with others in the tree of the same type. Circular references
138                 are detected and trapped.
139 
140         ***********************************************************************/
141 
142         VfsHost mount (VfsFolders group)
143         {
144                 foreach (folder; group)
145                          mount (folder);
146                 return this;
147         }
148 
149         /***********************************************************************
150 
151                 Unhook a child folder.
152 
153         ***********************************************************************/
154 
155         VfsHost dismount (VfsFolder folder)
156         {
157                 istring name = null;
158 
159                 // check this is a child, and locate the mapped name
160                 foreach (key, value; mounts)
161                          if (folder is value)
162                              name = key;
163                 .verify(name !is null);
164 
165                 // reach up to the root, and initiate tree sweep
166                 auto root = this;
167                 while (root.parent)
168                        root = root.parent;
169                 root.verify (folder, false);
170 
171                 // all clear, so remove it
172                 mounts.remove (name);
173                 return this;
174         }
175 
176         /***********************************************************************
177 
178                 Add a symbolic link to another file. These are referenced
179                 by file() alone, and do not show up in tree traversals.
180 
181         ***********************************************************************/
182 
183         final VfsHost map (VfsFile file, istring name)
184         {
185                 .verify(name !is null);
186                 files[name] = file;
187                 return this;
188         }
189 
190         /***********************************************************************
191 
192                 Add a symbolic link to another folder. These are referenced
193                 by folder() alone, and do not show up in tree traversals.
194 
195         ***********************************************************************/
196 
197         final VfsHost map (VfsFolderEntry folder, istring name)
198         {
199                 .verify(name !is null);
200                 folders[name] = folder;
201                 return this;
202         }
203 
204         /***********************************************************************
205 
206                 Iterate over the set of immediate child folders. This is
207                 useful for reflecting the hierarchy.
208 
209         ***********************************************************************/
210 
211         final int opApply (scope int delegate(ref VfsFolder) dg)
212         {
213                 int result;
214 
215                 foreach (folder; mounts)
216                         {
217                         VfsFolder x = folder;
218                         if ((result = dg(x)) != 0)
219                              break;
220                         }
221                 return result;
222         }
223 
224         /***********************************************************************
225 
226                 Return a folder representation of the given path. If the
227                 path-head does not refer to an immediate child, and does
228                 not match a symbolic link, it is considered unknown.
229 
230         ***********************************************************************/
231 
232         final VfsFolderEntry folder (istring path)
233         {
234                 istring tail;
235                 auto text = head (path, FileConst.PathSeparatorString, tail);
236 
237                 auto child = text in mounts;
238                 if (child)
239                     return child.folder (tail);
240 
241                 auto sym = text in folders;
242                 if (sym is null)
243                     error ("'"~text~"' is not a recognized member of '"~name~"'");
244                 return *sym;
245         }
246 
247         /***********************************************************************
248 
249                 Return a file representation of the given path. If the
250                 path-head does not refer to an immediate child folder,
251                 and does not match a symbolic link, it is considered unknown.
252 
253         ***********************************************************************/
254 
255         VfsFile file (istring path)
256         {
257                 auto tail = locatePrior (path, FileConst.PathSeparatorChar);
258                 if (tail < path.length)
259                     return folder(path[0..tail]).open.file(path[tail..$]);
260 
261                 auto sym = path in files;
262                 if (sym is null)
263                     error ("'"~path~"' is not a recognized member of '"~name~"'");
264                 return *sym;
265         }
266 
267         /***********************************************************************
268 
269                 Clear the entire subtree. Use with caution.
270 
271         ***********************************************************************/
272 
273         final VfsFolder clear ()
274         {
275                 foreach (name, child; mounts)
276                          child.clear;
277                 return this;
278         }
279 
280         /***********************************************************************
281 
282                 Returns true if all of the children are writable.
283 
284         ***********************************************************************/
285 
286         final bool writable ()
287         {
288                 foreach (name, child; mounts)
289                          if (! child.writable)
290                                return false;
291                 return true;
292         }
293 
294         /***********************************************************************
295 
296                 Returns a folder set containing only this one. Statistics
297                 are inclusive of entries within this folder only, which
298                 should be zero since symbolic links are not included.
299 
300         ***********************************************************************/
301 
302         final VfsFolders self ()
303         {
304                 return new VirtualFolders (this, false);
305         }
306 
307         /***********************************************************************
308 
309                 Returns a subtree of folders. Statistics are inclusive of
310                 all files and folders throughout the sub-tree.
311 
312         ***********************************************************************/
313 
314         final VfsFolders tree ()
315         {
316                 return new VirtualFolders (this, true);
317         }
318 
319         /***********************************************************************
320 
321                 Sweep the subtree of mountpoints, testing a new folder
322                 against all others. This propogates a folder test down
323                 throughout the tree, where each folder implementation
324                 should take appropriate action.
325 
326         ***********************************************************************/
327 
328         final void verify (VfsFolder folder, bool mounting)
329         {
330                 foreach (name, child; mounts)
331                          child.verify (folder, mounting);
332         }
333 
334         /***********************************************************************
335 
336                 Close and/or synchronize changes made to this folder. Each
337                 driver should take advantage of this as appropriate, perhaps
338                 combining multiple files together, or possibly copying to a
339                 remote location.
340 
341         ***********************************************************************/
342 
343         VfsFolder close (bool commit = true)
344         {
345                 foreach (name, child; mounts)
346                          child.close (commit);
347                 return this;
348         }
349 
350         /***********************************************************************
351 
352                 Throw an exception.
353 
354         ***********************************************************************/
355 
356         package final istring error (istring msg)
357         {
358                 throw new VfsException (idup(msg));
359         }
360 
361         /***********************************************************************
362 
363                 Validate path names.
364 
365         ***********************************************************************/
366 
367         private final void validate (istring name)
368         {
369                 .verify(name !is null);
370                 if (locatePrior(name, '.') != name.length ||
371                     locatePrior(name, FileConst.PathSeparatorChar) != name.length)
372                     error ("'"~name~"' contains invalid characters");
373         }
374 }
375 
376 
377 /*******************************************************************************
378 
379         A set of virtual folders. For a sub-tree, we compose the results
380         of all our subordinates and delegate subsequent request to that
381         group.
382 
383 *******************************************************************************/
384 
385 private class VirtualFolders : VfsFolders
386 {
387         private VfsFolders[] members;           // folders in group
388 
389         /***********************************************************************
390 
391                 Create a subset group.
392 
393         ***********************************************************************/
394 
395         private this () {}
396 
397         /***********************************************************************
398 
399                 Create a folder group including the provided folder and
400                 (optionally) all child folders.
401 
402         ***********************************************************************/
403 
404         private this (VirtualFolder root, bool recurse)
405         {
406                 if (recurse)
407                     foreach (name, folder; root.mounts)
408                              members ~= folder.tree;
409         }
410 
411         /***********************************************************************
412 
413                 Iterate over the set of contained VfsFolder instances.
414 
415         ***********************************************************************/
416 
417         final int opApply (scope int delegate(ref VfsFolder) dg)
418         {
419                 int ret;
420 
421                 foreach (group; members)
422                          foreach (folder; group)
423                                  {
424                                  auto x = cast(VfsFolder) folder;
425                                  if ((ret = dg(x)) != 0)
426                                       break;
427                                  }
428                 return ret;
429         }
430 
431         /***********************************************************************
432 
433                 Return the number of files in this group.
434 
435         ***********************************************************************/
436 
437         final uint files ()
438         {
439                 uint files;
440                 foreach (group; members)
441                          files += group.files;
442                 return files;
443         }
444 
445         /***********************************************************************
446 
447                 Return the total size of all files in this group.
448 
449         ***********************************************************************/
450 
451         final ulong bytes ()
452         {
453                 ulong bytes;
454                 foreach (group; members)
455                          bytes += group.bytes;
456                 return bytes;
457         }
458 
459         /***********************************************************************
460 
461                 Return the number of folders in this group.
462 
463         ***********************************************************************/
464 
465         final uint folders ()
466         {
467                 uint count;
468                 foreach (group; members)
469                          count += group.folders;
470                 return count;
471         }
472 
473         /***********************************************************************
474 
475                 Return the total number of entries in this group.
476 
477         ***********************************************************************/
478 
479         final uint entries ()
480         {
481                 uint count;
482                 foreach (group; members)
483                          count += group.entries;
484                 return count;
485         }
486 
487         /***********************************************************************
488 
489                 Return a subset of folders matching the given pattern.
490 
491         ***********************************************************************/
492 
493         final VfsFolders subset (istring pattern)
494         {
495                 auto set = new VirtualFolders;
496 
497                 foreach (group; members)
498                          set.members ~= group.subset (pattern);
499                 return set;
500         }
501 
502         /***********************************************************************
503 
504                 Return a set of files matching the given pattern.
505 
506         ***********************************************************************/
507 
508         final VfsFiles catalog (istring pattern)
509         {
510                 return catalog ((VfsInfo info){return patternMatch (info.name, pattern);});
511         }
512 
513         /***********************************************************************
514 
515                 Returns a set of files conforming to the given filter.
516 
517         ***********************************************************************/
518 
519         final VfsFiles catalog (scope VfsFilter filter = null)
520         {
521                 return new VirtualFiles (this, filter);
522         }
523 }
524 
525 
526 /*******************************************************************************
527 
528         A set of virtual files, represented by composing the results of
529         the given set of folders. Subsequent calls are delegated to the
530         results from those folders.
531 
532 *******************************************************************************/
533 
534 private class VirtualFiles : VfsFiles
535 {
536         private VfsFiles[] members;
537 
538         /***********************************************************************
539 
540         ***********************************************************************/
541 
542         private this (VirtualFolders host, scope VfsFilter filter)
543         {
544                 foreach (group; host.members)
545                          members ~= group.catalog (filter);
546         }
547 
548         /***********************************************************************
549 
550                 Iterate over the set of contained VfsFile instances.
551 
552         ***********************************************************************/
553 
554         final int opApply (scope int delegate(ref VfsFile) dg)
555         {
556                 int ret;
557 
558                 foreach (group; members)
559                          foreach (file; group)
560                                   if ((ret = dg(file)) != 0)
561                                        break;
562                 return ret;
563         }
564 
565         /***********************************************************************
566 
567                 Return the total number of entries.
568 
569         ***********************************************************************/
570 
571         final uint files ()
572         {
573                 uint count;
574                 foreach (group; members)
575                          count += group.files;
576                 return count;
577         }
578 
579         /***********************************************************************
580 
581                 Return the total size of all files.
582 
583         ***********************************************************************/
584 
585         final ulong bytes ()
586         {
587                 ulong count;
588                 foreach (group; members)
589                          count += group.bytes;
590                 return count;
591         }
592 }
593 
594 
595 debug (VirtualFolder)
596 {
597 /*******************************************************************************
598 
599 *******************************************************************************/
600 
601 import ocean.io.Stdout;
602 import ocean.io.vfs.FileFolder;
603 
604 void main()
605 {
606         auto root = new VirtualFolder ("root");
607         auto sub  = new VirtualFolder ("sub");
608         sub.mount (new FileFolder (r"d:/d/import/tango"));
609 
610         root.mount (sub)
611             .mount (new FileFolder (r"c:/"), "windows")
612             .mount (new FileFolder (r"d:/d/import/temp"));
613 
614         auto folder = root.folder (r"temp/bar");
615         Stdout.formatln ("folder = {}", folder);
616 
617         root.map (root.folder(r"temp/subtree"), "fsym")
618             .map (root.file(r"temp/subtree/test.txt"), "wumpus");
619         auto file = root.file (r"wumpus");
620         Stdout.formatln ("file = {}", file);
621         Stdout.formatln ("fsym = {}", root.folder(r"fsym").open.file("test.txt"));
622 
623         foreach (folder; root.folder(r"temp/subtree").open)
624                  Stdout.formatln ("folder.child '{}'", folder.name);
625 
626         auto set = root.self;
627         Stdout.formatln ("self.files = {}", set.files);
628         Stdout.formatln ("self.bytes = {}", set.bytes);
629         Stdout.formatln ("self.folders = {}", set.folders);
630 
631         set = root.folder("temp").open.tree;
632         Stdout.formatln ("tree.files = {}", set.files);
633         Stdout.formatln ("tree.bytes = {}", set.bytes);
634         Stdout.formatln ("tree.folders = {}", set.folders);
635 
636         foreach (folder; set)
637                  Stdout.formatln ("tree.folder '{}' has {} files", folder.name, folder.self.files);
638 
639         auto cat = set.catalog ("*.txt");
640         Stdout.formatln ("cat.files = {}", cat.files);
641         Stdout.formatln ("cat.bytes = {}", cat.bytes);
642         foreach (file; cat)
643                  Stdout.formatln ("cat.name '{}' '{}'", file.name, file.toString);
644 }
645 }