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.FileFolder; 19 20 import ocean.core.TypeConvert: assumeUnique; 21 import ocean.meta.types.Qualifiers; 22 23 import ocean.core.Verify; 24 25 import ocean.io.device.File; 26 27 import Path = ocean.io.Path; 28 29 import ocean.core.ExceptionDefinitions; 30 31 public import ocean.io.vfs.model.Vfs; 32 33 import ocean.io.model.IConduit; 34 35 import ocean.time.Time : Time; 36 37 /******************************************************************************* 38 39 Represents a physical folder in a file system. Use one of these 40 to address specific paths (sub-trees) within the file system. 41 42 *******************************************************************************/ 43 44 class FileFolder : VfsFolder 45 { 46 private istring path; 47 private VfsStats stats; 48 49 /*********************************************************************** 50 51 Create a file folder with the given path. 52 53 Option 'create' will create the path when set true, 54 or reference an existing path otherwise. 55 56 ***********************************************************************/ 57 58 this (istring path, bool create=false) 59 { 60 auto mpath = Path.standard(path.dup); 61 this.path = open (assumeUnique(mpath), create); 62 } 63 64 /*********************************************************************** 65 66 Create a FileFolder as a Group member. 67 68 ***********************************************************************/ 69 70 private this (istring path, istring name) 71 { 72 auto mpath = Path.join (path, name); 73 this.path = assumeUnique(mpath); 74 } 75 76 /*********************************************************************** 77 78 Explicitly create() or open() a named folder. 79 80 ***********************************************************************/ 81 82 private this (FileFolder parent, istring name, bool create=false) 83 { 84 .verify(parent !is null); 85 auto mpath = Path.join(parent.path, name); 86 this.path = open (assumeUnique(mpath), create); 87 } 88 89 /*********************************************************************** 90 91 Return a short name. 92 93 ***********************************************************************/ 94 95 final istring name () 96 { 97 auto mname = Path.parse(path.dup).name; 98 return assumeUnique(mname); 99 } 100 101 /*********************************************************************** 102 103 Return a long name. 104 105 ***********************************************************************/ 106 107 final override istring toString () 108 { 109 return idup(path); 110 } 111 112 /*********************************************************************** 113 114 A folder is being added or removed from the hierarchy. Use 115 this to test for validity (or whatever) and throw exceptions 116 as necessary 117 118 Here we test for folder overlap, and bail-out when found. 119 120 ***********************************************************************/ 121 122 final void verify (VfsFolder folder, bool mounting) 123 { 124 if (mounting && cast(FileFolder) folder) 125 { 126 auto src = Path.FS.padded (this.path); 127 auto dst = Path.FS.padded (folder.toString.dup); 128 129 auto len = src.length; 130 if (len > dst.length) 131 len = dst.length; 132 133 if (src[0..len] == dst[0..len]) 134 error ("folders '"~dst~"' and '"~src~"' overlap"); 135 } 136 } 137 138 /*********************************************************************** 139 140 Return a contained file representation. 141 142 ***********************************************************************/ 143 144 final VfsFile file (istring name) 145 { 146 auto mpath = Path.join (path, name); 147 return new FileHost (assumeUnique(mpath)); 148 } 149 150 /*********************************************************************** 151 152 Return a contained folder representation. 153 154 ***********************************************************************/ 155 156 final VfsFolderEntry folder (istring path) 157 { 158 return new FolderHost (this, path); 159 } 160 161 /*********************************************************************** 162 163 Remove the folder subtree. Use with care! 164 165 ***********************************************************************/ 166 167 final VfsFolder clear () 168 { 169 Path.remove (Path.collate(path, "*", true)); 170 return this; 171 } 172 173 /*********************************************************************** 174 175 Is folder writable? 176 177 ***********************************************************************/ 178 179 final bool writable () 180 { 181 return Path.isWritable (path); 182 } 183 184 /*********************************************************************** 185 186 Returns content information about this folder. 187 188 ***********************************************************************/ 189 190 final VfsFolders self () 191 { 192 return new FolderGroup (this, false); 193 } 194 195 /*********************************************************************** 196 197 Returns a subtree of folders matching the given name. 198 199 ***********************************************************************/ 200 201 final VfsFolders tree () 202 { 203 return new FolderGroup (this, true); 204 } 205 206 /*********************************************************************** 207 208 Iterate over the set of immediate child folders. This is 209 useful for reflecting the hierarchy. 210 211 ***********************************************************************/ 212 213 final int opApply (scope int delegate(ref VfsFolder) dg) 214 { 215 int result; 216 217 foreach (folder; folders(true)) 218 { 219 VfsFolder x = folder; 220 if ((result = dg(x)) != 0) 221 break; 222 } 223 return result; 224 } 225 226 /*********************************************************************** 227 228 Close and/or synchronize changes made to this folder. Each 229 driver should take advantage of this as appropriate, perhaps 230 combining multiple files together, or possibly copying to a 231 remote location. 232 233 ***********************************************************************/ 234 235 VfsFolder close (bool commit = true) 236 { 237 return this; 238 } 239 240 /*********************************************************************** 241 242 Sweep owned folders. 243 244 ***********************************************************************/ 245 246 private FileFolder[] folders (bool collect) 247 { 248 FileFolder[] folders; 249 250 stats = stats.init; 251 foreach (info; Path.children (path)) 252 if (info.folder) 253 { 254 if (collect) 255 folders ~= new FileFolder (info.path, info.name); 256 ++stats.folders; 257 } 258 else 259 { 260 stats.bytes += info.bytes; 261 ++stats.files; 262 } 263 264 return folders; 265 } 266 267 /*********************************************************************** 268 269 Sweep owned files. 270 271 ***********************************************************************/ 272 273 private char[][] files (ref VfsStats stats, scope VfsFilter filter = null) 274 { 275 char[][] files; 276 277 foreach (info; Path.children (path)) 278 if (info.folder is false) 279 if (filter is null || filter(&info)) 280 { 281 files ~= Path.join (info.path, info.name); 282 stats.bytes += info.bytes; 283 ++stats.files; 284 } 285 286 return files; 287 } 288 289 /*********************************************************************** 290 291 Throw an exception. 292 293 ***********************************************************************/ 294 295 private char[] error (cstring msg) 296 { 297 throw new VfsException (idup(msg)); 298 } 299 300 /*********************************************************************** 301 302 Create or open the given path, and detect path errors. 303 304 ***********************************************************************/ 305 306 private istring open (istring path, bool create) 307 { 308 if (Path.exists (path)) 309 { 310 if (! Path.isFolder (path)) 311 error ("FileFolder.open :: path exists but not as a folder: "~path); 312 } 313 else 314 if (create) 315 Path.createPath (path); 316 else 317 error ("FileFolder.open :: path does not exist: "~path); 318 return path; 319 } 320 } 321 322 323 /******************************************************************************* 324 325 Represents a group of files (need this declared here to avoid 326 a bunch of bizarre compiler warnings.) 327 328 *******************************************************************************/ 329 330 class FileGroup : VfsFiles 331 { 332 private char[][] group; // set of filtered filenames 333 private char[][] hosts; // set of containing folders 334 private VfsStats stats; // stats for contained files 335 336 /*********************************************************************** 337 338 ***********************************************************************/ 339 340 this (FolderGroup host, scope VfsFilter filter) 341 { 342 foreach (folder; host.members) 343 { 344 auto files = folder.files (stats, filter); 345 if (files.length) 346 { 347 group ~= files; 348 //hosts ~= folder.toString; 349 } 350 } 351 } 352 353 /*********************************************************************** 354 355 Iterate over the set of contained VfsFile instances. 356 357 ***********************************************************************/ 358 359 final int opApply (scope int delegate(ref VfsFile) dg) 360 { 361 int result; 362 auto host = new FileHost; 363 364 foreach (file; group) 365 { 366 VfsFile x = host; 367 host.path.parse (file); 368 if ((result = dg(x)) != 0) 369 break; 370 } 371 return result; 372 } 373 374 /*********************************************************************** 375 376 Return the total number of entries. 377 378 ***********************************************************************/ 379 380 final uint files () 381 { 382 return cast(uint) group.length; 383 } 384 385 /*********************************************************************** 386 387 Return the total size of all files. 388 389 ***********************************************************************/ 390 391 final ulong bytes () 392 { 393 return stats.bytes; 394 } 395 } 396 397 398 /******************************************************************************* 399 400 A set of folders representing a selection. This is where file 401 selection is made, and pattern-matched folder subsets can be 402 extracted. You need one of these to expose statistics (such as 403 file or folder count) of a selected folder group. 404 405 *******************************************************************************/ 406 407 private class FolderGroup : VfsFolders 408 { 409 private FileFolder[] members; // folders in group 410 411 /*********************************************************************** 412 413 Create a subset group. 414 415 ***********************************************************************/ 416 417 private this () {} 418 419 /*********************************************************************** 420 421 Create a folder group including the provided folder and 422 (optionally) all child folders. 423 424 ***********************************************************************/ 425 426 private this (FileFolder root, bool recurse) 427 { 428 members = root ~ scan (root, recurse); 429 } 430 431 /*********************************************************************** 432 433 Iterate over the set of contained VfsFolder instances. 434 435 ***********************************************************************/ 436 437 final int opApply (scope int delegate(ref VfsFolder) dg) 438 { 439 int result; 440 441 foreach (folder; members) 442 { 443 VfsFolder x = folder; 444 if ((result = dg(x)) != 0) 445 break; 446 } 447 return result; 448 } 449 450 /*********************************************************************** 451 452 Return the number of files in this group. 453 454 ***********************************************************************/ 455 456 final uint files () 457 { 458 uint files; 459 foreach (folder; members) 460 files += folder.stats.files; 461 return files; 462 } 463 464 /*********************************************************************** 465 466 Return the total size of all files in this group. 467 468 ***********************************************************************/ 469 470 final ulong bytes () 471 { 472 ulong bytes; 473 474 foreach (folder; members) 475 bytes += folder.stats.bytes; 476 return bytes; 477 } 478 479 /*********************************************************************** 480 481 Return the number of folders in this group. 482 483 ***********************************************************************/ 484 485 final uint folders () 486 { 487 if (members.length is 1) 488 return members[0].stats.folders; 489 return cast(uint) members.length; 490 } 491 492 /*********************************************************************** 493 494 Return the total number of entries in this group. 495 496 ***********************************************************************/ 497 498 final uint entries () 499 { 500 return files + folders; 501 } 502 503 /*********************************************************************** 504 505 Return a subset of folders matching the given pattern. 506 507 ***********************************************************************/ 508 509 final VfsFolders subset (istring pattern) 510 { 511 Path.PathParser parser; 512 auto set = new FolderGroup; 513 514 foreach (folder; members) 515 if (Path.patternMatch (parser.parse(folder.path.dup).name, pattern)) 516 set.members ~= folder; 517 return set; 518 } 519 520 /*********************************************************************** 521 522 Return a set of files matching the given pattern. 523 524 ***********************************************************************/ 525 526 final FileGroup catalog (istring pattern) 527 { 528 bool foo (VfsInfo info) 529 { 530 return Path.patternMatch (info.name, pattern); 531 } 532 533 return catalog (&foo); 534 } 535 536 /*********************************************************************** 537 538 Returns a set of files conforming to the given filter. 539 540 ***********************************************************************/ 541 542 final FileGroup catalog (scope VfsFilter filter = null) 543 { 544 return new FileGroup (this, filter); 545 } 546 547 /*********************************************************************** 548 549 Internal routine to traverse the folder tree. 550 551 ***********************************************************************/ 552 553 private final FileFolder[] scan (FileFolder root, bool recurse) 554 { 555 auto folders = root.folders (recurse); 556 if (recurse) 557 foreach (child; folders) 558 folders ~= scan (child, recurse); 559 return folders; 560 } 561 } 562 563 564 /******************************************************************************* 565 566 A host for folders, currently used to harbor create() and open() 567 methods only. 568 569 *******************************************************************************/ 570 571 private class FolderHost : VfsFolderEntry 572 { 573 private istring path; 574 private FileFolder parent; 575 576 /*********************************************************************** 577 578 ***********************************************************************/ 579 580 private this (FileFolder parent, istring path) 581 { 582 this.path = idup(path); 583 this.parent = parent; 584 } 585 586 /*********************************************************************** 587 588 ***********************************************************************/ 589 590 final VfsFolder create () 591 { 592 return new FileFolder (parent, path, true); 593 } 594 595 /*********************************************************************** 596 597 ***********************************************************************/ 598 599 final VfsFolder open () 600 { 601 return new FileFolder (parent, path, false); 602 } 603 604 /*********************************************************************** 605 606 Test to see if a folder exists. 607 608 ***********************************************************************/ 609 610 bool exists () 611 { 612 try { 613 open(); 614 return true; 615 } catch (IOException x) {} 616 return false; 617 } 618 } 619 620 621 /******************************************************************************* 622 623 Represents things you can do with a file. 624 625 *******************************************************************************/ 626 627 private class FileHost : VfsFile 628 { 629 // effectively immutable, mutated only in constructor 630 private Path.PathParser path; 631 632 /*********************************************************************** 633 634 ***********************************************************************/ 635 636 this (istring path = null) 637 { 638 this.path.parse (path.dup); 639 } 640 641 /*********************************************************************** 642 643 Return a short name. 644 645 ***********************************************************************/ 646 647 final istring name() 648 { 649 return cast(istring) path.file; 650 } 651 652 /*********************************************************************** 653 654 Return a long name. 655 656 ***********************************************************************/ 657 658 final override istring toString () 659 { 660 return path.toString; 661 } 662 663 /*********************************************************************** 664 665 Does this file exist? 666 667 ***********************************************************************/ 668 669 final bool exists() 670 { 671 return Path.exists (path.toString); 672 } 673 674 /*********************************************************************** 675 676 Return the file size. 677 678 ***********************************************************************/ 679 680 final ulong size() 681 { 682 return Path.fileSize(path.toString); 683 } 684 685 /*********************************************************************** 686 687 Create a new file instance. 688 689 ***********************************************************************/ 690 691 final VfsFile create () 692 { 693 Path.createFile(path.toString); 694 return this; 695 } 696 697 /*********************************************************************** 698 699 Create a new file instance and populate with stream. 700 701 ***********************************************************************/ 702 703 final VfsFile create (InputStream input) 704 { 705 create.output.copy(input).close; 706 return this; 707 } 708 709 /*********************************************************************** 710 711 Create and copy the given source. 712 713 ***********************************************************************/ 714 715 VfsFile copy (VfsFile source) 716 { 717 auto input = source.input; 718 scope (exit) input.close; 719 return create (input); 720 } 721 722 /*********************************************************************** 723 724 Create and copy the given source, and remove the source. 725 726 ***********************************************************************/ 727 728 final VfsFile move (VfsFile source) 729 { 730 copy (source); 731 source.remove; 732 return this; 733 } 734 735 /*********************************************************************** 736 737 Return the input stream. Don't forget to close it. 738 739 ***********************************************************************/ 740 741 final InputStream input () 742 { 743 return new File (path.toString); 744 } 745 746 /*********************************************************************** 747 748 Return the output stream. Don't forget to close it. 749 750 ***********************************************************************/ 751 752 final OutputStream output () 753 { 754 return new File (path.toString, File.WriteExisting); 755 } 756 757 /*********************************************************************** 758 759 Remove this file. 760 761 ***********************************************************************/ 762 763 final VfsFile remove () 764 { 765 Path.remove (path.toString); 766 return this; 767 } 768 769 /*********************************************************************** 770 771 Duplicate this entry. 772 773 ***********************************************************************/ 774 775 final VfsFile dup() 776 { 777 auto ret = new FileHost; 778 ret.path = path.dup; 779 return ret; 780 } 781 782 /*********************************************************************** 783 784 Modified time of the file. 785 786 ***********************************************************************/ 787 788 final Time modified () 789 { 790 return Path.timeStamps(path.toString).modified; 791 } 792 } 793 794 795 debug (FileFolder) 796 { 797 798 /******************************************************************************* 799 800 *******************************************************************************/ 801 802 import ocean.io.Stdout; 803 import ocean.io.device.Array; 804 805 void main() 806 { 807 auto root = new FileFolder ("d:/d/import/temp", true); 808 root.folder("test").create; 809 root.file("test.txt").create(new Array("hello")); 810 Stdout.formatln ("test.txt.length = {}", root.file("test.txt").size); 811 812 root = new FileFolder ("c:/"); 813 auto set = root.self; 814 815 Stdout.formatln ("self.files = {}", set.files); 816 Stdout.formatln ("self.bytes = {}", set.bytes); 817 Stdout.formatln ("self.folders = {}", set.folders); 818 Stdout.formatln ("self.entries = {}", set.entries); 819 /+ 820 set = root.tree; 821 Stdout.formatln ("tree.files = {}", set.files); 822 Stdout.formatln ("tree.bytes = {}", set.bytes); 823 Stdout.formatln ("tree.folders = {}", set.folders); 824 Stdout.formatln ("tree.entries = {}", set.entries); 825 826 //foreach (folder; set) 827 //Stdout.formatln ("tree.folder '{}' has {} files", folder.name, folder.self.files); 828 829 auto cat = set.catalog ("s*"); 830 Stdout.formatln ("cat.files = {}", cat.files); 831 Stdout.formatln ("cat.bytes = {}", cat.bytes); 832 +/ 833 //foreach (file; cat) 834 // Stdout.formatln ("cat.name '{}' '{}'", file.name, file.toString); 835 } 836 }