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