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 }