1 /******************************************************************************* 2 3 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 4 5 license: BSD style: $(LICENSE) 6 7 version: The Great Namechange: February 2008$(BR) 8 9 Initial release: December 2007 10 11 author: Daniel Keep 12 13 *******************************************************************************/ 14 15 module tango.io.vfs.ZipFolder; 16 17 import Path = tango.io.Path; 18 import tango.io.device.File : File; 19 import tango.io.FilePath : FilePath; 20 import tango.io.device.TempFile : TempFile; 21 import tango.util.compress.Zip : ZipReader, ZipBlockReader, 22 ZipWriter, ZipBlockWriter, ZipEntry, ZipEntryInfo, Method; 23 import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 24 import tango.io.vfs.model.Vfs : VfsFolder, VfsFolderEntry, VfsFile, 25 VfsFolders, VfsFiles, VfsFilter, VfsStats, VfsFilterInfo, 26 VfsInfo, VfsSync; 27 import tango.time.Time : Time; 28 29 debug( ZipFolder ) 30 { 31 import tango.io.Stdout : Stderr; 32 } 33 34 // This disables code that is causing heap corruption in Tango 0.99.3 35 version = Bug_HeapCorruption; 36 37 // ************************************************************************ // 38 // ************************************************************************ // 39 40 private 41 { 42 enum EntryType { Dir, File } 43 44 /* 45 * Entries are what make up the internal tree that describes the 46 * filesystem of the archive. Each Entry is either a directory or a file. 47 */ 48 struct Entry 49 { 50 EntryType type; 51 52 union 53 { 54 DirEntry dir; 55 FileEntry file; 56 } 57 58 const(char)[] fullname; 59 const(char)[] name; 60 61 /+ 62 invariant() 63 { 64 assert( (type == EntryType.Dir) 65 || (type == EntryType.File) ); 66 67 assert( fullname.nz() ); 68 assert( name.nz() ); 69 } 70 +/ 71 72 VfsFilterInfo vfsFilterInfo; 73 74 VfsInfo vfsInfo() 75 { 76 return &vfsFilterInfo; 77 } 78 79 /* 80 * Updates the VfsInfo structure for this entry. 81 */ 82 void makeVfsInfo() 83 { 84 with( vfsFilterInfo ) 85 { 86 // Cheat horribly here 87 name = this.name; 88 path = this.fullname[0..($-name.length+"/".length)]; 89 90 folder = isDir; 91 bytes = folder ? 0 : fileSize; 92 } 93 } 94 95 @property bool isDir() 96 { 97 return (type == EntryType.Dir); 98 } 99 100 @property bool isFile() 101 { 102 return (type == EntryType.File); 103 } 104 105 @property ulong fileSize() 106 in 107 { 108 assert( type == EntryType.File ); 109 } 110 body 111 { 112 if( file.zipEntry !is null ) 113 return file.zipEntry.size(); 114 115 else if( file.tempFile !is null ) 116 { 117 assert( file.tempFile.length >= 0 ); 118 return cast(ulong) file.tempFile.length; 119 } 120 else 121 return 0; 122 } 123 124 /* 125 * Opens a File Entry for reading. 126 * 127 * BUG: Currently, if a user opens a new or unmodified file for input, 128 * and then opens it for output, the two streams will be working with 129 * different underlying conduits. This means that the result of 130 * openInput should probably be wrapped in some kind of switching 131 * stream that can update when the backing store for the file changes. 132 */ 133 InputStream openInput() 134 in 135 { 136 assert( type == EntryType.File ); 137 } 138 body 139 { 140 if( file.zipEntry !is null ) 141 { 142 file.zipEntry.verify(); 143 return file.zipEntry.open(); 144 } 145 else if( file.tempFile !is null ) 146 return new WrapSeekInputStream(file.tempFile, 0); 147 148 else 149 { 150 throw new Exception ("cannot open input stream for '"~fullname.idup~"'"); 151 //return new DummyInputStream; 152 } 153 } 154 155 /* 156 * Opens a file entry for output. 157 */ 158 OutputStream openOutput() 159 in 160 { 161 assert( type == EntryType.File ); 162 } 163 body 164 { 165 if( file.tempFile !is null ) 166 return new WrapSeekOutputStream(file.tempFile); 167 168 else 169 { 170 // Ok; we need to make a temporary file to store output in. 171 // If we already have a zip entry, we need to dump that into 172 // the temp. file and remove the zipEntry. 173 if( file.zipEntry !is null ) 174 { 175 { 176 auto zi = file.zipEntry.open(); 177 scope(exit) zi.close(); 178 179 file.tempFile = new TempFile; 180 file.tempFile.copy(zi).close(); 181 182 debug( ZipFolder ) 183 Stderr.formatln("Entry.openOutput: duplicated" 184 " temp file {} for {}", 185 file.tempFile, this.fullname); 186 } 187 188 // TODO: Copy file info if available 189 190 file.zipEntry = null; 191 } 192 else 193 { 194 // Otherwise, just make a new, blank temp file 195 file.tempFile = new TempFile; 196 197 debug( ZipFolder ) 198 Stderr.formatln("Entry.openOutput: created" 199 " temp file {} for {}", 200 file.tempFile, this.fullname); 201 } 202 203 assert( file.tempFile !is null ); 204 return openOutput(); 205 } 206 } 207 208 void dispose() 209 { 210 fullname = name = null; 211 212 with( vfsFilterInfo ) 213 { 214 name = path = null; 215 } 216 217 dispose_children(); 218 } 219 220 void dispose_children() 221 { 222 switch( type ) 223 { 224 case EntryType.Dir: 225 auto keys = dir.children.keys; 226 scope(exit) delete keys; 227 foreach( k ; keys ) 228 { 229 auto child = dir.children[k]; 230 child.dispose(); 231 dir.children.remove(k); 232 delete child; 233 } 234 dir.children = dir.children.init; 235 break; 236 237 case EntryType.File: 238 if( file.zipEntry !is null ) 239 { 240 // Don't really need to do anything here 241 file.zipEntry = null; 242 } 243 else if( file.tempFile !is null ) 244 { 245 // Detatch to destroy the physical file itself 246 file.tempFile.detach(); 247 file.tempFile = null; 248 } 249 break; 250 251 default: 252 debug( ZipFolder ) Stderr.formatln( 253 "Entry.dispose_children: unknown type {}", 254 type); 255 assert(false); 256 } 257 } 258 } 259 260 struct DirEntry 261 { 262 Entry*[const(char)[]] children; 263 } 264 265 struct FileEntry 266 { 267 ZipEntry zipEntry; 268 TempFile tempFile; 269 270 invariant() 271 { 272 auto zn = zipEntry is null; 273 auto tn = tempFile is null; 274 assert( (zn && tn) 275 /* zn xor tn */ || (!(zn&&tn)&&(zn||tn)) ); 276 } 277 } 278 } 279 280 // ************************************************************************ // 281 // ************************************************************************ // 282 283 /** 284 * This class represents a folder in an archive. In addition to supporting 285 * the sync operation, you can also use the archive member to get a reference 286 * to the underlying ZipFolder instance. 287 */ 288 class ZipSubFolder : VfsFolder, VfsSync 289 { 290 /// 291 @property final const(char)[] name() 292 in { assert( valid ); } 293 body 294 { 295 return entry.name; 296 } 297 298 /// 299 final override string toString() 300 in { assert( valid ); } 301 body 302 { 303 return entry.fullname.idup; 304 } 305 306 /// 307 @property final VfsFile file(const(char)[] path) 308 in 309 { 310 assert( valid ); 311 assert( !Path.parse(path).isAbsolute ); 312 } 313 body 314 { 315 auto fp = Path.parse(path); 316 auto dir = fp.path; 317 auto name = fp.file; 318 319 if (dir.length > 0 && '/' == dir[$-1]) { 320 dir = dir[0..$-1]; 321 } 322 323 // If the file is in another directory, then we need to look up that 324 // up first. 325 if( dir.nz() ) 326 { 327 auto dir_ent = this.folder(dir); 328 auto dir_obj = dir_ent.open(); 329 return dir_obj.file(name); 330 } 331 else 332 { 333 // Otherwise, we need to check and see whether the file is in our 334 // entry list. 335 if( auto file_entry = (name in this.entry.dir.children) ) 336 { 337 // It is; create a new object for it. 338 return new ZipFile(archive, this.entry, *file_entry); 339 } 340 else 341 { 342 // Oh dear... return a holding object. 343 return new ZipFile(archive, this.entry, name); 344 } 345 } 346 } 347 348 /// 349 @property final VfsFolderEntry folder(const(char)[] path) 350 in 351 { 352 assert( valid ); 353 assert( !Path.parse(path).isAbsolute ); 354 } 355 body 356 { 357 // Locate the folder in question. We do this by "walking" the 358 // path components. If we find a component that doesn't exist, 359 // then we create a ZipSubFolderEntry for the remainder. 360 Entry* curent = this.entry; 361 362 // h is the "head" of the path, t is the remainder. ht is both 363 // joined together. 364 const(char)[] h,t,ht; 365 ht = path; 366 367 do 368 { 369 // Split ht at the first path separator. 370 assert( ht.nz() ); 371 headTail(ht,h,t); 372 373 // Look for a pre-existing subentry 374 auto subent = (h in curent.dir.children); 375 if( t.nz() && !!subent ) 376 { 377 // Move to the subentry, and split the tail on the next 378 // iteration. 379 curent = *subent; 380 ht = t; 381 } 382 else 383 // If the next component doesn't exist, return a folder entry. 384 // If the tail is empty, return a folder entry as well (let 385 // the ZipSubFolderEntry do the last lookup.) 386 return new ZipSubFolderEntry(archive, curent, ht); 387 } 388 while( true ); 389 //assert(false); 390 } 391 392 /// 393 @property final VfsFolders self() 394 in { assert( valid ); } 395 body 396 { 397 return new ZipSubFolderGroup(archive, this, false); 398 } 399 400 /// 401 @property final VfsFolders tree() 402 in { assert( valid ); } 403 body 404 { 405 return new ZipSubFolderGroup(archive, this, true); 406 } 407 408 /// 409 final int opApply(scope int delegate(ref VfsFolder) dg) 410 in { assert( valid ); } 411 body 412 { 413 int result = 0; 414 415 foreach( _,childEntry ; this.entry.dir.children ) 416 { 417 if( childEntry.isDir ) 418 { 419 VfsFolder childFolder = new ZipSubFolder(archive, childEntry); 420 if( (result = dg(childFolder)) != 0 ) 421 break; 422 } 423 } 424 425 return result; 426 } 427 428 /// 429 final VfsFolder clear() 430 in { assert( valid ); } 431 body 432 { 433 version( ZipFolder_NonMutating ) 434 { 435 mutate_error("VfsFolder.clear"); 436 assert(false); 437 } 438 else 439 { 440 // MUTATE 441 enforce_mutable(); 442 443 // Disposing of the underlying entry subtree should do our job for us. 444 entry.dispose_children(); 445 mutate(); 446 return this; 447 } 448 } 449 450 /// 451 @property final bool writable() 452 in { assert( valid ); } 453 body 454 { 455 return !archive.readonly; 456 } 457 458 /** 459 * Closes this folder object. If commit is true, then the folder is 460 * sync'ed before being closed. 461 */ 462 VfsFolder close(bool commit = true) 463 in { assert( valid ); } 464 body 465 { 466 // MUTATE 467 if( commit ) sync(); 468 469 // Just clean up our pointers 470 archive = null; 471 entry = null; 472 return this; 473 } 474 475 /** 476 * This will flush any changes to the archive to disk. Note that this 477 * applies to the entire archive, not just this folder and its contents. 478 */ 479 override VfsFolder sync() 480 { 481 assert( valid ); 482 483 // MUTATE 484 archive.sync(); 485 return this; 486 } 487 488 /// 489 final void verify(VfsFolder folder, bool mounting) 490 in { assert( valid ); } 491 body 492 { 493 auto zipfolder = cast(ZipSubFolder) folder; 494 495 if( mounting 496 && zipfolder !is null 497 && zipfolder.archive is archive ) 498 { 499 auto src = this.toString(); 500 auto dst = zipfolder.toString(); 501 502 auto len = src.length > dst.length ? dst.length : src.length; 503 504 if( src[0..len] == dst[0..len] ) 505 error(`folders "`~dst~`" and "`~src~`" in archive "` 506 ~archive.path~`" overlap`); 507 } 508 } 509 510 /** 511 * Returns a reference to the underlying ZipFolder instance. 512 */ 513 @property final ZipFolder archive() { return _archive; } 514 515 private: 516 ZipFolder _archive; 517 Entry* entry; 518 VfsStats stats; 519 520 @property final ZipFolder archive(ZipFolder v) { return _archive = v; } 521 522 this(ZipFolder archive, Entry* entry) 523 { 524 this.reset(archive, entry); 525 } 526 527 final void reset(ZipFolder archive, Entry* entry) 528 in 529 { 530 assert( archive !is null ); 531 assert( entry.isDir ); 532 } 533 out { assert( valid ); } 534 body 535 { 536 this.archive = archive; 537 this.entry = entry; 538 } 539 540 @property final bool valid() 541 { 542 return( (archive !is null) && !archive.closed ); 543 } 544 545 final void enforce_mutable() 546 in { assert( valid ); } 547 body 548 { 549 if( archive.readonly ) 550 // TODO: exception 551 throw new Exception("cannot mutate a read-only Zip archive"); 552 } 553 554 final void mutate() 555 in { assert( valid ); } 556 body 557 { 558 enforce_mutable(); 559 archive.modified = true; 560 } 561 562 final ZipSubFolder[] folders(bool collect) 563 in { assert( valid ); } 564 body 565 { 566 ZipSubFolder[] folders; 567 stats = stats.init; 568 569 foreach( _,childEntry ; entry.dir.children ) 570 { 571 if( childEntry.isDir ) 572 { 573 if( collect ) folders ~= new ZipSubFolder(archive, childEntry); 574 ++ stats.folders; 575 } 576 else 577 { 578 assert( childEntry.isFile ); 579 stats.bytes += childEntry.fileSize; 580 ++ stats.files; 581 } 582 } 583 584 return folders; 585 } 586 587 final Entry*[] files(ref VfsStats stats, VfsFilter filter = null) 588 in { assert( valid ); } 589 body 590 { 591 Entry*[] files; 592 593 foreach( _,childEntry ; entry.dir.children ) 594 { 595 if( childEntry.isFile ) 596 if( filter is null || filter(childEntry.vfsInfo()) ) 597 { 598 files ~= childEntry; 599 stats.bytes += childEntry.fileSize; 600 ++stats.files; 601 } 602 } 603 604 return files; 605 } 606 } 607 608 // ************************************************************************ // 609 // ************************************************************************ // 610 611 /** 612 * ZipFolder serves as the root object for all Zip archives in the VFS. 613 * Presently, it can only open archives on the local filesystem. 614 */ 615 class ZipFolder : ZipSubFolder 616 { 617 /** 618 * Opens an archive from the local filesystem. If the readonly argument 619 * is specified as true, then modification of the archive will be 620 * explicitly disallowed. 621 */ 622 this(const(char)[] path, bool readonly=false) 623 out { assert( valid ); } 624 body 625 { 626 debug( ZipFolder ) 627 Stderr.formatln(`ZipFolder("{}", {})`, path, readonly); 628 this.resetArchive(path, readonly); 629 super(this, root); 630 } 631 632 /** 633 * Closes the archive, and releases all internal resources. If the commit 634 * argument is true (the default), then changes to the archive will be 635 * flushed out to disk. If false, changes will simply be discarded. 636 */ 637 final override VfsFolder close(bool commit = true) 638 { 639 assert (valid); 640 641 debug( ZipFolder ) 642 Stderr.formatln("ZipFolder.close({})",commit); 643 644 // MUTATE 645 if( commit ) sync(); 646 647 // Close ZipReader 648 if( zr !is null ) 649 { 650 zr.close(); 651 delete zr; 652 } 653 654 // Destroy entries 655 root.dispose(); 656 version( Bug_HeapCorruption ) 657 root = null; 658 else 659 delete root; 660 661 return this; 662 } 663 664 /** 665 * Flushes all changes to the archive out to disk. 666 */ 667 final override VfsFolder sync() 668 out 669 { 670 assert( valid ); 671 assert( !modified ); 672 } 673 body 674 { 675 assert( valid ); 676 677 debug( ZipFolder ) 678 Stderr("ZipFolder.sync()").newline; 679 680 if( !modified ) 681 return this; 682 683 version( ZipFolder_NonMutating ) 684 { 685 mutate_error("ZipFolder.sync"); 686 assert(false); 687 } 688 else 689 { 690 enforce_mutable(); 691 692 // First, we need to determine if we have any zip entries. If we 693 // don't, then we can write directly to the path. If there *are* 694 // zip entries, then we'll need to write to a temporary path instead. 695 OutputStream os; 696 TempFile tempFile; 697 scope(exit) if( tempFile !is null ) delete tempFile; 698 699 auto p = Path.parse (path); 700 foreach( file ; this.tree.catalog ) 701 { 702 if( auto zf = cast(ZipFile) file ) 703 if( zf.entry.file.zipEntry !is null ) 704 { 705 tempFile = new TempFile(p.path, TempFile.Permanent); 706 os = tempFile; 707 debug( ZipFolder ) 708 Stderr.formatln(" sync: created temp file {}", 709 tempFile.path); 710 break; 711 } 712 } 713 714 if( tempFile is null ) 715 { 716 // Kill the current zip reader so we can re-open the file it's 717 // using. 718 if( zr !is null ) 719 { 720 zr.close(); 721 delete zr; 722 } 723 724 os = new File(path, File.WriteCreate); 725 } 726 727 // Now, we can create the archive. 728 { 729 scope zw = new ZipBlockWriter(os); 730 foreach( file ; this.tree.catalog ) 731 { 732 auto zei = ZipEntryInfo(file.toString()[1..$]); 733 // BUG: Passthru doesn't maintain compression for some 734 // reason... 735 if( auto zf = cast(ZipFile) file ) 736 { 737 if( zf.entry.file.zipEntry !is null ) 738 zw.putEntry(zei, zf.entry.file.zipEntry); 739 else 740 zw.putStream(zei, file.input); 741 } 742 else 743 zw.putStream(zei, file.input); 744 } 745 zw.finish(); 746 } 747 748 // With that done, we can free all our handles, etc. 749 debug( ZipFolder ) 750 Stderr(" sync: close").newline; 751 this.close(/*commit*/ false); 752 os.close(); 753 754 // If we wrote the archive into a temporary file, move that over the 755 // top of the old archive. 756 if( tempFile !is null ) 757 { 758 debug( ZipFolder ) 759 Stderr(" sync: destroying temp file").newline; 760 761 debug( ZipFolder ) 762 Stderr.formatln(" sync: renaming {} to {}", 763 tempFile, path); 764 765 Path.rename (tempFile.toString(), path); 766 } 767 768 // Finally, re-open the archive so that we have all the nicely 769 // compressed files. 770 debug( ZipFolder ) 771 Stderr(" sync: reset archive").newline; 772 this.resetArchive(path, readonly); 773 774 debug( ZipFolder ) 775 Stderr(" sync: reset folder").newline; 776 this.reset(this, root); 777 778 debug( ZipFolder ) 779 Stderr(" sync: done").newline; 780 781 return this; 782 } 783 } 784 785 /** 786 * Indicates whether the archive was opened for read-only access. Note 787 * that in addition to the readonly constructor flag, this is also 788 * influenced by whether the file itself is read-only or not. 789 */ 790 @property final bool readonly() { return _readonly; } 791 792 /** 793 * Allows you to read and specify the path to the archive. The effect of 794 * setting this is to change where the archive will be written to when 795 * flushed to disk. 796 */ 797 @property final const(char)[] path() { return _path; } 798 @property final const(char)[] path(const(char)[] v) { return _path = v; } /// ditto 799 800 private: 801 ZipReader zr; 802 Entry* root; 803 const(char)[] _path; 804 bool _readonly; 805 bool modified = false; 806 807 @property final bool readonly(bool v) { return _readonly = v; } 808 809 @property final bool closed() 810 { 811 debug( ZipFolder ) 812 Stderr("ZipFolder.closed()").newline; 813 return (root is null); 814 } 815 816 @property final bool valid() 817 { 818 debug( ZipFolder ) 819 Stderr("ZipFolder.valid()").newline; 820 return !closed; 821 } 822 823 final OutputStream mutateStream(OutputStream source) 824 { 825 return new EventSeekOutputStream(source, 826 EventSeekOutputStream.Callbacks( 827 null, 828 null, 829 &mutate_write, 830 null)); 831 } 832 833 void mutate_write(size_t bytes, const(void)[] src) 834 { 835 if( !(bytes == 0 || bytes == IConduit.Eof) ) 836 this.modified = true; 837 } 838 839 void resetArchive(const(char)[] path, bool readonly=false) 840 out { assert( valid ); } 841 body 842 { 843 debug( ZipFolder ) 844 Stderr.formatln(`ZipFolder.resetArchive("{}", {})`, path, readonly); 845 846 debug( ZipFolder ) 847 Stderr.formatln(" .. size of Entry: {0}, {0:x} bytes", Entry.sizeof); 848 849 this.path = path; 850 this.readonly = readonly; 851 852 // Make sure the modified flag is set appropriately 853 scope(exit) modified = false; 854 855 // First, create a root entry 856 root = new Entry; 857 root.type = EntryType.Dir; 858 root.fullname = root.name = "/"; 859 860 // If the user allowed writing, also allow creating a new archive. 861 // Note that we MUST drop out here if the archive DOES NOT exist, 862 // since Path.isWriteable will throw an exception if called on a 863 // non-existent path. 864 if( !this.readonly && !Path.exists(path) ) 865 return; 866 867 // Update readonly to reflect the write-protected status of the 868 // archive. 869 this.readonly = this.readonly || !Path.isWritable(path); 870 871 zr = new ZipBlockReader(path); 872 873 // Parse the contents of the archive 874 foreach( zipEntry ; zr ) 875 { 876 // Normalise name 877 auto name = FilePath(zipEntry.info.name.dup).standard.toString(); 878 879 // If the last character is '/', treat as a directory and skip 880 // TODO: is there a better way of detecting this? 881 if( name[$-1] == '/' ) 882 continue; 883 884 // Now, we need to locate the right spot to insert this entry. 885 { 886 // That's CURrent ENTity, not current OR currant... 887 Entry* curent = root; 888 const(char)[] h,t; 889 headTail(name,h,t); 890 while( t.nz() ) 891 { 892 assert( curent.isDir ); 893 if( auto nextent = (h in curent.dir.children) ) 894 curent = *nextent; 895 896 else 897 { 898 // Create new directory entry 899 Entry* dirent = new Entry; 900 dirent.type = EntryType.Dir; 901 if( curent.fullname != "/" ) 902 dirent.fullname = curent.fullname ~ "/" ~ h; 903 else 904 dirent.fullname = "/" ~ h; 905 dirent.name = dirent.fullname[$-h.length..$]; 906 907 // Insert into current entry 908 curent.dir.children[dirent.name] = dirent; 909 910 // Make it the new current entry 911 curent = dirent; 912 } 913 914 headTail(t,h,t); 915 } 916 917 // Getting here means that t is empty, which means the final 918 // component of the path--the file name--is in h. The entry 919 // of the containing directory is in curent. 920 921 // Make sure the file isn't already there (you never know!) 922 assert( !(h in curent.dir.children) ); 923 924 // Create a new file entry for it. 925 { 926 // BUG: Bug_HeapCorruption 927 // with ZipTest, on the resetArchive operation, on 928 // the second time through this next line, it erroneously 929 // allocates filent 16 bytes lower than curent. Entry 930 // is *way* larger than 16 bytes, and this causes it to 931 // zero-out the existing root element, which leads to 932 // segfaults later on at line +12: 933 // 934 // // Insert 935 // curent.dir.children[filent.name] = filent; 936 937 Entry* filent = new Entry; 938 filent.type = EntryType.File; 939 if( curent.fullname != "/" ) 940 filent.fullname = curent.fullname ~ "/" ~ h; 941 else 942 filent.fullname = "/" ~ h; 943 filent.name = filent.fullname[$-h.length..$]; 944 filent.file.zipEntry = zipEntry.dup(); 945 946 filent.makeVfsInfo(); 947 948 // Insert 949 curent.dir.children[filent.name] = filent; 950 } 951 } 952 } 953 } 954 } 955 956 // ************************************************************************ // 957 // ************************************************************************ // 958 959 /** 960 * This class represents a file within an archive. 961 */ 962 class ZipFile : VfsFile 963 { 964 /// 965 @property final const(char)[] name() 966 in { assert( valid ); } 967 body 968 { 969 if( entry ) return entry.name; 970 else return name_; 971 } 972 973 /// 974 final override string toString() 975 in { assert( valid ); } 976 body 977 { 978 if( entry ) return entry.fullname.idup; 979 else return (parent.fullname ~ "/" ~ name_).idup; 980 } 981 982 /// 983 @property final bool exists() 984 in { assert( valid ); } 985 body 986 { 987 // If we've only got a parent and a name, this means we don't actually 988 // exist; EXISTENTIAL CRISIS TEIM!!! 989 return !!entry; 990 } 991 992 /// 993 @property final ulong size() 994 in { assert( valid ); } 995 body 996 { 997 if( exists ) 998 return entry.fileSize; 999 else 1000 error("ZipFile.size: cannot reliably determine size of a " 1001 "non-existent file"); 1002 1003 assert(false); 1004 } 1005 1006 /// 1007 final VfsFile copy(VfsFile source) 1008 in { assert( valid ); } 1009 body 1010 { 1011 version( ZipFolder_NonMutating ) 1012 { 1013 mutate_error("ZipFile.copy"); 1014 assert(false); 1015 } 1016 else 1017 { 1018 // MUTATE 1019 enforce_mutable(); 1020 1021 if( !exists ) this.create(); 1022 this.output.copy(source.input); 1023 1024 return this; 1025 } 1026 } 1027 1028 /// 1029 final VfsFile move(VfsFile source) 1030 in { assert( valid ); } 1031 body 1032 { 1033 version( ZipFolder_NonMutating ) 1034 { 1035 mutate_error("ZipFile.move"); 1036 assert(false); 1037 } 1038 else 1039 { 1040 // MUTATE 1041 enforce_mutable(); 1042 1043 this.copy(source); 1044 source.remove(); 1045 1046 return this; 1047 } 1048 } 1049 1050 /// 1051 final VfsFile create() 1052 in { assert( valid ); } 1053 out { assert( valid ); } 1054 body 1055 { 1056 version( ZipFolder_NonMutating ) 1057 { 1058 mutate_error("ZipFile.create"); 1059 assert(false); 1060 } 1061 else 1062 { 1063 if( exists ) 1064 error("ZipFile.create: cannot create already existing file: " 1065 "this folder ain't big enough for the both of 'em"); 1066 1067 // MUTATE 1068 enforce_mutable(); 1069 1070 auto entry = new Entry; 1071 entry.type = EntryType.File; 1072 entry.fullname = parent.fullname.dir_app(name); 1073 entry.name = entry.fullname[$-name.length..$]; 1074 entry.makeVfsInfo(); 1075 1076 assert( !(entry.name in parent.dir.children) ); 1077 parent.dir.children[entry.name] = entry; 1078 this.reset(archive, parent, entry); 1079 mutate(); 1080 1081 // Done 1082 return this; 1083 } 1084 } 1085 1086 /// 1087 final VfsFile create(InputStream stream) 1088 in { assert( valid ); } 1089 body 1090 { 1091 version( ZipFolder_NonMutating ) 1092 { 1093 mutate_error("ZipFile.create"); 1094 assert(false); 1095 } 1096 else 1097 { 1098 create(); 1099 output.copy(stream).close(); 1100 return this; 1101 } 1102 } 1103 1104 /// 1105 final VfsFile remove() 1106 in{ assert( valid ); } 1107 out { assert( valid ); } 1108 body 1109 { 1110 version( ZipFolder_NonMutating ) 1111 { 1112 mutate_error("ZipFile.remove"); 1113 assert(false); 1114 } 1115 else 1116 { 1117 if( !exists ) 1118 error("ZipFile.remove: cannot remove non-existent file; " 1119 "rather redundant, really"); 1120 1121 // MUTATE 1122 enforce_mutable(); 1123 1124 // Save the old name 1125 auto old_name = name; 1126 1127 // Do the removal 1128 assert( !!(name in parent.dir.children) ); 1129 parent.dir.children.remove(name); 1130 entry.dispose(); 1131 entry = null; 1132 mutate(); 1133 1134 // Swap out our now empty entry for the name, so the file can be 1135 // directly recreated. 1136 this.reset(archive, parent, old_name); 1137 1138 return this; 1139 } 1140 } 1141 1142 /// 1143 @property final InputStream input() 1144 in { assert( valid ); } 1145 body 1146 { 1147 if( exists ) 1148 return entry.openInput(); 1149 1150 else 1151 error("ZipFile.input: cannot open non-existent file for input; " 1152 "results would not be very useful"); 1153 1154 assert(false); 1155 } 1156 1157 /// 1158 @property final OutputStream output() 1159 in { assert( valid ); } 1160 body 1161 { 1162 version( ZipFolder_NonMutable ) 1163 { 1164 mutate_error("ZipFile.output"); 1165 assert(false); 1166 } 1167 else 1168 { 1169 // MUTATE 1170 enforce_mutable(); 1171 1172 // Don't call mutate(); defer that until the user actually writes to or 1173 // modifies the underlying stream. 1174 return archive.mutateStream(entry.openOutput()); 1175 } 1176 } 1177 1178 /// 1179 @property final VfsFile dup() 1180 in { assert( valid ); } 1181 body 1182 { 1183 if( entry ) 1184 return new ZipFile(archive, parent, entry); 1185 else 1186 return new ZipFile(archive, parent, name); 1187 } 1188 1189 /// 1190 @property final Time modified() 1191 { 1192 return entry.file.zipEntry.info.modified; 1193 } 1194 1195 private: 1196 ZipFolder archive; 1197 Entry* entry; 1198 1199 Entry* parent; 1200 const(char)[] name_; 1201 1202 this() 1203 out { assert( !valid ); } 1204 body 1205 { 1206 } 1207 1208 this(ZipFolder archive, Entry* parent, Entry* entry) 1209 in 1210 { 1211 assert( archive !is null ); 1212 assert( parent ); 1213 assert( parent.isDir ); 1214 assert( entry ); 1215 assert( entry.isFile ); 1216 assert( parent.dir.children[entry.name] is entry ); 1217 } 1218 out { assert( valid ); } 1219 body 1220 { 1221 this.reset(archive, parent, entry); 1222 } 1223 1224 this(ZipFolder archive, Entry* parent, const(char)[] name) 1225 in 1226 { 1227 assert( archive !is null ); 1228 assert( parent ); 1229 assert( parent.isDir ); 1230 assert( name.nz() ); 1231 assert( !(name in parent.dir.children) ); 1232 } 1233 out { assert( valid ); } 1234 body 1235 { 1236 this.reset(archive, parent, name); 1237 } 1238 1239 @property final bool valid() 1240 { 1241 return( (archive !is null) && !archive.closed ); 1242 } 1243 1244 final void enforce_mutable() 1245 in { assert( valid ); } 1246 body 1247 { 1248 if( archive.readonly ) 1249 // TODO: exception 1250 throw new Exception("cannot mutate a read-only Zip archive"); 1251 } 1252 1253 final void mutate() 1254 in { assert( valid ); } 1255 body 1256 { 1257 enforce_mutable(); 1258 archive.modified = true; 1259 } 1260 1261 final void reset(ZipFolder archive, Entry* parent, Entry* entry) 1262 in 1263 { 1264 assert( archive !is null ); 1265 assert( parent ); 1266 assert( parent.isDir ); 1267 assert( entry ); 1268 assert( entry.isFile ); 1269 assert( parent.dir.children[entry.name] is entry ); 1270 } 1271 out { assert( valid ); } 1272 body 1273 { 1274 this.parent = parent; 1275 this.archive = archive; 1276 this.entry = entry; 1277 this.name_ = null; 1278 } 1279 1280 final void reset(ZipFolder archive, Entry* parent, const(char)[] name) 1281 in 1282 { 1283 assert( archive !is null ); 1284 assert( parent ); 1285 assert( parent.isDir ); 1286 assert( name.nz() ); 1287 assert( !(name in parent.dir.children) ); 1288 } 1289 out { assert( valid ); } 1290 body 1291 { 1292 this.archive = archive; 1293 this.parent = parent; 1294 this.entry = null; 1295 this.name_ = name; 1296 } 1297 1298 final void close() 1299 in { assert( valid ); } 1300 out { assert( !valid ); } 1301 body 1302 { 1303 archive = null; 1304 parent = null; 1305 entry = null; 1306 name_ = null; 1307 } 1308 } 1309 1310 // ************************************************************************ // 1311 // ************************************************************************ // 1312 1313 class ZipSubFolderEntry : VfsFolderEntry 1314 { 1315 final VfsFolder open() 1316 in { assert( valid ); } 1317 body 1318 { 1319 auto entry = (name in parent.dir.children); 1320 if( entry ) 1321 return new ZipSubFolder(archive, *entry); 1322 1323 else 1324 { 1325 // NOTE: this can be called with a multi-part path. 1326 error("ZipSubFolderEntry.open: \"" 1327 ~ parent.fullname ~ "/" ~ name 1328 ~ "\" does not exist"); 1329 1330 assert(false); 1331 } 1332 } 1333 1334 final VfsFolder create() 1335 in { assert( valid ); } 1336 body 1337 { 1338 version( ZipFolder_NonMutating ) 1339 { 1340 // TODO: different exception if folder exists (this operation is 1341 // currently invalid either way...) 1342 mutate_error("ZipSubFolderEntry.create"); 1343 assert(false); 1344 } 1345 else 1346 { 1347 // MUTATE 1348 enforce_mutable(); 1349 1350 // If the folder exists, we can't really create it, now can we? 1351 if( this.exists ) 1352 error("ZipSubFolderEntry.create: cannot create folder that already " 1353 "exists, and believe me, I *tried*"); 1354 1355 // Ok, I suppose I can do this for ya... 1356 auto entry = new Entry; 1357 entry.type = EntryType.Dir; 1358 entry.fullname = parent.fullname.dir_app(name); 1359 entry.name = entry.fullname[$-name.length..$]; 1360 entry.makeVfsInfo(); 1361 1362 assert( !(entry.name in parent.dir.children) ); 1363 parent.dir.children[entry.name] = entry; 1364 mutate(); 1365 1366 // Done 1367 return new ZipSubFolder(archive, entry); 1368 } 1369 } 1370 1371 @property final bool exists() 1372 in { assert( valid ); } 1373 body 1374 { 1375 return !!(name in parent.dir.children); 1376 } 1377 1378 private: 1379 ZipFolder archive; 1380 Entry* parent; 1381 const(char)[] name; 1382 1383 this(ZipFolder archive, Entry* parent, const(char)[] name) 1384 in 1385 { 1386 assert( archive !is null ); 1387 assert( parent.isDir ); 1388 assert( name.nz() ); 1389 assert( name.single_path_part() ); 1390 } 1391 out { assert( valid ); } 1392 body 1393 { 1394 this.archive = archive; 1395 this.parent = parent; 1396 this.name = name; 1397 } 1398 1399 @property final bool valid() 1400 { 1401 return (archive !is null) && !archive.closed; 1402 } 1403 1404 final void enforce_mutable() 1405 in { assert( valid ); } 1406 body 1407 { 1408 if( archive.readonly ) 1409 // TODO: exception 1410 throw new Exception("cannot mutate a read-only Zip archive"); 1411 } 1412 1413 final void mutate() 1414 in { assert( valid ); } 1415 body 1416 { 1417 enforce_mutable(); 1418 archive.modified = true; 1419 } 1420 } 1421 1422 // ************************************************************************ // 1423 // ************************************************************************ // 1424 1425 class ZipSubFolderGroup : VfsFolders 1426 { 1427 final int opApply(scope int delegate(ref VfsFolder) dg) 1428 in { assert( valid ); } 1429 body 1430 { 1431 int result = 0; 1432 1433 foreach( folder ; members ) 1434 { 1435 VfsFolder x = folder; 1436 if( (result = dg(x)) != 0 ) 1437 break; 1438 } 1439 1440 return result; 1441 } 1442 1443 @property final size_t files() 1444 in { assert( valid ); } 1445 body 1446 { 1447 uint files = 0; 1448 1449 foreach( folder ; members ) 1450 files += folder.stats.files; 1451 1452 return files; 1453 } 1454 1455 @property final size_t folders() 1456 in { assert( valid ); } 1457 body 1458 { 1459 return members.length; 1460 } 1461 1462 @property final size_t entries() 1463 in { assert( valid ); } 1464 body 1465 { 1466 return files + folders; 1467 } 1468 1469 @property final ulong bytes() 1470 in { assert( valid ); } 1471 body 1472 { 1473 ulong bytes = 0; 1474 1475 foreach( folder ; members ) 1476 bytes += folder.stats.bytes; 1477 1478 return bytes; 1479 } 1480 1481 final VfsFolders subset(const(char)[] pattern) 1482 in { assert( valid ); } 1483 body 1484 { 1485 ZipSubFolder[] set; 1486 1487 foreach( folder ; members ) 1488 if( Path.patternMatch(folder.name, pattern) ) 1489 set ~= folder; 1490 1491 return new ZipSubFolderGroup(archive, set); 1492 } 1493 1494 @property final VfsFiles catalog(const(char)[] pattern) 1495 in { assert( valid ); } 1496 body 1497 { 1498 bool filter (VfsInfo info) 1499 { 1500 return Path.patternMatch(info.name, pattern); 1501 } 1502 1503 return catalog (&filter); 1504 } 1505 1506 @property final VfsFiles catalog(VfsFilter filter = null) 1507 in { assert( valid ); } 1508 body 1509 { 1510 return new ZipFileGroup(archive, this, filter); 1511 } 1512 1513 private: 1514 ZipFolder archive; 1515 ZipSubFolder[] members; 1516 1517 this(ZipFolder archive, ZipSubFolder root, bool recurse) 1518 out { assert( valid ); } 1519 body 1520 { 1521 this.archive = archive; 1522 members = root ~ scan(root, recurse); 1523 } 1524 1525 this(ZipFolder archive, ZipSubFolder[] members) 1526 out { assert( valid ); } 1527 body 1528 { 1529 this.archive = archive; 1530 this.members = members; 1531 } 1532 1533 @property final bool valid() 1534 { 1535 return (archive !is null) && !archive.closed; 1536 } 1537 1538 final ZipSubFolder[] scan(ZipSubFolder root, bool recurse) 1539 in { assert( valid ); } 1540 body 1541 { 1542 auto folders = root.folders(recurse); 1543 1544 if( recurse ) 1545 foreach( child ; folders ) 1546 folders ~= scan(child, recurse); 1547 1548 return folders; 1549 } 1550 } 1551 1552 // ************************************************************************ // 1553 // ************************************************************************ // 1554 1555 class ZipFileGroup : VfsFiles 1556 { 1557 final int opApply(scope int delegate(ref VfsFile) dg) 1558 in { assert( valid ); } 1559 body 1560 { 1561 int result = 0; 1562 auto file = new ZipFile; 1563 1564 foreach( entry ; group ) 1565 { 1566 file.reset(archive,entry.parent,entry.entry); 1567 VfsFile x = file; 1568 if( (result = dg(x)) != 0 ) 1569 break; 1570 } 1571 1572 return result; 1573 } 1574 1575 @property final size_t files() 1576 in { assert( valid ); } 1577 body 1578 { 1579 return group.length; 1580 } 1581 1582 @property final ulong bytes() 1583 in { assert( valid ); } 1584 body 1585 { 1586 return stats.bytes; 1587 } 1588 1589 private: 1590 ZipFolder archive; 1591 FileEntry[] group; 1592 VfsStats stats; 1593 1594 struct FileEntry 1595 { 1596 Entry* parent; 1597 Entry* entry; 1598 } 1599 1600 this(ZipFolder archive, ZipSubFolderGroup host, VfsFilter filter) 1601 out { assert( valid ); } 1602 body 1603 { 1604 this.archive = archive; 1605 foreach( folder ; host.members ) 1606 foreach( file ; folder.files(stats, filter) ) 1607 group ~= FileEntry(folder.entry, file); 1608 } 1609 1610 @property final bool valid() 1611 { 1612 return (archive !is null) && !archive.closed; 1613 } 1614 } 1615 1616 // ************************************************************************ // 1617 // ************************************************************************ // 1618 1619 private: 1620 1621 void error(const(char)[] msg) 1622 { 1623 throw new Exception(msg.idup); 1624 } 1625 1626 void mutate_error(const(char)[] method) 1627 { 1628 error(method ~ ": mutating the contents of a ZipFolder " 1629 "is not supported yet; terribly sorry"); 1630 } 1631 1632 bool nz(const(char)[] s) 1633 { 1634 return s.length > 0; 1635 } 1636 1637 bool zero(const(char)[] s) 1638 { 1639 return s.length == 0; 1640 } 1641 1642 bool single_path_part(const(char)[] s) 1643 { 1644 foreach( c ; s ) 1645 if( c == '/' ) return false; 1646 return true; 1647 } 1648 1649 char[] dir_app(const(char)[] dir, const(char)[] name) 1650 { 1651 return dir ~ (dir[$-1]!='/' ? "/" : "") ~ name; 1652 } 1653 1654 inout(char)[] headTail(inout(char)[] path, out inout(char)[] head, out inout(char)[] tail) 1655 { 1656 foreach( i,dchar c ; path[1..$] ) 1657 if( c == '/' ) 1658 { 1659 head = path[0..i+1]; 1660 tail = path[i+2..$]; 1661 return head; 1662 } 1663 1664 head = path; 1665 tail = null; 1666 return head; 1667 } 1668 1669 debug (UnitTest) 1670 { 1671 unittest 1672 { 1673 const(char)[] h,t; 1674 1675 headTail("/a/b/c", h, t); 1676 assert( h == "/a" ); 1677 assert( t == "b/c" ); 1678 1679 headTail("a/b/c", h, t); 1680 assert( h == "a" ); 1681 assert( t == "b/c" ); 1682 1683 headTail("a/", h, t); 1684 assert( h == "a" ); 1685 assert( t == "" ); 1686 1687 headTail("a", h, t); 1688 assert( h == "a" ); 1689 assert( t == "" ); 1690 } 1691 } 1692 1693 // ************************************************************************** // 1694 // ************************************************************************** // 1695 // ************************************************************************** // 1696 1697 // Dependencies 1698 private: 1699 import tango.io.device.Conduit : Conduit; 1700 1701 /******************************************************************************* 1702 1703 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 1704 1705 license: BSD style: $(LICENSE) 1706 1707 version: Prerelease 1708 1709 author: Daniel Keep 1710 1711 *******************************************************************************/ 1712 1713 //module tangox.io.stream.DummyStream; 1714 1715 1716 1717 //import tango.io.device.Conduit : Conduit; 1718 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 1719 1720 /** 1721 * The dummy stream classes are used to provide simple, empty stream objects 1722 * where one is required, but none is available. 1723 * 1724 * Note that, currently, these classes return 'null' for the underlying 1725 * conduit, which will likely break code which expects streams to have an 1726 * underlying conduit. 1727 */ 1728 private deprecated class DummyInputStream : InputStream // IConduit.Seek 1729 { 1730 //alias IConduit.Seek.Anchor Anchor; 1731 1732 override InputStream input() {return null;} 1733 override IConduit conduit() { return null; } 1734 override void close() {} 1735 override size_t read(void[] dst) { return IConduit.Eof; } 1736 override InputStream flush() { return this; } 1737 override void[] load(size_t max=-1) 1738 { 1739 return Conduit.load(this, max); 1740 } 1741 override long seek(long offset, Anchor anchor = cast(Anchor)0) { return 0; } 1742 } 1743 1744 /// ditto 1745 private deprecated class DummyOutputStream : OutputStream //, IConduit.Seek 1746 { 1747 //alias IConduit.Seek.Anchor Anchor; 1748 1749 override OutputStream output() {return null;} 1750 override IConduit conduit() { return null; } 1751 override void close() {} 1752 override size_t write(const(void)[] src) { return IConduit.Eof; } 1753 override OutputStream copy(InputStream src, size_t max=-1) 1754 { 1755 Conduit.transfer(src, this, max); 1756 return this; 1757 } 1758 override OutputStream flush() { return this; } 1759 override long seek(long offset, Anchor anchor = cast(Anchor)0) { return 0; } 1760 } 1761 1762 /******************************************************************************* 1763 1764 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 1765 1766 license: BSD style: $(LICENSE) 1767 1768 version: Prerelease 1769 1770 author: Daniel Keep 1771 1772 *******************************************************************************/ 1773 1774 //module tangox.io.stream.EventStream; 1775 1776 //import tango.io.device.Conduit : Conduit; 1777 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 1778 1779 /** 1780 * The event stream classes are designed to allow you to receive feedback on 1781 * how a stream chain is being used. This is done through the use of 1782 * delegate callbacks which are invoked just before the associated method is 1783 * complete. 1784 */ 1785 class EventSeekInputStream : InputStream //, IConduit.Seek 1786 { 1787 /// 1788 struct Callbacks 1789 { 1790 void delegate() close; /// 1791 void delegate() clear; /// 1792 void delegate(size_t, void[]) read; /// 1793 void delegate(long, long, Anchor) seek; /// 1794 } 1795 1796 //alias IConduit.Seek.Anchor Anchor; 1797 1798 /// 1799 this(InputStream source, Callbacks callbacks) 1800 in 1801 { 1802 assert( source !is null ); 1803 assert( (cast(IConduit.Seek) source.conduit) !is null ); 1804 } 1805 body 1806 { 1807 this.source = source; 1808 this.seeker = source; //cast(IConduit.Seek) source; 1809 this.callbacks = callbacks; 1810 } 1811 1812 override IConduit conduit() 1813 { 1814 return source.conduit; 1815 } 1816 1817 InputStream input() 1818 { 1819 return source; 1820 } 1821 1822 override void close() 1823 { 1824 source.close(); 1825 source = null; 1826 seeker = null; 1827 if( callbacks.close ) callbacks.close(); 1828 } 1829 1830 override size_t read(void[] dst) 1831 { 1832 auto result = source.read(dst); 1833 if( callbacks.read ) callbacks.read(result, dst); 1834 return result; 1835 } 1836 1837 override InputStream flush() 1838 { 1839 source.flush(); 1840 if( callbacks.clear ) callbacks.clear(); 1841 return this; 1842 } 1843 1844 override void[] load(size_t max=-1) 1845 { 1846 return Conduit.load(this, max); 1847 } 1848 1849 override long seek(long offset, Anchor anchor = cast(Anchor)0) 1850 { 1851 auto result = seeker.seek(offset, anchor); 1852 if( callbacks.seek ) callbacks.seek(result, offset, anchor); 1853 return result; 1854 } 1855 1856 private: 1857 InputStream source; 1858 InputStream seeker; //IConduit.Seek seeker; 1859 Callbacks callbacks; 1860 1861 invariant() 1862 { 1863 assert( cast(Object) source is cast(Object) seeker ); 1864 } 1865 } 1866 1867 /// ditto 1868 class EventSeekOutputStream : OutputStream //, IConduit.Seek 1869 { 1870 /// 1871 struct Callbacks 1872 { 1873 void delegate() close; /// 1874 void delegate() flush; /// 1875 void delegate(size_t, const(void)[]) write; /// 1876 void delegate(long, long, Anchor) seek; /// 1877 } 1878 1879 //alias IConduit.Seek.Anchor Anchor; 1880 1881 /// 1882 this(OutputStream source, Callbacks callbacks) 1883 in 1884 { 1885 assert( source !is null ); 1886 assert( (cast(IConduit.Seek) source.conduit) !is null ); 1887 } 1888 body 1889 { 1890 this.source = source; 1891 this.seeker = source; //cast(IConduit.Seek) source; 1892 this.callbacks = callbacks; 1893 } 1894 1895 override IConduit conduit() 1896 { 1897 return source.conduit; 1898 } 1899 1900 override OutputStream output() 1901 { 1902 return source; 1903 } 1904 1905 override void close() 1906 { 1907 source.close(); 1908 source = null; 1909 seeker = null; 1910 if( callbacks.close ) callbacks.close(); 1911 } 1912 1913 override size_t write(const(void)[] dst) 1914 { 1915 auto result = source.write(dst); 1916 if( callbacks.write ) callbacks.write(result, dst); 1917 return result; 1918 } 1919 1920 override OutputStream flush() 1921 { 1922 source.flush(); 1923 if( callbacks.flush ) callbacks.flush(); 1924 return this; 1925 } 1926 1927 override long seek(long offset, Anchor anchor = cast(Anchor)0) 1928 { 1929 auto result = seeker.seek(offset, anchor); 1930 if( callbacks.seek ) callbacks.seek(result, offset, anchor); 1931 return result; 1932 } 1933 1934 override OutputStream copy(InputStream src, size_t max=-1) 1935 { 1936 Conduit.transfer(src, this, max); 1937 return this; 1938 } 1939 1940 private: 1941 OutputStream source; 1942 OutputStream seeker; //IConduit.Seek seeker; 1943 Callbacks callbacks; 1944 1945 invariant() 1946 { 1947 assert( cast(Object) source is cast(Object) seeker ); 1948 } 1949 } 1950 1951 /******************************************************************************* 1952 1953 copyright: Copyright © 2007 Daniel Keep. All rights reserved. 1954 1955 license: BSD style: $(LICENSE) 1956 1957 version: Prerelease 1958 1959 author: Daniel Keep 1960 *******************************************************************************/ 1961 1962 1963 //module tangox.io.stream.WrapStream; 1964 1965 1966 1967 //import tango.io.device.Conduit : Conduit; 1968 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream; 1969 1970 /** 1971 * This stream can be used to provide access to another stream. 1972 * Its distinguishing feature is that users cannot.close() the underlying 1973 * stream. 1974 * 1975 * This stream fully supports seeking, and as such requires that the 1976 * underlying stream also support seeking. 1977 */ 1978 class WrapSeekInputStream : InputStream //, IConduit.Seek 1979 { 1980 //alias IConduit.Seek.Anchor Anchor; 1981 1982 /** 1983 * Create a new wrap stream from the given source. 1984 */ 1985 this(InputStream source) 1986 in 1987 { 1988 assert( source !is null ); 1989 assert( (cast(IConduit.Seek) source.conduit) !is null ); 1990 } 1991 body 1992 { 1993 this.source = source; 1994 this.seeker = source; //cast(IConduit.Seek) source; 1995 this._position = seeker.seek(0, Anchor.Current); 1996 } 1997 1998 /// ditto 1999 this(InputStream source, long position) 2000 in 2001 { 2002 assert( position >= 0 ); 2003 } 2004 body 2005 { 2006 this(source); 2007 this._position = position; 2008 } 2009 2010 override IConduit conduit() 2011 { 2012 return source.conduit; 2013 } 2014 2015 InputStream input() 2016 { 2017 return source; 2018 } 2019 2020 override void close() 2021 { 2022 source = null; 2023 seeker = null; 2024 } 2025 2026 override size_t read(void[] dst) 2027 { 2028 if( seeker.seek(0, Anchor.Current) != _position ) 2029 seeker.seek(_position, Anchor.Begin); 2030 2031 auto read = source.read(dst); 2032 if( read != IConduit.Eof ) 2033 _position += read; 2034 2035 return read; 2036 } 2037 2038 override InputStream flush() 2039 { 2040 source.flush(); 2041 return this; 2042 } 2043 2044 override void[] load(size_t max=-1) 2045 { 2046 return Conduit.load(this, max); 2047 } 2048 2049 override long seek(long offset, Anchor anchor = cast(Anchor)0) 2050 { 2051 seeker.seek(_position, Anchor.Begin); 2052 return (_position = seeker.seek(offset, anchor)); 2053 } 2054 2055 private: 2056 InputStream source; 2057 InputStream seeker; //IConduit.Seek seeker; 2058 long _position; 2059 2060 invariant() 2061 { 2062 assert( cast(Object) source is cast(Object) seeker ); 2063 assert( _position >= 0 ); 2064 } 2065 } 2066 2067 /** 2068 * This stream can be used to provide access to another stream. 2069 * Its distinguishing feature is that the users cannot.close() the underlying 2070 * stream. 2071 * 2072 * This stream fully supports seeking, and as such requires that the 2073 * underlying stream also support seeking. 2074 */ 2075 class WrapSeekOutputStream : OutputStream//, IConduit.Seek 2076 { 2077 //alias IConduit.Seek.Anchor Anchor; 2078 2079 /** 2080 * Create a new wrap stream from the given source. 2081 */ 2082 this(OutputStream source) 2083 in 2084 { 2085 assert( (cast(IConduit.Seek) source.conduit) !is null ); 2086 } 2087 body 2088 { 2089 this.source = source; 2090 this.seeker = source; //cast(IConduit.Seek) source; 2091 this._position = seeker.seek(0, Anchor.Current); 2092 } 2093 2094 /// ditto 2095 this(OutputStream source, long position) 2096 in 2097 { 2098 assert( position >= 0 ); 2099 } 2100 body 2101 { 2102 this(source); 2103 this._position = position; 2104 } 2105 2106 override IConduit conduit() 2107 { 2108 return source.conduit; 2109 } 2110 2111 override OutputStream output() 2112 { 2113 return source; 2114 } 2115 2116 override void close() 2117 { 2118 source = null; 2119 seeker = null; 2120 } 2121 2122 size_t write(const(void)[] src) 2123 { 2124 if( seeker.seek(0, Anchor.Current) != _position ) 2125 seeker.seek(_position, Anchor.Begin); 2126 2127 auto wrote = source.write(src); 2128 if( wrote != IConduit.Eof ) 2129 _position += wrote; 2130 return wrote; 2131 } 2132 2133 override OutputStream copy(InputStream src, size_t max=-1) 2134 { 2135 Conduit.transfer(src, this, max); 2136 return this; 2137 } 2138 2139 override OutputStream flush() 2140 { 2141 source.flush(); 2142 return this; 2143 } 2144 2145 override long seek(long offset, Anchor anchor = cast(Anchor)0) 2146 { 2147 seeker.seek(_position, Anchor.Begin); 2148 return (_position = seeker.seek(offset, anchor)); 2149 } 2150 2151 private: 2152 OutputStream source; 2153 OutputStream seeker; //IConduit.Seek seeker; 2154 long _position; 2155 2156 invariant() 2157 { 2158 assert( cast(Object) source is cast(Object) seeker ); 2159 assert( _position >= 0 ); 2160 } 2161 } 2162 2163