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