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