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