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 }