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