132
|
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 }
|