comparison tango/tango/io/vfs/VirtualFolder.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
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.io.FileConst;
16
17 private import tango.util.PathUtil;
18
19 private import tango.core.Exception;
20
21 private import tango.io.vfs.model.Vfs;
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 char[] name_;
40 private VfsFile[char[]] files;
41 private VfsFolder[char[]] mounts;
42 private VfsFolderEntry[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 (char[] name)
53 {
54 validate (this.name_ = name);
55 }
56
57 /***********************************************************************
58
59 Return the (short) name of this folder
60
61 ***********************************************************************/
62
63 final 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 final char[] toString()
77 {
78 return name;
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, 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 if (child.parent)
103 error ("folder '"~name~"' belongs to another host");
104 else
105 child.parent = this;
106
107 // reach up to the root, and initiate tree sweep
108 auto root = this;
109 while (root.parent)
110 if (root is this)
111 error ("circular reference detected at '"~this.name~"' while mounting '"~name~"'");
112 else
113 root = root.parent;
114 root.verify (folder, true);
115
116 // all clear, so add the new folder
117 mounts [name] = folder;
118 return this;
119 }
120
121 /***********************************************************************
122
123 Add a set of child folders. The children cannot 'overlap'
124 with others in the tree of the same type. Circular references
125 are detected and trapped.
126
127 ***********************************************************************/
128
129 VfsHost mount (VfsFolders group)
130 {
131 foreach (folder; group)
132 mount (folder);
133 return this;
134 }
135
136 /***********************************************************************
137
138 Unhook a child folder
139
140 ***********************************************************************/
141
142 VfsHost dismount (VfsFolder folder)
143 {
144 char[] name = null;
145
146 // check this is a child, and locate the mapped name
147 foreach (key, value; mounts)
148 if (folder is value)
149 name = key;
150 assert (name.ptr);
151
152 // reach up to the root, and initiate tree sweep
153 auto root = this;
154 while (root.parent)
155 root = root.parent;
156 root.verify (folder, false);
157
158 // all clear, so remove it
159 mounts.remove (name);
160 return this;
161 }
162
163 /***********************************************************************
164
165 Add a symbolic link to another file. These are referenced
166 by file() alone, and do not show up in tree traversals
167
168 ***********************************************************************/
169
170 final VfsHost map (VfsFile file, char[] name)
171 {
172 assert (name);
173 files[name] = file;
174 return this;
175 }
176
177 /***********************************************************************
178
179 Add a symbolic link to another folder. These are referenced
180 by folder() alone, and do not show up in tree traversals
181
182 ***********************************************************************/
183
184 final VfsHost map (VfsFolderEntry folder, char[] name)
185 {
186 assert (name);
187 folders[name] = folder;
188 return this;
189 }
190
191 /***********************************************************************
192
193 Iterate over the set of immediate child folders. This is
194 useful for reflecting the hierarchy
195
196 ***********************************************************************/
197
198 final int opApply (int delegate(inout VfsFolder) dg)
199 {
200 int result;
201
202 foreach (folder; mounts)
203 {
204 VfsFolder x = folder;
205 if ((result = dg(x)) != 0)
206 break;
207 }
208 return result;
209 }
210
211 /***********************************************************************
212
213 Return a folder representation of the given path. If the
214 path-head does not refer to an immediate child, and does
215 not match a symbolic link, it is considered unknown.
216
217 ***********************************************************************/
218
219 final VfsFolderEntry folder (char[] path)
220 {
221 char[] tail;
222 auto text = head (path, FileConst.PathSeparatorString, tail);
223
224 auto child = text in mounts;
225 if (child)
226 return child.folder (tail);
227
228 auto sym = text in folders;
229 if (sym is null)
230 error ("'"~text~"' is not a recognized member of '"~name~"'");
231 return *sym;
232 }
233
234 /***********************************************************************
235
236 Return a file representation of the given path. If the
237 path-head does not refer to an immediate child folder,
238 and does not match a symbolic link, it is considered unknown.
239
240 ***********************************************************************/
241
242 VfsFile file (char[] path)
243 {
244 auto tail = locatePrior (path, FileConst.PathSeparatorChar);
245 if (tail < path.length)
246 return folder(path[0..tail]).open.file(path[tail..$]);
247
248 auto sym = path in files;
249 if (sym is null)
250 error ("'"~path~"' is not a recognized member of '"~name~"'");
251 return *sym;
252 }
253
254 /***********************************************************************
255
256 Clear the entire subtree. Use with caution
257
258 ***********************************************************************/
259
260 final VfsFolder clear ()
261 {
262 foreach (name, child; mounts)
263 child.clear;
264 return this;
265 }
266
267 /***********************************************************************
268
269 Returns true if all of the children are writable
270
271 ***********************************************************************/
272
273 final bool writable ()
274 {
275 foreach (name, child; mounts)
276 if (! child.writable)
277 return false;
278 return true;
279 }
280
281 /***********************************************************************
282
283 Returns a folder set containing only this one. Statistics
284 are inclusive of entries within this folder only, which
285 should be zero since symbolic links are not included
286
287 ***********************************************************************/
288
289 final VfsFolders self ()
290 {
291 return new VirtualFolders (this, false);
292 }
293
294 /***********************************************************************
295
296 Returns a subtree of folders. Statistics are inclusive of
297 all files and folders throughout the sub-tree
298
299 ***********************************************************************/
300
301 final VfsFolders tree ()
302 {
303 return new VirtualFolders (this, true);
304 }
305
306 /***********************************************************************
307
308 Sweep the subtree of mountpoints, testing a new folder
309 against all others. This propogates a folder test down
310 throughout the tree, where each folder implementation
311 should take appropriate action
312
313 ***********************************************************************/
314
315 final void verify (VfsFolder folder, bool mounting)
316 {
317 foreach (name, child; mounts)
318 child.verify (folder, mounting);
319 }
320
321 /***********************************************************************
322
323 Close and/or synchronize changes made to this folder. Each
324 driver should take advantage of this as appropriate, perhaps
325 combining multiple files together, or possibly copying to a
326 remote location
327
328 ***********************************************************************/
329
330 VfsFolder close (bool commit = true)
331 {
332 foreach (name, child; mounts)
333 child.close (commit);
334 return this;
335 }
336
337 /***********************************************************************
338
339 Throw an exception
340
341 ***********************************************************************/
342
343 package final char[] error (char[] msg)
344 {
345 throw new VfsException (msg);
346 }
347
348 /***********************************************************************
349
350 Validate path names
351
352 ***********************************************************************/
353
354 private final void validate (char[] name)
355 {
356 assert (name);
357 if (locatePrior(name, '.') != name.length ||
358 locatePrior(name, FileConst.PathSeparatorChar) != name.length)
359 error ("'"~name~"' contains invalid characters");
360 }
361 }
362
363
364 /*******************************************************************************
365
366 A set of virtual folders. For a sub-tree, we compose the results
367 of all our subordinates and delegate subsequent request to that
368 group.
369
370 *******************************************************************************/
371
372 private class VirtualFolders : VfsFolders
373 {
374 private VfsFolders[] members; // folders in group
375
376 /***********************************************************************
377
378 Create a subset group
379
380 ***********************************************************************/
381
382 private this () {}
383
384 /***********************************************************************
385
386 Create a folder group including the provided folder and
387 (optionally) all child folders
388
389 ***********************************************************************/
390
391 private this (VirtualFolder root, bool recurse)
392 {
393 if (recurse)
394 foreach (name, folder; root.mounts)
395 members ~= folder.tree;
396 }
397
398 /***********************************************************************
399
400 Iterate over the set of contained VfsFolder instances
401
402 ***********************************************************************/
403
404 final int opApply (int delegate(inout VfsFolder) dg)
405 {
406 int ret;
407
408 foreach (group; members)
409 foreach (folder; group)
410 {
411 auto x = cast(VfsFolder) folder;
412 if ((ret = dg(x)) != 0)
413 break;
414 }
415 return ret;
416 }
417
418 /***********************************************************************
419
420 Return the number of files in this group
421
422 ***********************************************************************/
423
424 final uint files ()
425 {
426 uint files;
427 foreach (group; members)
428 files += group.files;
429 return files;
430 }
431
432 /***********************************************************************
433
434 Return the total size of all files in this group
435
436 ***********************************************************************/
437
438 final ulong bytes ()
439 {
440 ulong bytes;
441 foreach (group; members)
442 bytes += group.bytes;
443 return bytes;
444 }
445
446 /***********************************************************************
447
448 Return the number of folders in this group
449
450 ***********************************************************************/
451
452 final uint folders ()
453 {
454 uint count;
455 foreach (group; members)
456 count += group.folders;
457 return count;
458 }
459
460 /***********************************************************************
461
462 Return the total number of entries in this group
463
464 ***********************************************************************/
465
466 final uint entries ()
467 {
468 uint count;
469 foreach (group; members)
470 count += group.entries;
471 return count;
472 }
473
474 /***********************************************************************
475
476 Return a subset of folders matching the given pattern
477
478 ***********************************************************************/
479
480 final VfsFolders subset (char[] pattern)
481 {
482 auto set = new VirtualFolders;
483
484 foreach (group; members)
485 set.members ~= group.subset (pattern);
486 return set;
487 }
488
489 /***********************************************************************
490
491 Return a set of files matching the given pattern
492
493 ***********************************************************************/
494
495 final VfsFiles catalog (char[] pattern)
496 {
497 return catalog ((VfsInfo info){return patternMatch (info.name, pattern);});
498 }
499
500 /***********************************************************************
501
502 Returns a set of files conforming to the given filter
503
504 ***********************************************************************/
505
506 final VfsFiles catalog (VfsFilter filter = null)
507 {
508 return new VirtualFiles (this, filter);
509 }
510 }
511
512
513 /*******************************************************************************
514
515 A set of virtual files, represented by composing the results of
516 the given set of folders. Subsequent calls are delegated to the
517 results from those folders
518
519 *******************************************************************************/
520
521 private class VirtualFiles : VfsFiles
522 {
523 private VfsFiles[] members;
524
525 /***********************************************************************
526
527 ***********************************************************************/
528
529 private this (VirtualFolders host, VfsFilter filter)
530 {
531 foreach (group; host.members)
532 members ~= group.catalog (filter);
533 }
534
535 /***********************************************************************
536
537 Iterate over the set of contained VfsFile instances
538
539 ***********************************************************************/
540
541 final int opApply (int delegate(inout VfsFile) dg)
542 {
543 int ret;
544
545 foreach (group; members)
546 foreach (file; group)
547 if ((ret = dg(file)) != 0)
548 break;
549 return ret;
550 }
551
552 /***********************************************************************
553
554 Return the total number of entries
555
556 ***********************************************************************/
557
558 final uint files ()
559 {
560 uint count;
561 foreach (group; members)
562 count += group.files;
563 return count;
564 }
565
566 /***********************************************************************
567
568 Return the total size of all files
569
570 ***********************************************************************/
571
572 final ulong bytes ()
573 {
574 ulong count;
575 foreach (group; members)
576 count += group.bytes;
577 return count;
578 }
579 }
580
581
582 debug (VirtualFolder)
583 {
584 /*******************************************************************************
585
586 *******************************************************************************/
587
588 import tango.io.Stdout;
589 import tango.io.Buffer;
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 }