libtango (0.99.9.dfsg-1) 02_compress.diff

Summary

 tango/io/compress/BzipStream.d |  616 --------
 tango/io/compress/Zip.d        | 2976 -----------------------------------------
 tango/io/compress/ZlibStream.d |  971 -------------
 tango/io/vfs/ZipFolder.d       | 2152 -----------------------------
 4 files changed, 6715 deletions(-)

    
download this patch

Patch contents

--- a/tango/io/compress/BzipStream.d
+++ /dev/null
@@ -1,616 +0,0 @@
-/*******************************************************************************
-
-    copyright:  Copyright (C) 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Initial release: July 2007
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-module tango.io.compress.BzipStream;
-
-private import tango.io.compress.c.bzlib;
-
-private import tango.core.Exception : IOException;
-
-private import tango.io.device.Conduit : InputFilter, OutputFilter;
-
-private import tango.io.model.IConduit : InputStream, OutputStream, IConduit;
-
-private
-{
-    /* This constant controls the size of the input/output buffers we use
-     * internally.  There's no particular reason to pick this size.  It might
-     * be an idea to run some benchmarks to work out what a good number is.
-     */
-    private enum { BUFFER_SIZE = 4*1024 };
-
-    private enum { DEFAULT_BLOCKSIZE = 9 };
-    private enum { DEFAULT_WORKFACTOR = 0 };
-}
-
-/*******************************************************************************
-  
-    This output filter can be used to perform compression of data into a bzip2
-    stream.
-
-*******************************************************************************/
-
-class BzipOutput : OutputFilter
-{
-    /***************************************************************************
-
-        This enumeration represents several pre-defined compression block
-        sizes, measured in hundreds of kilobytes.  See the documentation for
-        the BzipOutput class' constructor for more details.
-
-    ***************************************************************************/
-
-    enum BlockSize : int
-    {
-        Normal = 9,
-        Fast = 1,
-        Best = 9,
-    }
-
-    private
-    {
-        bool bzs_valid = false;
-        bz_stream bzs;
-        ubyte[] out_chunk;
-        size_t _written = 0;
-    }
-
-    /***************************************************************************
-
-        Constructs a new bzip2 compression filter.  You need to pass in the
-        stream that the compression filter will write to.  If you are using
-        this filter with a conduit, the idiom to use is:
-
-        ---
-        auto output = new BzipOutput(myConduit.output);
-        output.write(myContent);
-        ---
-
-        blockSize relates to the size of the window bzip2 uses when
-        compressing data and determines how much memory is required to
-        decompress a stream.  It is measured in hundreds of kilobytes.
-        
-        ccording to the bzip2 documentation, there is no dramatic difference
-        between the various block sizes, so the default should suffice in most
-        cases.
-
-        BlockSize.Normal (the default) is the same as BlockSize.Best
-        (or 9).  The blockSize may be any integer between 1 and 9 inclusive.
-
-    ***************************************************************************/
-
-    this(OutputStream stream, int blockSize = BlockSize.Normal)
-    {
-        init(stream, blockSize);
-        scope(failure) kill_bzs();
-
-        super(stream);
-        out_chunk = new ubyte[BUFFER_SIZE];
-    }
-
-    /*
-     * This method performs initialisation for the stream.  Note that this may
-     * be called more than once for an instance, provided the instance is
-     * either new or as part of a call to reset.
-     */
-    private void init(OutputStream stream, int blockSize)
-    {
-        if( blockSize < 1 || blockSize > 9 )
-            throw new BzipException("bzip2 block size must be between"
-                    " 1 and 9");
-
-        auto ret = BZ2_bzCompressInit(&bzs, blockSize, 0, DEFAULT_WORKFACTOR);
-        if( ret != BZ_OK )
-            throw new BzipException(ret);
-
-        bzs_valid = true;
-    }
-
-    ~this()
-    {
-        if( bzs_valid )
-            kill_bzs();
-    }
-
-    /***************************************************************************
-        
-        Resets and re-initialises this instance.
-
-        If you are creating compression streams inside a loop, you may wish to
-        use this method to re-use a single instance.  This prevents the
-        potentially costly re-allocation of internal buffers.
-
-        The stream must have already been closed before calling reset.
-
-    ***************************************************************************/ 
-
-    void reset(OutputStream stream, int blockSize = BlockSize.Normal)
-    {
-        // If the stream is still valid, bail.
-        if( bzs_valid )
-            throw new BzipStillOpenException;
-
-        init(stream, blockSize);
-    }
-
-    /***************************************************************************
-
-        Compresses the given data to the underlying conduit.
-
-        Returns the number of bytes from src that were compressed, which may
-        be less than given.
-
-    ***************************************************************************/
-
-    size_t write(void[] src)
-    {
-        check_valid();
-        scope(failure) kill_bzs();
-
-        bzs.avail_in = src.length;
-        bzs.next_in = cast(ubyte*)src.ptr;
-
-        do
-        {
-            bzs.avail_out = out_chunk.length;
-            bzs.next_out = out_chunk.ptr;
-
-            auto ret = BZ2_bzCompress(&bzs, BZ_RUN);
-            if( ret != BZ_RUN_OK )
-                throw new BzipException(ret);
-
-            // Push the compressed bytes out to the stream, until it's either
-            // written them all, or choked.
-            auto have = out_chunk.length-bzs.avail_out;
-            auto out_buffer = out_chunk[0..have];
-            do
-            {
-                auto w = sink.write(out_buffer);
-                if( w == IConduit.Eof )
-                    return w;
-
-                out_buffer = out_buffer[w..$];
-                _written += w;
-            }
-            while( out_buffer.length > 0 );
-        }
-        // Loop while we are still using up the whole output buffer
-        while( bzs.avail_out == 0 );
-
-        assert( bzs.avail_in == 0, "failed to compress all provided data" );
-
-        return src.length;
-    }
-
-    /***************************************************************************
-
-        This read-only property returns the number of compressed bytes that
-        have been written to the underlying stream.  Following a call to
-        either close or commit, this will contain the total compressed size of
-        the input data stream.
-
-    ***************************************************************************/
-
-    size_t written()
-    {
-        return _written;
-    }
-
-    /***************************************************************************
-
-        Close the compression stream.  This will cause any buffered content to
-        be committed to the underlying stream.
-
-    ***************************************************************************/
-
-    void close()
-    {
-        if( bzs_valid ) commit;
-        super.close;
-    }
-
-    /***************************************************************************
-
-        Purge any buffered content.  Calling this will implicitly end the
-        bzip2 stream, so it should not be called until you are finished
-        compressing data.  Any calls to either write or commit after a
-        compression filter has been committed will throw an exception.
-
-        The only difference between calling this method and calling close is
-        that the underlying stream will not be closed.
-
-    ***************************************************************************/
-
-    void commit()
-    {
-        check_valid();
-        scope(failure) kill_bzs();
-
-        bzs.avail_in = 0;
-        bzs.next_in = null;
-
-        bool finished = false;
-
-        do
-        {
-            bzs.avail_out = out_chunk.length;
-            bzs.next_out = out_chunk.ptr;
-
-            auto ret = BZ2_bzCompress(&bzs, BZ_FINISH);
-            switch( ret )
-            {
-                case BZ_FINISH_OK:
-                    break;
-
-                case BZ_STREAM_END:
-                    finished = true;
-                    break;
-
-                default:
-                    throw new BzipException(ret);
-            }
-
-            auto have = out_chunk.length - bzs.avail_out;
-            auto out_buffer = out_chunk[0..have];
-            if( have > 0 )
-            {
-                do
-                {
-                    auto w = sink.write(out_buffer);
-                    if( w == IConduit.Eof )
-                        return;
-
-                    out_buffer = out_buffer[w..$];
-                    _written += w;
-                }
-                while( out_buffer.length > 0 );
-            }
-        }
-        while( !finished );
-
-        kill_bzs();
-    }
-
-    // Disable seeking
-    override long seek(long offset, Anchor anchor = Anchor.Begin)
-    {
-        throw new IOException("BzipOutput does not support seek requests");
-    }
-
-    // This function kills the stream: it deallocates the internal state, and
-    // unsets the bzs_valid flag.
-    private void kill_bzs()
-    {
-        check_valid();
-
-        BZ2_bzCompressEnd(&bzs);
-        bzs_valid = false;
-    }
-
-    // Asserts that the stream is still valid and usable (except that this
-    // check doesn't get elided with -release).
-    private void check_valid()
-    {
-        if( !bzs_valid )
-            throw new BzipClosedException;
-    }
-}
-
-/*******************************************************************************
-  
-    This input filter can be used to perform decompression of bzip2 streams.
-
-*******************************************************************************/
-
-class BzipInput : InputFilter
-{
-    private
-    {
-        bool bzs_valid = false;
-        bz_stream bzs;
-        ubyte[] in_chunk;
-    }
-
-    /***************************************************************************
-
-        Constructs a new bzip2 decompression filter.  You need to pass in the
-        stream that the decompression filter will read from.  If you are using
-        this filter with a conduit, the idiom to use is:
-
-        ---
-        auto input = new BzipInput(myConduit.input);
-        input.read(myContent);
-        ---
-
-        The small argument, if set to true, instructs bzip2 to perform
-        decompression using half the regular amount of memory, at the cost of
-        running at half speed.
-
-    ***************************************************************************/
-
-    this(InputStream stream, bool small=false)
-    {
-        init(stream, small);
-        scope(failure) kill_bzs();
-
-        super(stream);
-        in_chunk = new ubyte[BUFFER_SIZE];
-    }
-
-    /*
-     * This method performs initialisation for the stream.  Note that this may
-     * be called more than once for an instance, provided the instance is
-     * either new or as part of a call to reset.
-     */
-    private void init(InputStream stream, bool small)
-    {
-        auto ret = BZ2_bzDecompressInit(&bzs, 0, small?1:0);
-        if( ret != BZ_OK )
-            throw new BzipException(ret);
-
-        bzs_valid = true;
-    }
-
-    ~this()
-    {
-        if( bzs_valid )
-            kill_bzs();
-    }
-
-    /***************************************************************************
-        
-        Resets and re-initialises this instance.
-
-        If you are creating compression streams inside a loop, you may wish to
-        use this method to re-use a single instance.  This prevents the
-        potentially costly re-allocation of internal buffers.
-
-        The stream must have already been closed before calling reset.
-
-    ***************************************************************************/ 
-
-    void reset(InputStream stream, bool small=false)
-    {
-        // If the stream is still valid, bail.
-        if( bzs_valid )
-            throw new BzipStillOpenException;
-
-        init(stream, small);
-    }
-
-    /***************************************************************************
-
-        Decompresses data from the underlying conduit into a target array.
-
-        Returns the number of bytes stored into dst, which may be less than
-        requested.
-
-    ***************************************************************************/ 
-
-    size_t read(void[] dst)
-    {
-        if( !bzs_valid )
-            return IConduit.Eof;
-
-        scope(failure) kill_bzs();
-
-        bool finished = false;
-
-        bzs.avail_out = dst.length;
-        bzs.next_out = cast(ubyte*)dst.ptr;
-
-        do
-        {
-            if( bzs.avail_in == 0 )
-            {
-                auto len = source.read(in_chunk);
-                if( len == IConduit.Eof )
-                    return IConduit.Eof;
-
-                bzs.avail_in = len;
-                bzs.next_in = in_chunk.ptr;
-            }
-
-            auto ret = BZ2_bzDecompress(&bzs);
-            if( ret == BZ_STREAM_END )
-            {
-                kill_bzs();
-                finished = true;
-            }
-            else if( ret != BZ_OK )
-                throw new BzipException(ret);
-        }
-        while( !finished && bzs.avail_out > 0 );
-
-        return dst.length - bzs.avail_out;
-    }
-
-    /***************************************************************************
-
-        Closes the compression stream.
-
-    ***************************************************************************/ 
-
-    override void close()
-    {
-        check_valid();
-
-        // Kill the stream.  Don't deallocate the buffer since the user may
-        // yet reset the stream.
-        kill_bzs();
-        super.close();
-    }
-
-    // Disable seeking
-    override long seek(long offset, Anchor anchor = Anchor.Begin)
-    {
-        throw new IOException("BzipOutput does not support seek requests");
-    }
-
-    // This function kills the stream: it deallocates the internal state, and
-    // unsets the bzs_valid flag.
-    private void kill_bzs()
-    {
-        check_valid();
-
-        BZ2_bzDecompressEnd(&bzs);
-        bzs_valid = false;
-    }
-
-    // Asserts that the stream is still valid and usable (except that this
-    // check doesn't get elided with -release).
-    private void check_valid()
-    {
-        if( !bzs_valid )
-            throw new BzipClosedException;
-    }
-}
-
-/*******************************************************************************
-  
-    This exception is thrown when an error occurs in the underlying bzip2
-    library.
-
-*******************************************************************************/
-
-class BzipException : IOException
-{
-    this(in int code)
-    {
-        super(codeName(code));
-    }
-
-    this(char[] msg)
-    {
-        super(msg);
-    }
-
-    private char[] codeName(in int code)
-    {
-        char[] name;
-
-        switch( code )
-        {
-            case BZ_OK:                 name = "BZ_OK";                 break;
-            case BZ_RUN_OK:             name = "BZ_RUN_OK";             break;
-            case BZ_FLUSH_OK:           name = "BZ_FLUSH_OK";           break;
-            case BZ_STREAM_END:         name = "BZ_STREAM_END";         break;
-            case BZ_SEQUENCE_ERROR:     name = "BZ_SEQUENCE_ERROR";     break;
-            case BZ_PARAM_ERROR:        name = "BZ_PARAM_ERROR";        break;
-            case BZ_MEM_ERROR:          name = "BZ_MEM_ERROR";          break;
-            case BZ_DATA_ERROR:         name = "BZ_DATA_ERROR";         break;
-            case BZ_DATA_ERROR_MAGIC:   name = "BZ_DATA_ERROR_MAGIC";   break;
-            case BZ_IO_ERROR:           name = "BZ_IO_ERROR";           break;
-            case BZ_UNEXPECTED_EOF:     name = "BZ_UNEXPECTED_EOF";     break;
-            case BZ_OUTBUFF_FULL:       name = "BZ_OUTBUFF_FULL";       break;
-            case BZ_CONFIG_ERROR:       name = "BZ_CONFIG_ERROR";       break;
-            default:                    name = "BZ_UNKNOWN";
-        }
-
-        return name;
-    }
-}
-
-/*******************************************************************************
-  
-    This exception is thrown if you attempt to perform a read, write or flush
-    operation on a closed bzip2 filter stream.  This can occur if the input
-    stream has finished, or an output stream was flushed.
-
-*******************************************************************************/
-
-class BzipClosedException : IOException
-{
-    this()
-    {
-        super("cannot operate on closed bzip2 stream");
-    }
-}
-
-/*******************************************************************************
-  
-    This exception is thrown if you attempt to reset a compression stream that
-    is still open.  You must either close or commit a stream before it can be
-    reset.
-
-*******************************************************************************/
-
-class BzipStillOpenException : IOException
-{
-    this()
-    {
-        super("cannot reset an open bzip2 stream");
-    }
-}
-
-/* *****************************************************************************
-
-    This section contains a simple unit test for this module.  It is hidden
-    behind a version statement because it introduces additional dependencies.
-
-***************************************************************************** */
-
-debug(UnitTest):
-
-import tango.io.device.Array : Array;
-
-unittest
-{
-    const char[] message =
-        "All dwarfs are by nature dutiful, serious, literate, obedient "
-        "and thoughtful people whose only minor failing is a tendency, "
-        "after one drink, to rush at enemies screaming \"Arrrrrrgh!\" and "
-        "axing their legs off at the knee.";
-
-    const ubyte[] message_z = [
-        0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26,
-        0x53, 0x59, 0x40, 0x98, 0xbe, 0xaa, 0x00, 0x00,
-        0x16, 0xd5, 0x80, 0x10, 0x00, 0x70, 0x05, 0x20,
-        0x00, 0x3f, 0xef, 0xde, 0xe0, 0x30, 0x00, 0xac,
-        0xd8, 0x8a, 0x3d, 0x34, 0x6a, 0x6d, 0x4c, 0x4f,
-        0x24, 0x31, 0x0d, 0x08, 0x98, 0x9b, 0x48, 0x9a,
-        0x7a, 0x80, 0x00, 0x06, 0xa6, 0xd2, 0xa7, 0xe9,
-        0xaa, 0x37, 0xa8, 0xd4, 0xf5, 0x3f, 0x54, 0x63,
-        0x51, 0xe9, 0x2d, 0x4b, 0x99, 0xe1, 0xcc, 0xca,
-        0xda, 0x75, 0x04, 0x42, 0x14, 0xc8, 0x6a, 0x8e,
-        0x23, 0xc1, 0x3e, 0xb1, 0x8a, 0x16, 0xd2, 0x55,
-        0x9a, 0x3e, 0x56, 0x1a, 0xb1, 0x83, 0x11, 0xa6,
-        0x50, 0x4f, 0xd3, 0xed, 0x21, 0x40, 0xaa, 0xd1,
-        0x95, 0x2c, 0xda, 0xcb, 0xb7, 0x0e, 0xce, 0x65,
-        0xfc, 0x63, 0xf2, 0x88, 0x5b, 0x36, 0xda, 0xf0,
-        0xf5, 0xd2, 0x9c, 0xe6, 0xf1, 0x87, 0x12, 0x87,
-        0xce, 0x56, 0x0c, 0xf5, 0x65, 0x4d, 0x2e, 0xd6,
-        0x27, 0x61, 0x2b, 0x74, 0xcd, 0x5e, 0x3b, 0x02,
-        0x42, 0x4e, 0x0b, 0x80, 0xa8, 0x70, 0x04, 0x48,
-        0xfb, 0x93, 0x4c, 0x41, 0xa8, 0x2a, 0xdf, 0xf2,
-        0x67, 0x37, 0x28, 0xad, 0x38, 0xd4, 0x5c, 0xd6,
-        0x34, 0x8b, 0x49, 0x5e, 0x90, 0xb2, 0x06, 0xce,
-        0x0a, 0x83, 0x29, 0x84, 0x20, 0xd7, 0x5f, 0xc5,
-        0xdc, 0x91, 0x4e, 0x14, 0x24, 0x10, 0x26, 0x2f,
-        0xaa, 0x80];
-
-    scope cond = new Array(1024, 1024);
-    scope comp = new BzipOutput(cond);
-    comp.write(message);
-    comp.close;
-
-    assert( comp.written == message_z.length );
-
-    assert( message_z == cast(ubyte[])(cond.slice) );
-
-    scope decomp = new BzipInput(cond);
-    auto buffer = new ubyte[256];
-    buffer = buffer[0 .. decomp.read(buffer)];
-
-    assert( cast(ubyte[])message == buffer );
-}
-
--- a/tango/io/compress/ZlibStream.d
+++ /dev/null
@@ -1,971 +0,0 @@
-/*******************************************************************************
-
-    copyright:  Copyright (C) 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    author:     Daniel Keep
-
-    version:    Feb 08: Added support for different stream encodings, removed
-                        old "window bits" ctors.
-                        
-                Dec 07: Added support for "window bits", needed for Zip support.
-                
-                Jul 07: Initial release.
-
-*******************************************************************************/
-
-module tango.io.compress.ZlibStream;
-
-private import tango.io.compress.c.zlib;
-
-private import tango.stdc.stringz : fromStringz;
-
-private import tango.core.Exception : IOException;
-
-private import tango.io.device.Conduit : InputFilter, OutputFilter;
-
-private import tango.io.model.IConduit : InputStream, OutputStream, IConduit;
-
-private import tango.text.convert.Integer : toString;
-
-
-/* This constant controls the size of the input/output buffers we use
- * internally.  This should be a fairly sane value (it's suggested by the zlib
- * documentation), that should only need changing for memory-constrained
- * platforms/use cases.
- *
- * An alternative would be to make the chunk size a template parameter to the
- * filters themselves, but Tango already has more than enough template
- * parameters getting in the way :)
- */
-
-private enum { CHUNKSIZE = 256 * 1024 };
-
-/* This constant specifies the default windowBits value.  This is taken from
- * documentation in zlib.h.  It shouldn't break anything if zlib changes to
- * a different default.
- */
-
-private enum { WINDOWBITS_DEFAULT = 15 };
-
-/*******************************************************************************
-  
-    This input filter can be used to perform decompression of zlib streams.
-
-*******************************************************************************/
-
-class ZlibInput : InputFilter
-{
-    /***************************************************************************
-    
-        This enumeration allows you to specify the encoding of the compressed
-        stream.
-    
-    ***************************************************************************/
-
-    enum Encoding : int
-    {
-        /**
-         *  The code should attempt to automatically determine what the encoding
-         *  of the stream should be.  Note that this cannot detect the case
-         *  where the stream was compressed with no encoding.
-         */
-        Guess,
-        /**
-         *  Stream has zlib encoding.
-         */
-        Zlib,
-        /**
-         *  Stream has gzip encoding.
-         */
-        Gzip,
-        /**
-         *  Stream has no encoding.
-         */
-        None
-    }
-
-    private
-    {
-        /* Used to make sure we don't try to perform operations on a dead
-         * stream. */
-        bool zs_valid = false;
-
-        z_stream zs;
-        ubyte[] in_chunk;
-    }
-    
-    /***************************************************************************
-
-        Constructs a new zlib decompression filter.  You need to pass in the
-        stream that the decompression filter will read from.  If you are using
-        this filter with a conduit, the idiom to use is:
-
-        ---
-        auto input = new ZlibInput(myConduit.input));
-        input.read(myContent);
-        ---
-
-        The optional windowBits parameter is the base two logarithm of the
-        window size, and should be in the range 8-15, defaulting to 15 if not
-        specified.  Additionally, the windowBits parameter may be negative to
-        indicate that zlib should omit the standard zlib header and trailer,
-        with the window size being -windowBits.
-        
-      Params:
-        stream = compressed input stream.
-        
-        encoding =
-            stream encoding.  Defaults to Encoding.Guess, which
-            should be sufficient unless the stream was compressed with
-            no encoding; in this case, you must manually specify
-            Encoding.None.
-            
-        windowBits =
-            the base two logarithm of the window size, and should be in the
-            range 8-15, defaulting to 15 if not specified.
-
-    ***************************************************************************/
-
-    this(InputStream stream, Encoding encoding,
-            int windowBits = WINDOWBITS_DEFAULT)
-    {
-        init(stream, encoding, windowBits);
-        scope(failure) kill_zs();
-
-        super(stream);
-        in_chunk = new ubyte[CHUNKSIZE];
-    }
-    
-    /// ditto
-    this(InputStream stream)
-    {
-        // DRK 2009-02-26
-        // Removed unique implementation in favour of passing on to another
-        // constructor.  The specific implementation was because the default
-        // value of windowBits is documented in zlib.h, but not actually
-        // exposed.  Using inflateInit over inflateInit2 ensured we would
-        // never get it wrong.  That said, the default value of 15 is REALLY
-        // unlikely to change: values below that aren't terribly useful, and
-        // values higher than 15 are already used for other purposes.
-        // Also, this leads to less code which is always good.  :D
-        this(stream, Encoding.init);
-    }
-
-    /*
-     * This method performs initialisation for the stream.  Note that this may
-     * be called more than once for an instance, provided the instance is
-     * either new or as part of a call to reset.
-     */
-    private void init(InputStream stream, Encoding encoding, int windowBits)
-    {
-        /*
-         * Here's how windowBits works, according to zlib.h:
-         * 
-         * 8 .. 15
-         *      zlib encoding.
-         *      
-         * (8 .. 15) + 16
-         *      gzip encoding.
-         *      
-         * (8 .. 15) + 32
-         *      auto-detect encoding.
-         *      
-         * (8 .. 15) * -1
-         *      raw/no encoding.
-         *      
-         * Since we're going to be playing with the value, we DO care whether
-         * windowBits is in the expected range, so we'll check it.
-         */
-        if( !( 8 <= windowBits && windowBits <= 15 ) )
-        {
-            // No compression for you!
-            throw new ZlibException("invalid windowBits argument"
-                ~ .toString(windowBits));
-        }
-        
-        switch( encoding )
-        {
-        case Encoding.Zlib:
-            // no-op
-            break;
-            
-        case Encoding.Gzip:
-            windowBits += 16;
-            break;
-
-        case Encoding.Guess:
-            windowBits += 32;
-            break;
-            
-        case Encoding.None:
-            windowBits *= -1;
-            break;
-
-        default:
-            assert (false);
-        }
-        
-        // Allocate inflate state
-        with( zs )
-        {
-            zalloc = null;
-            zfree = null;
-            opaque = null;
-            avail_in = 0;
-            next_in = null;
-        }
-
-        auto ret = inflateInit2(&zs, windowBits);
-        if( ret != Z_OK )
-            throw new ZlibException(ret);
-
-        zs_valid = true;
-
-        // Note that this is redundant when init is called from the ctor, but
-        // it is NOT REDUNDANT when called from reset.  source is declared in
-        // InputFilter.
-        //
-        // This code is a wee bit brittle, since if the ctor of InputFilter
-        // changes, this code might break in subtle, hard to find ways.
-        //
-        // See ticket #1837
-        this.source = stream;
-    }
-    
-    ~this()
-    {
-        if( zs_valid )
-            kill_zs();
-    }
-
-    /***************************************************************************
-        
-        Resets and re-initialises this instance.
-
-        If you are creating compression streams inside a loop, you may wish to
-        use this method to re-use a single instance.  This prevents the
-        potentially costly re-allocation of internal buffers.
-
-        The stream must have already been closed before calling reset.
-
-    ***************************************************************************/ 
-
-    void reset(InputStream stream, Encoding encoding,
-            int windowBits = WINDOWBITS_DEFAULT)
-    {
-        // If the stream is still valid, bail.
-        if( zs_valid )
-            throw new ZlibStillOpenException;
-        
-        init(stream, encoding, windowBits);
-    }
-
-    /// ditto
-
-    void reset(InputStream stream)
-    {
-        reset(stream, Encoding.init);
-    }
-
-    /***************************************************************************
-
-        Decompresses data from the underlying conduit into a target array.
-
-        Returns the number of bytes stored into dst, which may be less than
-        requested.
-
-    ***************************************************************************/ 
-
-    override size_t read(void[] dst)
-    {
-        if( !zs_valid )
-            return IConduit.Eof;
-
-        // Check to see if we've run out of input data.  If we have, get some
-        // more.
-        if( zs.avail_in == 0 )
-        {
-            auto len = source.read(in_chunk);
-            if( len == IConduit.Eof )
-                return IConduit.Eof;
-
-            zs.avail_in = len;
-            zs.next_in = in_chunk.ptr;
-        }
-
-        // We'll tell zlib to inflate straight into the target array.
-        zs.avail_out = dst.length;
-        zs.next_out = cast(ubyte*)dst.ptr;
-        auto ret = inflate(&zs, Z_NO_FLUSH);
-
-        switch( ret )
-        {
-            case Z_NEED_DICT:
-                // Whilst not technically an error, this should never happen
-                // for general-use code, so treat it as an error.
-            case Z_DATA_ERROR:
-            case Z_MEM_ERROR:
-                kill_zs();
-                throw new ZlibException(ret);
-
-            case Z_STREAM_END:
-                // zlib stream is finished; kill the stream so we don't try to
-                // read from it again.
-                kill_zs();
-                break;
-
-            default:
-        }
-
-        return dst.length - zs.avail_out;
-    }
-
-    /***************************************************************************
-
-        Closes the compression stream.
-
-    ***************************************************************************/ 
-
-    override void close()
-    {
-        // Kill the stream.  Don't deallocate the buffer since the user may
-        // yet reset the stream.
-        if( zs_valid )
-            kill_zs();
-
-        super.close();
-    }
-
-    // Disable seeking
-    override long seek(long offset, Anchor anchor = Anchor.Begin)
-    {
-        throw new IOException("ZlibInput does not support seek requests");
-    }
-
-    // This function kills the stream: it deallocates the internal state, and
-    // unsets the zs_valid flag.
-    private void kill_zs()
-    {
-        check_valid();
-
-        inflateEnd(&zs);
-        zs_valid = false;
-    }
-
-    // Asserts that the stream is still valid and usable (except that this
-    // check doesn't get elided with -release).
-    private void check_valid()
-    {
-        if( !zs_valid )
-            throw new ZlibClosedException;
-    }
-}
-
-/*******************************************************************************
-  
-    This output filter can be used to perform compression of data into a zlib
-    stream.
-
-*******************************************************************************/
-
-class ZlibOutput : OutputFilter
-{
-    /***************************************************************************
-
-        This enumeration represents several pre-defined compression levels.
-
-        Any integer between -1 and 9 inclusive may be used as a level,
-        although the symbols in this enumeration should suffice for most
-        use-cases.
-
-    ***************************************************************************/
-
-    enum Level : int
-    {
-        /**
-         * Default compression level.  This is selected for a good compromise
-         * between speed and compression, and the exact compression level is
-         * determined by the underlying zlib library.  Should be roughly
-         * equivalent to compression level 6.
-         */
-        Normal = -1,
-        /**
-         * Do not perform compression.  This will cause the stream to expand
-         * slightly to accommodate stream metadata.
-         */
-        None = 0,
-        /**
-         * Minimal compression; the fastest level which performs at least
-         * some compression.
-         */
-        Fast = 1,
-        /**
-         * Maximal compression.
-         */
-        Best = 9
-    }
-
-    /***************************************************************************
-    
-        This enumeration allows you to specify what the encoding of the
-        compressed stream should be.
-    
-    ***************************************************************************/
-
-    enum Encoding : int
-    {
-        /**
-         *  Stream should use zlib encoding.
-         */
-        Zlib,
-        /**
-         *  Stream should use gzip encoding.
-         */
-        Gzip,
-        /**
-         *  Stream should use no encoding.
-         */
-        None
-    }
-
-    private
-    {
-        bool zs_valid = false;
-        z_stream zs;
-        ubyte[] out_chunk;
-        size_t _written = 0;
-    }
-
-    /***************************************************************************
-
-        Constructs a new zlib compression filter.  You need to pass in the
-        stream that the compression filter will write to.  If you are using
-        this filter with a conduit, the idiom to use is:
-
-        ---
-        auto output = new ZlibOutput(myConduit.output);
-        output.write(myContent);
-        ---
-
-        The optional windowBits parameter is the base two logarithm of the
-        window size, and should be in the range 8-15, defaulting to 15 if not
-        specified.  Additionally, the windowBits parameter may be negative to
-        indicate that zlib should omit the standard zlib header and trailer,
-        with the window size being -windowBits.
-
-    ***************************************************************************/
-
-    this(OutputStream stream, Level level, Encoding encoding,
-            int windowBits = WINDOWBITS_DEFAULT)
-    {
-        init(stream, level, encoding, windowBits);
-        scope(failure) kill_zs();
-
-        super(stream);
-        out_chunk = new ubyte[CHUNKSIZE];
-    }
-    
-    /// ditto
-    this(OutputStream stream, Level level = Level.Normal)
-    {
-        // DRK 2009-02-26
-        // Removed unique implementation in favour of passing on to another
-        // constructor.  See ZlibInput.this(InputStream).
-        this(stream, level, Encoding.init);
-    }
-
-    /*
-     * This method performs initialisation for the stream.  Note that this may
-     * be called more than once for an instance, provided the instance is
-     * either new or as part of a call to reset.
-     */
-    private void init(OutputStream stream, Level level, Encoding encoding,
-            int windowBits)
-    {
-        /*
-         * Here's how windowBits works, according to zlib.h:
-         * 
-         * 8 .. 15
-         *      zlib encoding.
-         *      
-         * (8 .. 15) + 16
-         *      gzip encoding.
-         *      
-         * (8 .. 15) + 32
-         *      auto-detect encoding.
-         *      
-         * (8 .. 15) * -1
-         *      raw/no encoding.
-         *      
-         * Since we're going to be playing with the value, we DO care whether
-         * windowBits is in the expected range, so we'll check it.
-         * 
-         * Also, note that OUR Encoding enum doesn't contain the 'Guess'
-         * member.  I'm still waiting on tango.io.psychic...
-         */
-        if( !( 8 <= windowBits && windowBits <= 15 ) )
-        {
-            // No compression for you!
-            throw new ZlibException("invalid windowBits argument"
-                ~ .toString(windowBits));
-        }
-        
-        switch( encoding )
-        {
-        case Encoding.Zlib:
-            // no-op
-            break;
-            
-        case Encoding.Gzip:
-            windowBits += 16;
-            break;
-            
-        case Encoding.None:
-            windowBits *= -1;
-            break;
-
-        default:
-            assert (false);
-        }
-        
-        // Allocate deflate state
-        with( zs )
-        {
-            zalloc = null;
-            zfree = null;
-            opaque = null;
-        }
-
-        auto ret = deflateInit2(&zs, level, Z_DEFLATED, windowBits, 8,
-                Z_DEFAULT_STRATEGY);
-        if( ret != Z_OK )
-            throw new ZlibException(ret);
-
-        zs_valid = true;
-
-        // This is NOT REDUNDANT.  See ZlibInput.init.
-        this.sink = stream;
-    }
-
-    ~this()
-    {
-        if( zs_valid )
-            kill_zs();
-    }
-
-    /***************************************************************************
-        
-        Resets and re-initialises this instance.
-
-        If you are creating compression streams inside a loop, you may wish to
-        use this method to re-use a single instance.  This prevents the
-        potentially costly re-allocation of internal buffers.
-
-        The stream must have already been closed or committed before calling
-        reset.
-
-    ***************************************************************************/ 
-
-    void reset(OutputStream stream, Level level, Encoding encoding,
-            int windowBits = WINDOWBITS_DEFAULT)
-    {
-        // If the stream is still valid, bail.
-        if( zs_valid )
-            throw new ZlibStillOpenException;
-
-        init(stream, level, encoding, windowBits);
-    }
-
-    /// ditto
-    void reset(OutputStream stream, Level level = Level.Normal)
-    {
-        reset(stream, level, Encoding.init);
-    }
-
-    /***************************************************************************
-
-        Compresses the given data to the underlying conduit.
-
-        Returns the number of bytes from src that were compressed; write
-        should always consume all data provided to it, although it may not be
-        immediately written to the underlying output stream.
-
-    ***************************************************************************/
-
-    override size_t write(void[] src)
-    {
-        check_valid();
-        scope(failure) kill_zs();
-
-        zs.avail_in = src.length;
-        zs.next_in = cast(ubyte*)src.ptr;
-
-        do
-        {
-            zs.avail_out = out_chunk.length;
-            zs.next_out = out_chunk.ptr;
-
-            auto ret = deflate(&zs, Z_NO_FLUSH);
-            if( ret == Z_STREAM_ERROR )
-                throw new ZlibException(ret);
-
-            // Push the compressed bytes out to the stream, until it's either
-            // written them all, or choked.
-            auto have = out_chunk.length-zs.avail_out;
-            auto out_buffer = out_chunk[0..have];
-            do
-            {
-                auto w = sink.write(out_buffer);
-                if( w == IConduit.Eof )
-                    return w;
-
-                out_buffer = out_buffer[w..$];
-                _written += w;
-            }
-            while( out_buffer.length > 0 );
-        }
-        // Loop while we are still using up the whole output buffer
-        while( zs.avail_out == 0 );
-
-        assert( zs.avail_in == 0, "failed to compress all provided data" );
-
-        return src.length;
-    }
-
-    /***************************************************************************
-
-        This read-only property returns the number of compressed bytes that
-        have been written to the underlying stream.  Following a call to
-        either close or commit, this will contain the total compressed size of
-        the input data stream.
-
-    ***************************************************************************/
-
-    size_t written()
-    {
-        return _written;
-    }
-
-    /***************************************************************************
-
-        Close the compression stream.  This will cause any buffered content to
-        be committed to the underlying stream.
-
-    ***************************************************************************/
-
-    override void close()
-    {
-        // Only commit if the stream is still open.
-        if( zs_valid ) commit;
-
-        super.close;
-    }
-
-    /***************************************************************************
-
-        Purge any buffered content.  Calling this will implicitly end the zlib
-        stream, so it should not be called until you are finished compressing
-        data.  Any calls to either write or commit after a compression filter
-        has been committed will throw an exception.
-
-        The only difference between calling this method and calling close is
-        that the underlying stream will not be closed.
-
-    ***************************************************************************/
-
-    void commit()
-    {
-        check_valid();
-        scope(failure) kill_zs();
-
-        zs.avail_in = 0;
-        zs.next_in = null;
-
-        bool finished = false;
-
-        do
-        {
-            zs.avail_out = out_chunk.length;
-            zs.next_out = out_chunk.ptr;
-
-            auto ret = deflate(&zs, Z_FINISH);
-            switch( ret )
-            {
-                case Z_OK:
-                    // Keep going
-                    break;
-
-                case Z_STREAM_END:
-                    // We're done!
-                    finished = true;
-                    break;
-
-                default:
-                    throw new ZlibException(ret);
-            }
-
-            auto have = out_chunk.length - zs.avail_out;
-            auto out_buffer = out_chunk[0..have];
-            if( have > 0 )
-            {
-                do
-                {
-                    auto w = sink.write(out_buffer);
-                    if( w == IConduit.Eof )
-                        return;
-
-                    out_buffer = out_buffer[w..$];
-                    _written += w;
-                }
-                while( out_buffer.length > 0 );
-            }
-        }
-        while( !finished );
-
-        kill_zs();
-    }
-
-    // Disable seeking
-    override long seek(long offset, Anchor anchor = Anchor.Begin)
-    {
-        throw new IOException("ZlibOutput does not support seek requests");
-    }
-
-    // This function kills the stream: it deallocates the internal state, and
-    // unsets the zs_valid flag.
-    private void kill_zs()
-    {
-        check_valid();
-
-        deflateEnd(&zs);
-        zs_valid = false;
-    }
-
-    // Asserts that the stream is still valid and usable (except that this
-    // check doesn't get elided with -release).
-    private void check_valid()
-    {
-        if( !zs_valid )
-            throw new ZlibClosedException;
-    }
-}
-
-/*******************************************************************************
-  
-    This exception is thrown if you attempt to perform a read, write or flush
-    operation on a closed zlib filter stream.  This can occur if the input
-    stream has finished, or an output stream was flushed.
-
-*******************************************************************************/
-
-class ZlibClosedException : IOException
-{
-    this()
-    {
-        super("cannot operate on closed zlib stream");
-    }
-}
-
-/*******************************************************************************
-  
-    This exception is thrown if you attempt to reset a compression stream that
-    is still open.  You must either close or commit a stream before it can be
-    reset.
-
-*******************************************************************************/
-
-class ZlibStillOpenException : IOException
-{
-    this()
-    {
-        super("cannot reset an open zlib stream");
-    }
-}
-
-/*******************************************************************************
-  
-    This exception is thrown when an error occurs in the underlying zlib
-    library.  Where possible, it will indicate both the name of the error, and
-    any textural message zlib has provided.
-
-*******************************************************************************/
-
-class ZlibException : IOException
-{
-    /*
-     * Use this if you want to throw an exception that isn't actually
-     * generated by zlib.
-     */
-    this(char[] msg)
-    {
-        super(msg);
-    }
-    
-    /*
-     * code is the error code returned by zlib.  The exception message will
-     * be the name of the error code.
-     */
-    this(int code)
-    {
-        super(codeName(code));
-    }
-
-    /*
-     * As above, except that it appends msg as well.
-     */
-    this(int code, char* msg)
-    {
-        super(codeName(code)~": "~fromStringz(msg));
-    }
-
-    protected char[] codeName(int code)
-    {
-        char[] name;
-
-        switch( code )
-        {
-            case Z_OK:              name = "Z_OK";              break;
-            case Z_STREAM_END:      name = "Z_STREAM_END";      break;
-            case Z_NEED_DICT:       name = "Z_NEED_DICT";       break;
-            case Z_ERRNO:           name = "Z_ERRNO";           break;
-            case Z_STREAM_ERROR:    name = "Z_STREAM_ERROR";    break;
-            case Z_DATA_ERROR:      name = "Z_DATA_ERROR";      break;
-            case Z_MEM_ERROR:       name = "Z_MEM_ERROR";       break;
-            case Z_BUF_ERROR:       name = "Z_BUF_ERROR";       break;
-            case Z_VERSION_ERROR:   name = "Z_VERSION_ERROR";   break;
-            default:                name = "Z_UNKNOWN";
-        }
-
-        return name;
-    }
-}
-
-/* *****************************************************************************
-
-    This section contains a simple unit test for this module.  It is hidden
-    behind a version statement because it introduces additional dependencies.
-
-***************************************************************************** */
-
-debug(UnitTest) {
-
-import tango.io.device.Array : Array;
-
-void check_array(char[] FILE=__FILE__, int LINE=__LINE__)(
-        ubyte[] as, ubyte[] bs, lazy char[] msg)
-{
-    assert( as.length == bs.length,
-        FILE ~":"~ toString(LINE) ~ ": " ~ msg()
-        ~ "array lengths differ (" ~ toString(as.length)
-        ~ " vs " ~ toString(bs.length) ~ ")" );
-    
-    foreach( i, a ; as )
-    {
-        auto b = bs[i];
-        
-        assert( a == b,
-            FILE ~":"~ toString(LINE) ~ ": " ~ msg()
-            ~ "arrays differ at " ~ toString(i)
-            ~ " (" ~ toString(cast(int) a)
-            ~ " vs " ~ toString(cast(int) b) ~ ")" );
-    }
-}
-
-unittest
-{
-    // One ring to rule them all, one ring to find them,
-    // One ring to bring them all and in the darkness bind them.
-    const char[] message = 
-        "Ash nazg durbatulûk, ash nazg gimbatul, "
-        "ash nazg thrakatulûk, agh burzum-ishi krimpatul.";
-    
-    static assert( message.length == 90 );
-
-    // This compressed data was created using Python 2.5's built in zlib
-    // module, with the default compression level.
-    {
-        const ubyte[] message_z = [
-            0x78,0x9c,0x73,0x2c,0xce,0x50,0xc8,0x4b,
-            0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a,
-            0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47,
-            0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16,
-            0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46,
-            0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa,
-            0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54,
-            0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea,
-            0x01,0x00,0x1f,0xe3,0x22,0x99];
-    
-        scope cond_z = new Array(2048);
-        scope comp = new ZlibOutput(cond_z);
-        comp.write (message);
-        comp.close;
-    
-        assert( comp.written == message_z.length );
-        
-        /+
-        Stdout("message_z:").newline;
-        foreach( b ; cast(ubyte[]) cond_z.slice )
-            Stdout.format("0x{0:x2},", b);
-        Stdout.newline.newline;
-        +/
-    
-        //assert( message_z == cast(ubyte[])(cond_z.slice) );
-        check_array!(__FILE__,__LINE__)
-            ( message_z, cast(ubyte[]) cond_z.slice, "message_z " );
-    
-        scope decomp = new ZlibInput(cond_z);
-        auto buffer = new ubyte[256];
-        buffer = buffer[0 .. decomp.read(buffer)];
-    
-        //assert( cast(ubyte[])message == buffer );
-        check_array!(__FILE__,__LINE__)
-            ( cast(ubyte[]) message, buffer, "message (zlib) " );
-    }
-    
-    // This compressed data was created using the Cygwin gzip program
-    // with default options.  The original file was called "testdata.txt".
-    {
-        const ubyte[] message_gz = [
-            0x1f,0x8b,0x08,0x00,0x80,0x70,0x6f,0x45,
-            0x00,0x03,0x73,0x2c,0xce,0x50,0xc8,0x4b,
-            0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a,
-            0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47,
-            0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16,
-            0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46,
-            0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa,
-            0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54,
-            0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea,
-            0x01,0x00,0x45,0x38,0xbc,0x58,0x5a,0x00,
-            0x00,0x00];
-        
-        // Compresses the original message, and outputs the bytes.  You can use
-        // this to test the output of ZlibOutput with gzip.  If you use this,
-        // don't forget to import Stdout somewhere.
-        /+
-        scope comp_gz = new Array(2048);
-        scope comp = new ZlibOutput(comp_gz, ZlibOutput.Level.Normal, ZlibOutput.Encoding.Gzip, WINDOWBITS_DEFAULT);
-        comp.write(message);
-        comp.close;
-        
-        Stdout.format("message_gz ({0} bytes):", comp_gz.slice.length).newline;
-        foreach( b ; cast(ubyte[]) comp_gz.slice )
-            Stdout.format("0x{0:x2},", b);
-        Stdout.newline;
-        +/
-        
-        // We aren't going to test that we can compress to a gzip stream
-        // since gzip itself always adds stuff like the filename, timestamps,
-        // etc.  We'll just make sure we can DECOMPRESS gzip streams.
-        scope decomp_gz = new Array(message_gz.dup);
-        scope decomp = new ZlibInput(decomp_gz);
-        auto buffer = new ubyte[256];
-        buffer = buffer[0 .. decomp.read(buffer)];
-        
-        //assert( cast(ubyte[]) message == buffer );
-        check_array!(__FILE__,__LINE__)
-            ( cast(ubyte[]) message, buffer, "message (gzip) ");
-    }
-}
-}
--- a/tango/io/compress/Zip.d
+++ /dev/null
@@ -1,2976 +0,0 @@
-/*******************************************************************************
- *
- * copyright:   Copyright (c) 2007 Daniel Keep.  All rights reserved.
- *
- * license:     BSD style: $(LICENSE)
- *
- * version:     Initial release: December 2007
- *
- * author:      Daniel Keep
- *
- ******************************************************************************/
-
-module tango.io.compress.Zip;
-
-/*
-
-TODO
-====
-
-* Disable UTF encoding until I've worked out what version of Zip that's
-  related to... (actually; it's entirely possible that's it's merely a
-  *proposal* at the moment.) (*Done*)
-
-* Make ZipEntry safe: make them aware that their creating reader has been
-  destroyed.
-
-*/
-
-import tango.core.ByteSwap : ByteSwap;
-import tango.io.device.Array : Array;
-import tango.io.device.File : File;
-import tango.io.FilePath : FilePath, PathView;
-import tango.io.device.FileMap : FileMap;
-import tango.io.compress.ZlibStream : ZlibInput, ZlibOutput;
-import tango.util.digest.Crc32 : Crc32;
-import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-import tango.io.stream.Digester : DigestInput;
-import tango.time.Time : Time, TimeSpan;
-import tango.time.WallClock : WallClock;
-import tango.time.chrono.Gregorian : Gregorian;
-
-import Path = tango.io.Path;
-import Integer = tango.text.convert.Integer;
-
-debug(Zip) import tango.io.Stdout : Stderr;
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Implementation crap
-//
-// Why is this here, you ask?  Because of bloody DMD forward reference bugs.
-// For pete's sake, Walter, FIX THEM, please!
-//
-// To skip to the actual user-visible stuff, search for "Shared stuff".
-
-private
-{
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// LocalFileHeader
-//
-
-    align(1)
-    struct LocalFileHeaderData
-    {
-        ushort      extract_version = ushort.max;
-        ushort      general_flags = 0;
-        ushort      compression_method = 0;
-        ushort      modification_file_time = 0;
-        ushort      modification_file_date = 0;
-        uint        crc_32 = 0; // offsetof = 10
-        uint        compressed_size = 0;
-        uint        uncompressed_size = 0;
-        ushort      file_name_length = 0;
-        ushort      extra_field_length = 0;
-
-        debug(Zip) void dump()
-        {
-            Stderr
-            ("LocalFileHeader.Data {")("\n")
-            ("  extract_version = ")(extract_version)("\n")
-            ("  general_flags = ")(general_flags)("\n")
-            ("  compression_method = ")(compression_method)("\n")
-            ("  modification_file_time = ")(modification_file_time)("\n")
-            ("  modification_file_date = ")(modification_file_date)("\n")
-            ("  crc_32 = ")(crc_32)("\n")
-            ("  compressed_size = ")(compressed_size)("\n")
-            ("  uncompressed_size = ")(uncompressed_size)("\n")
-            ("  file_name_length = ")(file_name_length)("\n")
-            ("  extra_field_length = ")(extra_field_length)("\n")
-            ("}").newline;
-        }
-    }
-
-struct LocalFileHeader
-{
-    const uint signature = 0x04034b50;
-
-    alias LocalFileHeaderData Data;
-    Data data;
-    static assert( Data.sizeof == 26 );
-
-    char[] file_name;
-    ubyte[] extra_field;
-
-    void[] data_arr()
-    {
-        return (&data)[0..1];
-    }
-
-    void put(OutputStream output)
-    {
-        // Make sure var-length fields will fit.
-        if( file_name.length > ushort.max )
-            ZipException.fntoolong;
-
-        if( extra_field.length > ushort.max )
-            ZipException.eftoolong;
-
-        // Encode filename
-        auto file_name = utf8_to_cp437(this.file_name);
-        scope(exit) if( file_name !is cast(ubyte[])this.file_name )
-            delete file_name;
-
-        if( file_name is null )
-            ZipException.fnencode;
-
-        // Update lengths in data
-        Data data = this.data;
-        data.file_name_length = cast(ushort) file_name.length;
-        data.extra_field_length = cast(ushort) extra_field.length;
-
-        // Do it
-        version( BigEndian ) swapAll(data);
-        writeExact(output, (&data)[0..1]);
-        writeExact(output, file_name);
-        writeExact(output, extra_field);
-    }
-
-    void fill(InputStream src)
-    {
-        readExact(src, data_arr);
-        version( BigEndian ) swapAll(data);
-
-        //debug(Zip) data.dump;
-
-        auto tmp = new ubyte[data.file_name_length];
-        readExact(src, tmp);
-        file_name = cp437_to_utf8(tmp);
-        if( cast(char*) tmp.ptr !is file_name.ptr ) delete tmp;
-
-        extra_field = new ubyte[data.extra_field_length];
-        readExact(src, extra_field);
-    }
-
-    /*
-     * This method will check to make sure that the local and central headers
-     * are the same; if they're not, then that indicates that the archive is
-     * corrupt.
-     */
-    bool agrees_with(FileHeader h)
-    {
-        // NOTE: extra_field used to be compared with h.extra_field, but this caused
-        // an assertion in certain archives. I found a mention of these fields being
-        // allowed to be different, so I think it in general is wrong to include in
-        // this sanity check. larsivi 20081111
-        if( data.extract_version != h.data.extract_version
-                || data.general_flags != h.data.general_flags
-                || data.compression_method != h.data.compression_method
-                || data.modification_file_time != h.data.modification_file_time
-                || data.modification_file_date != h.data.modification_file_date
-                || file_name != h.file_name )
-            return false;
-        
-        // We need a separate check for the sizes and crc32, since these will
-        // be zero if a trailing descriptor was used.
-        if( !h.usingDataDescriptor && (
-                   data.crc_32 != h.data.crc_32
-                || data.compressed_size != h.data.compressed_size
-                || data.uncompressed_size != h.data.uncompressed_size ) )
-            return false;
-
-        return true;
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// FileHeader
-//
-
-    align(1)
-    struct FileHeaderData
-    {
-        ubyte       zip_version;
-        ubyte       file_attribute_type;
-        ushort      extract_version;
-        ushort      general_flags;
-        ushort      compression_method;
-        ushort      modification_file_time;
-        ushort      modification_file_date;
-        uint        crc_32;
-        uint        compressed_size;
-        uint        uncompressed_size;
-        ushort      file_name_length;
-        ushort      extra_field_length;
-        ushort      file_comment_length;
-        ushort      disk_number_start;
-        ushort      internal_file_attributes = 0;
-        uint        external_file_attributes = 0;
-        int         relative_offset_of_local_header;
-
-        debug(Zip) void dump()
-        {
-            Stderr
-            ("FileHeader.Data {\n")
-            ("  zip_version = ")(zip_version)("\n")
-            ("  file_attribute_type = ")(file_attribute_type)("\n")
-            ("  extract_version = ")(extract_version)("\n")
-            ("  general_flags = ")(general_flags)("\n")
-            ("  compression_method = ")(compression_method)("\n")
-            ("  modification_file_time = ")(modification_file_time)("\n")
-            ("  modification_file_date = ")(modification_file_date)("\n")
-            ("  crc_32 = ")(crc_32)("\n")
-            ("  compressed_size = ")(compressed_size)("\n")
-            ("  uncompressed_size = ")(uncompressed_size)("\n")
-            ("  file_name_length = ")(file_name_length)("\n")
-            ("  extra_field_length = ")(extra_field_length)("\n")
-            ("  file_comment_length = ")(file_comment_length)("\n")
-            ("  disk_number_start = ")(disk_number_start)("\n")
-            ("  internal_file_attributes = ")(internal_file_attributes)("\n")
-            ("  external_file_attributes = ")(external_file_attributes)("\n")
-            ("  relative_offset_of_local_header = ")(relative_offset_of_local_header)
-                ("\n")
-            ("}").newline;
-        }
-
-        void fromLocal(LocalFileHeader.Data data)
-        {
-            extract_version = data.extract_version;
-            general_flags = data.general_flags;
-            compression_method = data.compression_method;
-            modification_file_time = data.modification_file_time;
-            modification_file_date = data.modification_file_date;
-            crc_32 = data.crc_32;
-            compressed_size = data.compressed_size;
-            uncompressed_size = data.uncompressed_size;
-            file_name_length = data.file_name_length;
-            extra_field_length = data.extra_field_length;
-        }
-    }
-
-struct FileHeader
-{
-    const uint signature = 0x02014b50;
-
-    alias FileHeaderData Data;
-    Data* data;
-    static assert( Data.sizeof == 42 );
-
-    char[] file_name;
-    ubyte[] extra_field;
-    char[] file_comment;
-
-    bool usingDataDescriptor()
-    {
-        return !!(data.general_flags & 1<<3);
-    }
-
-    uint compressionOptions()
-    {
-        return (data.general_flags >> 1) & 0b11;
-    }
-
-    bool usingUtf8()
-    {
-        //return !!(data.general_flags & 1<<11);
-        return false;
-    }
-
-    void[] data_arr()
-    {
-        return (cast(void*)data)[0 .. Data.sizeof];
-    }
-
-    void put(OutputStream output)
-    {
-        // Make sure the var-length fields will fit.
-        if( file_name.length > ushort.max )
-            ZipException.fntoolong;
-
-        if( extra_field.length > ushort.max )
-            ZipException.eftoolong;
-
-        if( file_comment.length > ushort.max )
-            ZipException.cotoolong;
-
-        // encode the filename and comment
-        auto file_name = utf8_to_cp437(this.file_name);
-        scope(exit) if( file_name !is cast(ubyte[])this.file_name )
-            delete file_name;
-        auto file_comment = utf8_to_cp437(this.file_comment);
-        scope(exit) if( file_comment !is cast(ubyte[])this.file_comment )
-            delete file_comment;
-
-        if( file_name is null )
-            ZipException.fnencode;
-
-        if( file_comment is null && this.file_comment !is null )
-            ZipException.coencode;
-
-        // Update the lengths
-        Data data = *(this.data);
-        data.file_name_length = cast(ushort) file_name.length;
-        data.extra_field_length = cast(ushort) extra_field.length;
-        data.file_comment_length = cast(ushort) file_comment.length;
-
-        // Ok; let's do this!
-        version( BigEndian ) swapAll(data);
-        writeExact(output, (&data)[0..1]);
-        writeExact(output, file_name);
-        writeExact(output, extra_field);
-        writeExact(output, file_comment);
-    }
-
-    long map(void[] src)
-    {
-        //debug(Zip) Stderr.formatln("FileHeader.map([0..{}])",src.length);
-
-        auto old_ptr = src.ptr;
-
-        data = cast(Data*) src.ptr;
-        src = src[Data.sizeof..$];
-        version( BigEndian ) swapAll(*data);
-
-        //debug(Zip) data.dump;
-
-        char[] function(ubyte[]) conv_fn;
-        if( usingUtf8 )
-            conv_fn = &cp437_to_utf8;
-        else
-            conv_fn = &utf8_to_utf8;
-
-        file_name = conv_fn(
-                cast(ubyte[]) src[0..data.file_name_length]);
-        src = src[data.file_name_length..$];
-
-        extra_field = cast(ubyte[]) src[0..data.extra_field_length];
-        src = src[data.extra_field_length..$];
-
-        file_comment = conv_fn(
-                cast(ubyte[]) src[0..data.file_comment_length]);
-        src = src[data.file_comment_length..$];
-
-        // Return how many bytes we've eaten
-        //debug(Zip) Stderr.formatln(" . used {} bytes", cast(long)(src.ptr - old_ptr));
-        return cast(long)(src.ptr - old_ptr);
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// EndOfCDRecord
-//
-
-    align(1)
-    struct EndOfCDRecordData
-    {
-        ushort      disk_number = 0;
-        ushort      disk_with_start_of_central_directory = 0;
-        ushort      central_directory_entries_on_this_disk;
-        ushort      central_directory_entries_total;
-        uint        size_of_central_directory;
-        uint        offset_of_start_of_cd_from_starting_disk;
-        ushort      file_comment_length;
-
-        debug(Zip) void dump()
-        {
-            Stderr
-                .formatln("EndOfCDRecord.Data {}","{")
-                .formatln("  disk_number = {}", disk_number)
-                .formatln("  disk_with_start_of_central_directory = {}",
-                        disk_with_start_of_central_directory)
-                .formatln("  central_directory_entries_on_this_disk = {}",
-                        central_directory_entries_on_this_disk)
-                .formatln("  central_directory_entries_total = {}",
-                        central_directory_entries_total)
-                .formatln("  size_of_central_directory = {}",
-                        size_of_central_directory)
-                .formatln("  offset_of_start_of_cd_from_starting_disk = {}",
-                        offset_of_start_of_cd_from_starting_disk)
-                .formatln("  file_comment_length = {}", file_comment_length)
-                .formatln("}");
-        }
-    }
-
-struct EndOfCDRecord
-{
-    const uint  signature = 0x06054b50;
-
-    alias EndOfCDRecordData Data;
-    Data data;
-    static assert( data.sizeof == 18 );
-
-    char[] file_comment;
-
-    void[] data_arr()
-    {
-        return (cast(void*)&data)[0 .. data.sizeof];
-    }
-
-    void put(OutputStream output)
-    {
-        // Set up the comment; check length, encode
-        if( file_comment.length > ushort.max )
-            ZipException.cotoolong;
-
-        auto file_comment = utf8_to_cp437(this.file_comment);
-        scope(exit) if( file_comment !is cast(ubyte[])this.file_comment )
-                delete file_comment;
-
-        // Set up data block
-        Data data = this.data;
-        data.file_comment_length = cast(ushort) file_comment.length;
-
-        version( BigEndian ) swapAll(data);
-        writeExact(output, (&data)[0..1]);
-    }
-
-    void fill(void[] src)
-    {
-        //Stderr.formatln("EndOfCDRecord.fill([0..{}])",src.length);
-
-        auto _data = data_arr;
-        _data[] = src[0.._data.length];
-        src = src[_data.length..$];
-        version( BigEndian ) swapAll(data);
-
-        //data.dump;
-
-        file_comment = cast(char[]) src[0..data.file_comment_length].dup;
-    }
-}
-
-// End of implementation crap
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Shared stuff
-
-public
-{
-    /**
-     * This enumeration denotes the kind of compression used on a file.
-     */
-    enum Method
-    {
-        /// No compression should be used.
-        Store,
-        /// Deflate compression.
-        Deflate,
-        /**
-         * This is a special value used for unsupported or unrecognised
-         * compression methods.  This value is only used internally.
-         */
-        Unsupported
-    }
-}
-
-private
-{
-    const ushort ZIP_VERSION = 20;
-    const ushort MAX_EXTRACT_VERSION = 20;
-
-    /*                                     compression flags
-                                  uses trailing descriptor |
-                               utf-8 encoding            | |
-                                            ^            ^ /\               */
-    const ushort SUPPORTED_FLAGS = 0b00_0_0_0_0000_0_0_0_1_11_0;
-    const ushort UNSUPPORTED_FLAGS = ~SUPPORTED_FLAGS;
-
-    Method toMethod(ushort method)
-    {
-        switch( method )
-        {
-            case 0:     return Method.Store;
-            case 8:     return Method.Deflate;
-            default:    return Method.Unsupported;
-        }
-    }
-
-    ushort fromMethod(Method method)
-    {
-        switch( method )
-        {
-            case Method.Store:      return 0;
-            case Method.Deflate:    return 8;
-            default:
-                assert(false, "unsupported compression method");
-        }
-    }
-
-    /* NOTE: This doesn't actually appear to work.  Using the default magic
-     * number with Tango's Crc32 digest works, however.
-     */
-    //const CRC_MAGIC = 0xdebb20e3u;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// ZipReader
-
-interface ZipReader
-{
-    bool streamed();
-    void close();
-    bool more();
-    ZipEntry get();
-    ZipEntry get(ZipEntry);
-    int opApply(int delegate(ref ZipEntry));
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// ZipWriter
-
-interface ZipWriter
-{
-    void finish();
-    void putFile(ZipEntryInfo info, char[] path);
-    void putFile(ZipEntryInfo info, char[] path);
-    void putStream(ZipEntryInfo info, InputStream source);
-    void putEntry(ZipEntryInfo info, ZipEntry entry);
-    void putData(ZipEntryInfo info, void[] data);
-    Method method();
-    Method method(Method);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// ZipBlockReader
-
-/**
- * The ZipBlockReader class is used to parse a Zip archive.  It exposes the
- * contents of the archive via an iteration interface.  For instance, to loop
- * over all files in an archive, one can use either
- *
- * -----
- *  foreach( entry ; reader )
- *      ...
- * -----
- *
- * Or
- *
- * -----
- *  while( reader.more )
- *  {
- *      auto entry = reader.get;
- *      ...
- *  }
- * -----
- *
- * See the ZipEntry class for more information on the contents of entries.
- *
- * Note that this class can only be used with input sources which can be
- * freely seeked.  Also note that you may open a ZipEntry instance produced by
- * this reader at any time until the ZipReader that created it is closed.
- */
-class ZipBlockReader : ZipReader
-{
-    /**
-     * Creates a ZipBlockReader using the specified file on the local
-     * filesystem.
-     */
-    this(char[] path)
-    {
-        file_source = new File(path);
-        this(file_source);
-    }
-
-    /**
-     * Creates a ZipBlockReader using the provided InputStream.  Please note
-     * that this InputStream must be attached to a conduit implementing the 
-     * IConduit.Seek interface.
-     */
-    this(InputStream source)
-    in
-    {
-        assert( cast(IConduit.Seek) source.conduit, "source stream must be seekable" );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-    }
-
-    bool streamed() { return false; }
-
-    /**
-     * Closes the reader, and releases all resources.  After this operation,
-     * all ZipEntry instances created by this ZipReader are invalid and should
-     * not be used.
-     */
-    void close()
-    {
-        // NOTE: Originally more of the GC allocated data in this class were
-        // explicitly deleted here, such as cd_data - this caused segfaults
-        // and have been removed as they were not necessary from correctness
-        // point of view, and the memory usage win is questionable.
-        state = State.Done;
-        source = null;
-        seeker = null;
-        delete headers;
-
-        if( file_source !is null )  
-          {
-          file_source.close;
-          delete file_source;
-          }
-    }
-
-    /**
-     * Returns true if and only if there are additional files in the archive
-     * which have not been read via the get method.  This returns true before
-     * the first call to get (assuming the opened archive is non-empty), and
-     * false after the last file has been accessed.
-     */
-    bool more()
-    {
-        switch( state )
-        {
-            case State.Init:
-                read_cd;
-                assert( state == State.Open );
-                return more;
-
-            case State.Open:
-                return (current_index < headers.length);
-
-            case State.Done:
-                return false;
-
-            default:
-                assert(false);
-        }
-    }
-
-    /**
-     * Retrieves the next file from the archive.  Note that although this does
-     * perform IO operations, it will not read the contents of the file.
-     *
-     * The optional reuse argument can be used to instruct the reader to reuse
-     * an existing ZipEntry instance.  If passed a null reference, it will
-     * create a new ZipEntry instance.
-     */
-    ZipEntry get()
-    {
-        if( !more )
-            ZipExhaustedException();
-
-        return new ZipEntry(headers[current_index++], &open_file);
-    }
-
-    /// ditto
-    ZipEntry get(ZipEntry reuse)
-    {
-        if( !more )
-            ZipExhaustedException();
-
-        if( reuse is null )
-            return new ZipEntry(headers[current_index++], &open_file);
-        else
-            return reuse.reset(headers[current_index++], &open_file);
-    }
-
-    /**
-     * This is used to iterate over the contents of an archive using a foreach
-     * loop.  Please note that the iteration will reuse the ZipEntry instance
-     * passed to your loop.  If you wish to keep the instance and re-use it
-     * later, you $(B must) use the dup member to create a copy.
-     */
-    int opApply(int delegate(ref ZipEntry) dg)
-    {
-        int result = 0;
-        ZipEntry entry;
-
-        while( more )
-        {
-            entry = get(entry);
-
-            result = dg(entry);
-            if( result )
-                break;
-        }
-
-        if( entry !is null )
-            delete entry;
-
-        return result;
-    }
-
-private:
-    InputStream source;
-    InputStream seeker; //IConduit.Seek seeker;
-
-    enum State { Init, Open, Done }
-    State state;
-    size_t current_index = 0;
-    FileHeader[] headers;
-
-    // These should be killed when the reader is closed.
-    ubyte[] cd_data;
-    File file_source = null;
-
-    /*
-     * This function will read the contents of the central directory.  Split
-     * or spanned archives aren't supported.
-     */
-    void read_cd()
-    in
-    {
-        assert( state == State.Init );
-        assert( headers is null );
-        assert( cd_data is null );
-    }
-    out
-    {
-        assert( state == State.Open );
-        assert( headers !is null );
-        assert( cd_data !is null );
-        assert( current_index == 0 );
-    }
-    body
-    {
-        //Stderr.formatln("ZipReader.read_cd()");
-
-        // First, we need to locate the end of cd record, so that we know
-        // where the cd itself is, and how big it is.
-        auto eocdr = read_eocd_record;
-
-        // Now, make sure the archive is all in one file.
-        if( eocdr.data.disk_number !=
-                    eocdr.data.disk_with_start_of_central_directory
-                || eocdr.data.central_directory_entries_on_this_disk !=
-                    eocdr.data.central_directory_entries_total )
-            ZipNotSupportedException.spanned;
-
-        // Ok, read the whole damn thing in one go.
-        cd_data = new ubyte[eocdr.data.size_of_central_directory];
-        long cd_offset = eocdr.data.offset_of_start_of_cd_from_starting_disk;
-        seeker.seek(cd_offset, seeker.Anchor.Begin);
-        readExact(source, cd_data);
-
-        // Cake.  Now, we need to break it up into records.
-        headers = new FileHeader[
-            eocdr.data.central_directory_entries_total];
-
-        long cdr_offset = cd_offset;
-
-        // Ok, map the CD data into file headers.
-        foreach( i,ref header ; headers )
-        {
-            //Stderr.formatln(" . reading header {}...", i);
-
-            // Check signature
-            {
-                uint sig = (cast(uint[])(cd_data[0..4]))[0];
-                version( BigEndian ) swap(sig);
-                if( sig != FileHeader.signature )
-                    ZipException.badsig("file header");
-            }
-
-            auto used = header.map(cd_data[4..$]);
-            assert( used <= (size_t.max-4) );
-            cd_data = cd_data[4+cast(size_t)used..$];
-
-            // Update offset for next record
-            cdr_offset += 4 /* for sig. */ + used;
-        }
-
-        // Done!
-        state = State.Open;
-    }
-
-    /*
-     * This will locate the end of CD record in the open stream.
-     *
-     * This code sucks, but that's because Zip sucks.
-     *
-     * Basically, the EOCD record is stuffed somewhere at the end of the file.
-     * In a brilliant move, the record is *variably sized*, which means we
-     * have to do a linear backwards search to find it.
-     *
-     * The header itself (including the signature) is at minimum 22 bytes
-     * long, plus anywhere between 0 and 2^16-1 bytes of comment.  That means
-     * we need to read the last 2^16-1 + 22 bytes from the file, and look for
-     * the signature [0x50,0x4b,0x05,0x06] in [0 .. $-18].
-     *
-     * If we find the EOCD record, we'll return its contents.  If we couldn't
-     * find it, we'll throw an exception.
-     */
-    EndOfCDRecord read_eocd_record()
-    in
-    {
-        assert( state == State.Init );
-    }
-    body
-    {
-        //Stderr.formatln("read_eocd_record()");
-
-        // Signature + record + max. comment length
-        const max_chunk_len = 4 + EndOfCDRecord.Data.sizeof + ushort.max;
-
-        auto file_len = seeker.seek(0, seeker.Anchor.End);
-        assert( file_len <= size_t.max );
-
-        // We're going to need min(max_chunk_len, file_len) bytes.
-        size_t chunk_len = max_chunk_len;
-        if( file_len < max_chunk_len )
-            chunk_len = cast(size_t) file_len;
-        //Stderr.formatln(" . chunk_len = {}", chunk_len);
-
-        // Seek back and read in the chunk.  Don't forget to clean up after
-        // ourselves.
-        seeker.seek(-cast(long)chunk_len, seeker.Anchor.End);
-        auto chunk_offset = seeker.seek(0, seeker.Anchor.Current);
-        //Stderr.formatln(" . chunk_offset = {}", chunk_offset);
-        auto chunk = new ubyte[chunk_len];
-        scope(exit) delete chunk;
-        readExact(source, chunk);
-
-        // Now look for our magic number.  Don't forget that on big-endian
-        // machines, we need to byteswap the value we're looking for.
-        uint eocd_magic = EndOfCDRecord.signature;
-        version( BigEndian )
-            swap(eocd_magic);
-
-        size_t eocd_loc = -1;
-
-        if( chunk_len >= 18 )
-            for( size_t i=chunk_len-18; i>=0; --i )
-            {
-                if( *(cast(uint*)(chunk.ptr+i)) == eocd_magic )
-                {
-                    // Found the bugger!  Make sure we skip the signature (forgot
-                    // to do that originally; talk about weird errors :P)
-                    eocd_loc = i+4;
-                    break;
-                }
-            }
-
-        // If we didn't find it, then we'll assume that this is not a valid
-        // archive.
-        if( eocd_loc == -1 )
-            ZipException.missingdir;
-
-        // Ok, so we found it; now what?  Now we need to read the record
-        // itself in.  eocd_loc is the offset within the chunk where the eocd
-        // record was found, so slice it out.
-        EndOfCDRecord eocdr;
-        eocdr.fill(chunk[eocd_loc..$]);
-
-        // Excellent.  We're done here.
-        return eocdr;
-    }
-
-    /*
-     * Opens the specified file for reading.  If the raw argument passed is
-     * true, then the file is *not* decompressed.
-     */
-    InputStream open_file(FileHeader header, bool raw)
-    {
-        // Check to make sure that we actually *can* open this file.
-        if( header.data.extract_version > MAX_EXTRACT_VERSION )
-            ZipNotSupportedException.zipver(header.data.extract_version);
-
-        if( header.data.general_flags & UNSUPPORTED_FLAGS )
-            ZipNotSupportedException.flags;
-
-        if( toMethod(header.data.compression_method) == Method.Unsupported )
-            ZipNotSupportedException.method(header.data.compression_method);
-
-        // Open a raw stream
-        InputStream stream = open_file_raw(header);
-
-        // If that's all they wanted, pass it back.
-        if( raw )
-            return stream;
-
-        // Next up, wrap in an appropriate decompression stream
-        switch( toMethod(header.data.compression_method) )
-        {
-            case Method.Store:
-                // Do nothing: \o/
-                break;
-
-            case Method.Deflate:
-                // Wrap in a zlib stream.  We want a raw deflate stream,
-                // so force no encoding.
-                stream = new ZlibInput(stream, ZlibInput.Encoding.None);
-                break;
-
-            default:
-                assert(false);
-        }
-
-        // We done, yo!
-        return stream;
-    }
-
-    /*
-     * Opens a file's raw input stream.  Basically, this returns a slice of
-     * the archive's input stream.
-     */
-    InputStream open_file_raw(FileHeader header)
-    {
-        // Seek to and parse the local file header
-        seeker.seek(header.data.relative_offset_of_local_header,
-                seeker.Anchor.Begin);
-
-        {
-            uint sig;
-            readExact(source, (&sig)[0..1]);
-            version( BigEndian ) swap(sig);
-            if( sig != LocalFileHeader.signature )
-                ZipException.badsig("local file header");
-        }
-
-        LocalFileHeader lheader; lheader.fill(source);
-
-        if( !lheader.agrees_with(header) )
-            ZipException.incons(header.file_name);
-
-        // Ok; get a slice stream for the file
-        return new SliceSeekInputStream(
-             source, seeker.seek(0, seeker.Anchor.Current),
-             header.data.compressed_size);
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// ZipBlockWriter
-
-/**
- * The ZipBlockWriter class is used to create a Zip archive.  It uses a
- * writing iterator interface.
- *
- * Note that this class can only be used with output streams which can be
- * freely seeked.
- */
-
-class ZipBlockWriter : ZipWriter
-{
-    /**
-     * Creates a ZipBlockWriter using the specified file on the local
-     * filesystem.
-     */
-    this(char[] path)
-    {
-        file_output = new File(path, File.WriteCreate);
-        this(file_output);
-    }
-
-    /**
-     * Creates a ZipBlockWriter using the provided OutputStream.  Please note
-     * that this OutputStream must be attached to a conduit implementing the 
-     * IConduit.Seek interface.
-     */
-    this(OutputStream output)
-    in
-    {
-        assert( output !is null );
-        assert( (cast(IConduit.Seek) output.conduit) !is null );
-    }
-    body
-    {
-        this.output = output;
-        this.seeker = output; // cast(IConduit.Seek) output;
-
-        // Default to Deflate compression
-        method = Method.Deflate;
-    }
-
-    /**
-     * Finalises the archive, writes out the central directory, and closes the
-     * output stream.
-     */
-    void finish()
-    {
-        put_cd;
-        output.close();
-        output = null;
-        seeker = null;
-
-        if( file_output !is null ) delete file_output;
-    }
-
-    /**
-     * Adds a file from the local filesystem to the archive.
-     */
-    void putFile(ZipEntryInfo info, char[] path)
-    {
-        scope file = new File(path);
-        scope(exit) file.close();
-        putStream(info, file);
-    }
-
-    /**
-     * Adds a file using the contents of the given InputStream to the archive.
-     */
-    void putStream(ZipEntryInfo info, InputStream source)
-    {
-        put_compressed(info, source);
-    }
-
-    /**
-     * Transfers a file from another archive into this archive.  Note that
-     * this method will not perform any compression: whatever compression was
-     * applied to the file originally will be preserved.
-     */
-    void putEntry(ZipEntryInfo info, ZipEntry entry)
-    {
-        put_raw(info, entry);
-    }
-
-    /**
-     * Adds a file using the contents of the given array to the archive.
-     */
-    void putData(ZipEntryInfo info, void[] data)
-    {
-        //scope mc = new MemoryConduit(data);
-        scope mc = new Array(data);
-        scope(exit) mc.close;
-        put_compressed(info, mc);
-    }
-
-    /**
-     * This property allows you to control what compression method should be
-     * used for files being added to the archive.
-     */
-    Method method() { return _method; }
-    Method method(Method v) { return _method = v; } /// ditto
-
-private:
-    OutputStream output;
-    OutputStream seeker;
-    File file_output;
-
-    Method _method;
-
-    struct Entry
-    {
-        FileHeaderData data;
-        long header_position;
-        char[] filename;
-        char[] comment;
-        ubyte[] extra;
-    }
-    Entry[] entries;
-
-    void put_cd()
-    {
-        // check that there aren't too many CD entries
-        if( entries.length > ushort.max )
-            ZipException.toomanyentries;
-
-        auto cd_pos = seeker.seek(0, seeker.Anchor.Current);
-        if( cd_pos > uint.max )
-            ZipException.toolong;
-
-        foreach( entry ; entries )
-        {
-            FileHeader header;
-            header.data = &entry.data;
-            header.file_name = entry.filename;
-            header.extra_field = entry.extra;
-            header.file_comment = entry.comment;
-
-            write(output, FileHeader.signature);
-            header.put(output);
-        }
-
-        auto cd_len = seeker.seek(0, seeker.Anchor.Current) - cd_pos;
-
-        if( cd_len > uint.max )
-            ZipException.cdtoolong;
-
-        {
-            assert( entries.length < ushort.max );
-            assert( cd_len < uint.max );
-            assert( cd_pos < uint.max );
-
-            EndOfCDRecord eocdr;
-            eocdr.data.central_directory_entries_on_this_disk =
-                cast(ushort) entries.length;
-            eocdr.data.central_directory_entries_total =
-                cast(ushort) entries.length;
-            eocdr.data.size_of_central_directory =
-                cast(uint) cd_len;
-            eocdr.data.offset_of_start_of_cd_from_starting_disk =
-                cast(uint) cd_pos;
-
-            write(output, EndOfCDRecord.signature);
-            eocdr.put(output);
-        }
-    }
-
-    void put_raw(ZipEntryInfo info, ZipEntry entry)
-    {
-        // Write out local file header
-        LocalFileHeader.Data lhdata;
-        auto chdata = entry.header.data;
-        lhdata.extract_version = chdata.extract_version;
-
-        // Note: we need to mask off the data descriptor bit because we aren't
-        // going to write one.
-        lhdata.general_flags = chdata.general_flags & ~(1<<3);
-        lhdata.compression_method = chdata.compression_method;
-        lhdata.crc_32 = chdata.crc_32;
-        lhdata.compressed_size = chdata.compressed_size;
-        lhdata.uncompressed_size = chdata.uncompressed_size;
-
-        timeToDos(info.modified, lhdata.modification_file_time,
-                                 lhdata.modification_file_date);
-
-        put_local_header(lhdata, info.name);
-
-        // Store comment
-        entries[$-1].comment = info.comment;
-
-        // Output file contents
-        {
-            auto input = entry.open_raw;
-            scope(exit) input.close;
-            output.copy(input).flush();
-        }
-    }
-
-    void put_compressed(ZipEntryInfo info, InputStream source)
-    {
-        debug(Zip) Stderr.formatln("ZipBlockWriter.put_compressed()");
-
-        // Write out partial local file header
-        auto header_pos = seeker.seek(0, seeker.Anchor.Current);
-        debug(Zip) Stderr.formatln(" . header for {} at {}", info.name, header_pos);
-        put_local_header(info, _method);
-
-        // Store comment
-        entries[$-1].comment = info.comment;
-
-        uint crc;
-        uint compressed_size;
-        uint uncompressed_size;
-
-        // Output file contents
-        {
-            // Input/output chains
-            InputStream in_chain = source;
-            OutputStream out_chain = new WrapSeekOutputStream(output);
-
-            // Count number of bytes coming in from the source file
-            scope in_counter = new CounterInput(in_chain);
-            in_chain = in_counter;
-            assert( in_counter.count <= typeof(uncompressed_size).max );
-            scope(success) uncompressed_size = cast(uint) in_counter.count;
-
-            // Count the number of bytes going out to the archive
-            scope out_counter = new CounterOutput(out_chain);
-            out_chain = out_counter;
-            assert( out_counter.count <= typeof(compressed_size).max );
-            scope(success) compressed_size = cast(uint) out_counter.count;
-
-            // Add crc
-            scope crc_d = new Crc32(/*CRC_MAGIC*/);
-            scope crc_s = new DigestInput(in_chain, crc_d);
-            in_chain = crc_s;
-            scope(success)
-            {
-                debug(Zip) Stderr.formatln(" . Success: storing CRC.");
-                crc = crc_d.crc32Digest;
-            }
-
-            // Add compression
-            ZlibOutput compress;
-            scope(exit) if( compress !is null ) delete compress;
-
-            switch( _method )
-            {
-                case Method.Store:
-                    break;
-
-                case Method.Deflate:
-                    compress = new ZlibOutput(out_chain,
-                            ZlibOutput.Level.init, ZlibOutput.Encoding.None);
-                    out_chain = compress;
-                    break;
-
-                default:
-                    assert(false);
-            }
-
-            // All done.
-            scope(exit) in_chain.close();
-            scope(success) in_chain.flush();
-            scope(exit) out_chain.close();
-
-            out_chain.copy(in_chain).flush;
-
-            debug(Zip) if( compress !is null )
-            {
-                Stderr.formatln(" . compressed to {} bytes", compress.written);
-            }
-
-            debug(Zip) Stderr.formatln(" . wrote {} bytes", out_counter.count);
-            debug(Zip) Stderr.formatln(" . contents written");
-        }
-
-        debug(Zip) Stderr.formatln(" . CRC for \"{}\": 0x{:x8}", info.name, crc);
-
-        // Rewind, and patch the header
-        auto final_pos = seeker.seek(0, seeker.Anchor.Current);
-        seeker.seek(header_pos);
-        patch_local_header(crc, compressed_size, uncompressed_size);
-
-        // Seek back to the end of the file, and we're done!
-        seeker.seek(final_pos);
-    }
-
-    /*
-     * Patches the local file header starting at the current output location
-     * with updated crc and size information.  Also updates the current last
-     * Entry.
-     */
-    void patch_local_header(uint crc_32, uint compressed_size,
-            uint uncompressed_size)
-    {
-        /* BUG: For some reason, this code won't compile.  No idea why... if
-         * you instantiate LFHD, it says that there is no "offsetof" property.
-         */
-        /+
-        alias LocalFileHeaderData LFHD;
-        static assert( LFHD.compressed_size.offsetof
-                == LFHD.crc_32.offsetof + 4 );
-        static assert( LFHD.uncompressed_size.offsetof
-                == LFHD.compressed_size.offsetof + 4 );
-        +/
-
-        // Don't forget we have to seek past the signature, too
-        // BUG: .offsetof is broken here
-        /+seeker.seek(LFHD.crc_32.offsetof+4, seeker.Anchor.Current);+/
-        seeker.seek(10+4, seeker.Anchor.Current);
-        write(output, crc_32);
-        write(output, compressed_size);
-        write(output, uncompressed_size);
-
-        with( entries[$-1] )
-        {
-            data.crc_32 = crc_32;
-            data.compressed_size = compressed_size;
-            data.uncompressed_size = uncompressed_size;
-        }
-    }
-
-    /*
-     * Generates and outputs a local file header from the given info block and
-     * compression method.  Note that the crc_32, compressed_size and
-     * uncompressed_size header fields will be set to zero, and must be
-     * patched.
-     */
-    void put_local_header(ZipEntryInfo info, Method method)
-    {
-        LocalFileHeader.Data data;
-
-        data.compression_method = fromMethod(method);
-        timeToDos(info.modified, data.modification_file_time,
-                                 data.modification_file_date);
-
-        put_local_header(data, info.name);
-    }
-
-    /*
-     * Writes the given local file header data and filename out to the output
-     * stream.  It also appends a new Entry with the data and filename.
-     */
-    void put_local_header(LocalFileHeaderData data,
-            char[] file_name)
-    {
-        auto f_name = Path.normalize(file_name);
-        auto p = Path.parse(f_name);
-
-        // Compute Zip version
-        if( data.extract_version == data.extract_version.max )
-        {
-
-            ushort zipver = 10;
-            void minver(ushort v) { zipver = v>zipver ? v : zipver; }
-
-            {
-                // Compression method
-                switch( data.compression_method )
-                {
-                    case 0: minver(10); break;
-                    case 8: minver(20); break;
-                    default:
-                        assert(false);
-                }
-
-                // File is a folder
-                if( f_name.length > 0 && f_name[$-1] == '/' )
-                    // Is a directory, not a real file
-                    minver(20);
-            }
-            data.extract_version = zipver;
-        }
-
-        /+// Encode filename
-        auto file_name_437 = utf8_to_cp437(file_name);
-        if( file_name_437 is null )
-            ZipException.fnencode;+/
-
-        /+// Set up file name length
-        if( file_name_437.length > ushort.max )
-            ZipException.fntoolong;
-
-        data.file_name_length = file_name_437.length;+/
-
-        LocalFileHeader header;
-        header.data = data;
-        if (p.isAbsolute)
-            f_name = f_name[p.root.length+1..$]; 
-        header.file_name = f_name;
-
-        // Write out the header and the filename
-        auto header_pos = seeker.seek(0, seeker.Anchor.Current);
-
-        write(output, LocalFileHeader.signature);
-        header.put(output);
-
-        // Save the header
-        assert( header_pos <= int.max );
-        Entry entry;
-        entry.data.fromLocal(header.data);
-        entry.filename = header.file_name;
-        entry.header_position = header_pos;
-        entry.data.relative_offset_of_local_header = cast(int) header_pos;
-        entries ~= entry;
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// ZipEntry
-
-/**
- * This class is used to represent a single entry in an archive.
- * Specifically, it combines meta-data about the file (see the info field)
- * along with the two basic operations on an entry: open and verify.
- */
-class ZipEntry
-{
-    /**
-     * Header information on the file.  See the ZipEntryInfo structure for
-     * more information.
-     */
-    ZipEntryInfo info;
-
-    /**
-     * Size (in bytes) of the file's uncompressed contents.
-     */
-    uint size()
-    {
-        return header.data.uncompressed_size;
-    }
-
-    /**
-     * Opens a stream for reading from the file.  The contents of this stream
-     * represent the decompressed contents of the file stored in the archive.
-     *
-     * You should not assume that the returned stream is seekable.
-     *
-     * Note that the returned stream may be safely closed without affecting
-     * the underlying archive stream.
-     *
-     * If the file has not yet been verified, then the stream will be checked
-     * as you read from it.  When the stream is either exhausted or closed,
-     * then the integrity of the file's data will be checked.  This means that
-     * if the file is corrupt, an exception will be thrown only after you have
-     * finished reading from the stream.  If you wish to make sure the data is
-     * valid before you read from the file, call the verify method.
-     */
-    InputStream open()
-    {
-        // If we haven't verified yet, wrap the stream in the appropriate
-        // decorators.
-        if( !verified )
-            return new ZipEntryVerifier(this, open_dg(header, false));
-
-        else
-            return open_dg(header, false);
-    }
-
-    /**
-     * Verifies the contents of this file by computing the CRC32 checksum,
-     * and comparing it against the stored one.  Throws an exception if the
-     * checksums do not match.
-     *
-     * Not valid on streamed Zip archives.
-     */
-    void verify()
-    {
-        // If we haven't verified the contents yet, just read everything in
-        // to trigger it.
-        auto s = open;
-        auto buffer = new ubyte[s.conduit.bufferSize];
-        while( s.read(buffer) != s.Eof )
-            {/*Do nothing*/}
-        s.close;
-    }
-
-    /**
-     * Creates a new, independent copy of this instance.
-     */
-    ZipEntry dup()
-    {
-        return new ZipEntry(header, open_dg);
-    }
-
-private:
-    /*
-     * Callback used to open the file.
-     */
-    alias InputStream delegate(FileHeader, bool raw) open_dg_t;
-    open_dg_t open_dg;
-
-    /*
-     * Raw ZIP header.
-     */
-    FileHeader header;
-
-    /*
-     * The flag used to keep track of whether the file's contents have been
-     * verified.
-     */
-    bool verified = false;
-
-    /*
-     * Opens a stream that does not perform any decompression or
-     * transformation of the file contents.  This is used internally by
-     * ZipWriter to perform fast zip to zip transfers without having to
-     * decompress and then recompress the contents.
-     *
-     * Note that because zip stores CRCs for the *uncompressed* data, this
-     * method currently does not do any verification.
-     */
-    InputStream open_raw()
-    {
-        return open_dg(header, true);
-    }
-
-    /*
-     * Creates a new ZipEntry from the FileHeader.
-     */
-    this(FileHeader header, open_dg_t open_dg)
-    {
-        this.reset(header, open_dg);
-    }
-
-    /*
-     * Resets the current instance with new values.
-     */
-    ZipEntry reset(FileHeader header, open_dg_t open_dg)
-    {
-        this.header = header;
-        this.open_dg = open_dg;
-        with( info )
-        {
-            name = Path.standard(header.file_name.dup);
-            dosToTime(header.data.modification_file_time,
-                      header.data.modification_file_date,
-                      modified);
-            comment = header.file_comment.dup;
-        }
-
-        this.verified = false;
-
-        return this;
-    }
-}
-
-/**
- * This structure contains various pieces of meta-data on a file.  The
- * contents of this structure may be safely mutated.
- *
- * This structure is also used to specify meta-data about a file when adding
- * it to an archive.
- */
-struct ZipEntryInfo
-{
-    /// Full path and file name of this file.
-    char[] name;
-    /// Modification timestamp.  If this is left uninitialised when passed to
-    /// a ZipWriter, it will be reset to the current system time.
-    Time modified = Time.min;
-    /// Comment on the file.
-    char[] comment;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Exceptions
-//
-
-/**
- * This is the base class from which all exceptions generated by this module
- * derive from.
- */
-class ZipException : Exception
-{
-    this(char[] msg) { super(msg); }
-
-private:
-    alias typeof(this) thisT;
-    static void opCall(char[] msg) { throw new ZipException(msg); }
-
-    static void badsig()
-    {
-        thisT("corrupt signature or unexpected section found");
-    }
-
-    static void badsig(char[] type)
-    {
-        thisT("corrupt "~type~" signature or unexpected section found");
-    }
-
-    static void incons(char[] name)
-    {
-        thisT("inconsistent headers for file \""~name~"\"; "
-                "archive is likely corrupted");
-    }
-
-    static void missingdir()
-    {
-        thisT("could not locate central archive directory; "
-                "file is corrupt or possibly not a Zip archive");
-    }
-
-    static void toomanyentries()
-    {
-        thisT("too many archive entries");
-    }
-
-    static void toolong()
-    {
-        thisT("archive is too long; limited to 4GB total");
-    }
-
-    static void cdtoolong()
-    {
-        thisT("central directory is too long; limited to 4GB total");
-    }
-
-    static void fntoolong()
-    {
-        thisT("file name too long; limited to 65,535 characters");
-    }
-
-    static void eftoolong()
-    {
-        thisT("extra field too long; limited to 65,535 characters");
-    }
-
-    static void cotoolong()
-    {
-        thisT("extra field too long; limited to 65,535 characters");
-    }
-
-    static void fnencode()
-    {
-        thisT("could not encode filename into codepage 437");
-    }
-
-    static void coencode()
-    {
-        thisT("could not encode comment into codepage 437");
-    }
-
-    static void tooold()
-    {
-        thisT("cannot represent dates before January 1, 1980");
-    }
-}
-
-/**
- * This exception is thrown if a ZipReader detects that a file's contents do
- * not match the stored checksum.
- */
-class ZipChecksumException : ZipException
-{
-    this(char[] name)
-    {
-        super("checksum failed on zip entry \""~name~"\"");
-    }
-
-private:
-    static void opCall(char[] name) { throw new ZipChecksumException(name); }
-}
-
-/**
- * This exception is thrown if you call get reader method when there are no
- * more files in the archive.
- */
-class ZipExhaustedException : ZipException
-{
-    this() { super("no more entries in archive"); }
-
-private:
-    static void opCall() { throw new ZipExhaustedException; }
-}
-
-/**
- * This exception is thrown if you attempt to read an archive that uses
- * features not supported by the reader.
- */
-class ZipNotSupportedException : ZipException
-{
-    this(char[] msg) { super(msg); }
-
-private:
-    alias ZipNotSupportedException thisT;
-
-    static void opCall(char[] msg)
-    {
-        throw new thisT(msg ~ " not supported");
-    }
-
-    static void spanned()
-    {
-        thisT("split and multi-disk archives");
-    }
-
-    static void zipver(ushort ver)
-    {
-        throw new thisT("zip format version "
-                ~Integer.toString(ver / 10)
-                ~"."
-                ~Integer.toString(ver % 10)
-                ~" not supported; maximum of version "
-                ~Integer.toString(MAX_EXTRACT_VERSION / 10)
-                ~"."
-                ~Integer.toString(MAX_EXTRACT_VERSION % 10)
-                ~" supported.");
-    }
-
-    static void flags()
-    {
-        throw new thisT("unknown or unsupported file flags enabled");
-    }
-
-    static void method(ushort m)
-    {
-        // Cheat here and work out what the method *actually* is
-        char[] ms;
-        switch( m )
-        {
-            case 0:
-            case 8:     assert(false); // supported
-
-            case 1:     ms = "Shrink"; break;
-            case 2:     ms = "Reduce (factor 1)"; break;
-            case 3:     ms = "Reduce (factor 2)"; break;
-            case 4:     ms = "Reduce (factor 3)"; break;
-            case 5:     ms = "Reduce (factor 4)"; break;
-            case 6:     ms = "Implode"; break;
-
-            case 9:     ms = "Deflate64"; break;
-            case 10:    ms = "TERSE (old)"; break;
-
-            case 12:    ms = "Bzip2"; break;
-            case 14:    ms = "LZMA"; break;
-
-            case 18:    ms = "TERSE (new)"; break;
-            case 19:    ms = "LZ77"; break;
-
-            case 97:    ms = "WavPack"; break;
-            case 98:    ms = "PPMd"; break;
-
-            default:    ms = "unknown";
-        }
-
-        thisT(ms ~ " compression method");
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Convenience methods
-
-void createArchive(char[] archive, Method method, char[][] files...)
-{
-    scope zw = new ZipBlockWriter(archive);
-    zw.method = method;
-
-    foreach( file ; files )
-    {
-        scope fp = new FilePath(file);
-        
-        ZipEntryInfo zi;
-        zi.name = file;
-        zi.modified = fp.modified;
-
-        zw.putFile(zi, file);
-    }
-
-    zw.finish;
-}
-
-void extractArchive(char[] archive, char[] dest)
-{
-    scope zr = new ZipBlockReader(archive);
-
-    foreach( entry ; zr )
-    {
-        // Skip directories
-        if( entry.info.name[$-1] == '/' ||
-            entry.info.name[$-1] == '\\') continue;
-
-        auto path = Path.join(dest, entry.info.name);
-        path = Path.normalize(path);
-
-        // Create the parent directory if necessary.
-        auto parent = Path.parse(path).parent;
-        if( !Path.exists(parent) )
-        {
-            Path.createPath(parent);
-        }
-
-        path = Path.native(path);
-
-        // Write out the file
-        scope fout = new File(path, File.WriteCreate);
-        fout.copy(entry.open);
-        fout.close;
-
-        // Update timestamps
-        auto oldTS = Path.timeStamps(path);
-        Path.timeStamps(path, oldTS.accessed, entry.info.modified);
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Private implementation stuff
-//
-
-private:
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Verification stuff
-
-/*
- * This class wraps an input stream, and computes the CRC as it passes
- * through.  On the event of either a close or EOF, it checks the CRC against
- * the one in the provided ZipEntry.  If they don't match, it throws an
- * exception.
- */
-
-class ZipEntryVerifier : InputStream
-{
-    this(ZipEntry entry, InputStream source)
-    in
-    {
-        assert( entry !is null );
-        assert( source !is null );
-    }
-    body
-    {
-        this.entry = entry;
-        this.digest = new Crc32;
-        this.source = new DigestInput(source, digest);
-    }
-
-    IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    long seek (long ofs, Anchor anchor = Anchor.Begin) 
-    {
-        return source.seek (ofs, anchor);
-    }
-
-    void close()
-    {
-        check;
-
-        this.source.close;
-        this.entry = null;
-        this.digest = null;
-        this.source = null;
-    }
-
-    size_t read(void[] dst)
-    {
-        auto bytes = source.read(dst);
-        if( bytes == IConduit.Eof )
-            check;
-        return bytes;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-    
-    override InputStream flush()
-    {
-        this.source.flush;
-        return this;
-    }
-
-private:
-    Crc32 digest;
-    InputStream source;
-    ZipEntry entry;
-
-    void check()
-    {
-        if( digest is null ) return;
-
-        auto crc = digest.crc32Digest;
-        delete digest;
-
-        if( crc != entry.header.data.crc_32 )
-            ZipChecksumException(entry.info.name);
-
-        else
-            entry.verified = true;
-    }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// IO functions
-
-/*
- * Really, seriously, read some bytes without having to go through a sodding
- * buffer.
- */
-void readExact(InputStream s, void[] dst)
-{
-    //Stderr.formatln("readExact(s, [0..{}])", dst.length);
-    while( dst.length > 0 )
-    {
-        auto octets = s.read(dst);
-        //Stderr.formatln(" . octets = {}", octets);
-        if( octets == -1 ) // Beware the dangers of MAGICAL THINKING
-            throw new Exception("unexpected end of stream");
-        dst = dst[octets..$];
-    }
-}
-
-/*
- * Really, seriously, write some bytes.
- */
-void writeExact(OutputStream s, void[] src)
-{
-    while( src.length > 0 )
-    {
-        auto octets = s.write(src);
-        if( octets == -1 )
-            throw new Exception("unexpected end of stream");
-        src = src[octets..$];
-    }
-}
-
-void write(T)(OutputStream s, T value)
-{
-    version( BigEndian ) swap(value);
-    writeExact(s, (&value)[0..1]);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Endian garbage
-
-void swapAll(T)(ref T data)
-{
-    static if( is(typeof(T.record_fields)) )
-        const fields = T.record_fields;
-    else
-        const fields = data.tupleof.length;
-
-    foreach( i,_ ; data.tupleof )
-    {
-        if( i == fields ) break;
-        swap(data.tupleof[i]);
-    }
-}
-
-void swap(T)(ref T data)
-{
-    static if( T.sizeof == 1 )
-        {}
-    else static if( T.sizeof == 2 )
-        ByteSwap.swap16(&data, 2);
-    else static if( T.sizeof == 4 )
-        ByteSwap.swap32(&data, 4);
-    else static if( T.sizeof == 8 )
-        ByteSwap.swap64(&data, 8);
-    else static if( T.sizeof == 10 )
-        ByteSwap.swap80(&data, 10);
-    else
-        static assert(false, "Can't swap "~T.stringof~"s.");
-}
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// IBM Code Page 437 stuff
-//
-
-const char[][] cp437_to_utf8_map_low = [
-    "\u0000"[], "\u263a",   "\u263b",   "\u2665",
-    "\u2666",   "\u2663",   "\u2660",   "\u2022",
-    "\u25d8",   "\u25cb",   "\u25d9",   "\u2642",
-    "\u2640",   "\u266a",   "\u266b",   "\u263c",
-
-    "\u25b6",   "\u25c0",   "\u2195",   "\u203c",
-    "\u00b6",   "\u00a7",   "\u25ac",   "\u21a8",
-    "\u2191",   "\u2193",   "\u2192",   "\u2190",
-    "\u221f",   "\u2194",   "\u25b2",   "\u25bc"
-];
-
-const char[][] cp437_to_utf8_map_high = [
-    "\u00c7"[], "\u00fc",   "\u00e9",   "\u00e2",
-    "\u00e4",   "\u00e0",   "\u00e5",   "\u00e7",
-    "\u00ea",   "\u00eb",   "\u00e8",   "\u00ef",
-    "\u00ee",   "\u00ec",   "\u00c4",   "\u00c5",
-
-    "\u00c9",   "\u00e6",   "\u00c6",   "\u00f4",
-    "\u00f6",   "\u00f2",   "\u00fb",   "\u00f9",
-    "\u00ff",   "\u00d6",   "\u00dc",   "\u00f8",
-    "\u00a3",   "\u00a5",   "\u20a7",   "\u0192",
-
-    "\u00e1",   "\u00ed",   "\u00f3",   "\u00fa",
-    "\u00f1",   "\u00d1",   "\u00aa",   "\u00ba",
-    "\u00bf",   "\u2310",   "\u00ac",   "\u00bd",
-    "\u00bc",   "\u00a1",   "\u00ab",   "\u00bb",
-
-    "\u2591",   "\u2592",   "\u2593",   "\u2502",
-    "\u2524",   "\u2561",   "\u2562",   "\u2556",
-    "\u2555",   "\u2563",   "\u2551",   "\u2557",
-    "\u255d",   "\u255c",   "\u255b",   "\u2510",
-
-    "\u2514",   "\u2534",   "\u252c",   "\u251c",
-    "\u2500",   "\u253c",   "\u255e",   "\u255f",
-    "\u255a",   "\u2554",   "\u2569",   "\u2566",
-    "\u2560",   "\u2550",   "\u256c",   "\u2567",
-
-    "\u2568",   "\u2564",   "\u2565",   "\u2559",
-    "\u2558",   "\u2552",   "\u2553",   "\u256b",
-    "\u256a",   "\u2518",   "\u250c",   "\u2588",
-    "\u2584",   "\u258c",   "\u2590",   "\u2580",
-    "\u03b1",   "\u00df",   "\u0393",   "\u03c0",
-    "\u03a3",   "\u03c3",   "\u00b5",   "\u03c4",
-    "\u03a6",   "\u0398",   "\u03a9",   "\u03b4",
-    "\u221e",   "\u03c6",   "\u03b5",   "\u2229",
-
-    "\u2261",   "\u00b1",   "\u2265",   "\u2264",
-    "\u2320",   "\u2321",   "\u00f7",   "\u2248",
-    "\u00b0",   "\u2219",   "\u00b7",   "\u221a",
-    "\u207f",   "\u00b2",   "\u25a0",   "\u00a0"
-];
-
-char[] cp437_to_utf8(ubyte[] s)
-{
-    foreach( i,c ; s )
-    {
-        if( (1 <= c && c <= 31) || c >= 127 )
-        {
-            /* Damn; we got a character not in ASCII.  Since this is the first
-             * non-ASCII character we found, copy everything up to this point
-             * into the output verbatim.  We'll allocate twice as much space
-             * as there are remaining characters to ensure we don't need to do
-             * any further allocations.
-             */
-            auto r = new char[i+2*(s.length-i)];
-            r[0..i] = cast(char[]) s[0..i];
-            size_t k=i; // current length
-
-            // We insert new characters at r[i+j+k]
-
-            foreach( d ; s[i..$] )
-            {
-                if( 32 <= d && d <= 126 || d == 0 )
-                {
-                    r[k++] = d;
-                }
-                else if( 1 <= d && d <= 31 )
-                {
-                    char[] repl = cp437_to_utf8_map_low[d];
-                    r[k..k+repl.length] = repl[];
-                    k += repl.length;
-                }
-                else if( d == 127 )
-                {
-                    char[] repl = "\u2302";
-                    r[k..k+repl.length] = repl[];
-                    k += repl.length;
-                }
-                else if( d > 127 )
-                {
-                    char[] repl = cp437_to_utf8_map_high[d-128];
-                    r[k..k+repl.length] = repl[];
-                    k += repl.length;
-                }
-                else
-                    assert(false);
-            }
-
-            return r[0..k];
-        }
-    }
-
-    /* If we got here, then all the characters in s are also in ASCII, which
-     * means it's also valid UTF-8; return the string unmodified.
-     */
-    return cast(char[]) s;
-}
-
-debug( UnitTest )
-{
-    unittest
-    {
-        char[] c(char[] s) { return cp437_to_utf8(cast(ubyte[]) s); }
-
-        auto s = c("Hi there \x01 old \x0c!");
-        assert( s == "Hi there \u263a old \u2640!", "\""~s~"\"" );
-        s = c("Marker \x7f and divide \xf6.");
-        assert( s == "Marker \u2302 and divide \u00f7.", "\""~s~"\"" );
-    }
-}
-
-const char[dchar] utf8_to_cp437_map;
-
-static this()
-{
-    utf8_to_cp437_map = [
-        '\u0000': '\x00', '\u263a': '\x01', '\u263b': '\x02', '\u2665': '\x03',
-        '\u2666': '\x04', '\u2663': '\x05', '\u2660': '\x06', '\u2022': '\x07',
-        '\u25d8': '\x08', '\u25cb': '\x09', '\u25d9': '\x0a', '\u2642': '\x0b',
-        '\u2640': '\x0c', '\u266a': '\x0d', '\u266b': '\x0e', '\u263c': '\x0f',
-
-        '\u25b6': '\x10', '\u25c0': '\x11', '\u2195': '\x12', '\u203c': '\x13',
-        '\u00b6': '\x14', '\u00a7': '\x15', '\u25ac': '\x16', '\u21a8': '\x17',
-        '\u2191': '\x18', '\u2193': '\x19', '\u2192': '\x1a', '\u2190': '\x1b',
-        '\u221f': '\x1c', '\u2194': '\x1d', '\u25b2': '\x1e', '\u25bc': '\x1f',
-
-        /*
-         * Printable ASCII range (well, most of it) is handled specially.
-         */
-
-        '\u00c7': '\x80', '\u00fc': '\x81', '\u00e9': '\x82', '\u00e2': '\x83',
-        '\u00e4': '\x84', '\u00e0': '\x85', '\u00e5': '\x86', '\u00e7': '\x87',
-        '\u00ea': '\x88', '\u00eb': '\x89', '\u00e8': '\x8a', '\u00ef': '\x8b',
-        '\u00ee': '\x8c', '\u00ec': '\x8d', '\u00c4': '\x8e', '\u00c5': '\x8f',
-
-        '\u00c9': '\x90', '\u00e6': '\x91', '\u00c6': '\x92', '\u00f4': '\x93',
-        '\u00f6': '\x94', '\u00f2': '\x95', '\u00fb': '\x96', '\u00f9': '\x97',
-        '\u00ff': '\x98', '\u00d6': '\x99', '\u00dc': '\x9a', '\u00f8': '\x9b',
-        '\u00a3': '\x9c', '\u00a5': '\x9d', '\u20a7': '\x9e', '\u0192': '\x9f',
-
-        '\u00e1': '\xa0', '\u00ed': '\xa1', '\u00f3': '\xa2', '\u00fa': '\xa3',
-        '\u00f1': '\xa4', '\u00d1': '\xa5', '\u00aa': '\xa6', '\u00ba': '\xa7',
-        '\u00bf': '\xa8', '\u2310': '\xa9', '\u00ac': '\xaa', '\u00bd': '\xab',
-        '\u00bc': '\xac', '\u00a1': '\xad', '\u00ab': '\xae', '\u00bb': '\xaf',
-
-        '\u2591': '\xb0', '\u2592': '\xb1', '\u2593': '\xb2', '\u2502': '\xb3',
-        '\u2524': '\xb4', '\u2561': '\xb5', '\u2562': '\xb6', '\u2556': '\xb7',
-        '\u2555': '\xb8', '\u2563': '\xb9', '\u2551': '\xba', '\u2557': '\xbb',
-        '\u255d': '\xbc', '\u255c': '\xbd', '\u255b': '\xbe', '\u2510': '\xbf',
-
-        '\u2514': '\xc0', '\u2534': '\xc1', '\u252c': '\xc2', '\u251c': '\xc3',
-        '\u2500': '\xc4', '\u253c': '\xc5', '\u255e': '\xc6', '\u255f': '\xc7',
-        '\u255a': '\xc8', '\u2554': '\xc9', '\u2569': '\xca', '\u2566': '\xcb',
-        '\u2560': '\xcc', '\u2550': '\xcd', '\u256c': '\xce', '\u2567': '\xcf',
-
-        '\u2568': '\xd0', '\u2564': '\xd1', '\u2565': '\xd2', '\u2559': '\xd3',
-        '\u2558': '\xd4', '\u2552': '\xd5', '\u2553': '\xd6', '\u256b': '\xd7',
-        '\u256a': '\xd8', '\u2518': '\xd9', '\u250c': '\xda', '\u2588': '\xdb',
-        '\u2584': '\xdc', '\u258c': '\xdd', '\u2590': '\xde', '\u2580': '\xdf',
-
-        '\u03b1': '\xe0', '\u00df': '\xe1', '\u0393': '\xe2', '\u03c0': '\xe3',
-        '\u03a3': '\xe4', '\u03c3': '\xe5', '\u00b5': '\xe6', '\u03c4': '\xe7',
-        '\u03a6': '\xe8', '\u0398': '\xe9', '\u03a9': '\xea', '\u03b4': '\xeb',
-        '\u221e': '\xec', '\u03c6': '\xed', '\u03b5': '\xee', '\u2229': '\xef',
-
-        '\u2261': '\xf0', '\u00b1': '\xf1', '\u2265': '\xf2', '\u2264': '\xf3',
-        '\u2320': '\xf4', '\u2321': '\xf5', '\u00f7': '\xf6', '\u2248': '\xf7',
-        '\u00b0': '\xf8', '\u2219': '\xf9', '\u00b7': '\xfa', '\u221a': '\xfb',
-        '\u207f': '\xfc', '\u00b2': '\xfd', '\u25a0': '\xfe', '\u00a0': '\xff'
-    ];
-}
-
-ubyte[] utf8_to_cp437(char[] s)
-{
-    foreach( i,dchar c ; s )
-    {
-        if( !((32 <= c && c <= 126) || c == 0) )
-        {
-            /* We got a character not in CP 437: we need to create a buffer to
-             * hold the new string.  Since UTF-8 is *always* larger than CP
-             * 437, we need, at most, an array of the same number of elements.
-             */
-            auto r = new ubyte[s.length];
-            r[0..i] = cast(ubyte[]) s[0..i];
-            size_t k=i;
-
-            foreach( dchar d ; s[i..$] )
-            {
-                if( 32 <= d && d <= 126 || d == 0 )
-                    r[k++] = d;
-
-                else if( d == '\u2302' )
-                    r[k++] = '\x7f';
-
-                else if( auto e_ptr = d in utf8_to_cp437_map )
-                    r[k++] = *e_ptr;
-
-                else
-                {
-                    throw new Exception("cannot encode character \""
-                            ~ Integer.toString(cast(uint)d)
-                            ~ "\" in codepage 437.");
-                }
-            }
-
-            return r[0..k];
-        }
-    }
-
-    // If we got here, then the entire string is printable ASCII, which just
-    // happens to *also* be valid CP 437!  Huzzah!
-    return cast(ubyte[]) s;
-}
-
-debug( UnitTest )
-{
-    unittest
-    {
-        alias cp437_to_utf8 x;
-        alias utf8_to_cp437 y;
-
-        ubyte[256] s;
-        foreach( i,ref c ; s )
-            c = i;
-
-        auto a = x(s);
-        auto b = y(a);
-        if(!( b == s ))
-        {
-            // Display list of characters that failed to convert as expected,
-            // and what value we got.
-            auto hex = "0123456789abcdef";
-            auto msg = "".dup;
-            foreach( i,ch ; b )
-            {
-                if( ch != i )
-                {
-                    msg ~= hex[i>>4];
-                    msg ~= hex[i&15];
-                    msg ~= " (";
-                    msg ~= hex[ch>>4];
-                    msg ~= hex[ch&15];
-                    msg ~= "), ";
-                }
-            }
-            msg ~= "failed.";
-
-            assert( false, msg );
-        }
-    }
-}
-
-/*
- * This is here to simplify the code elsewhere.
- */
-char[] utf8_to_utf8(ubyte[] s) { return cast(char[]) s; }
-ubyte[] utf8_to_utf8(char[] s) { return cast(ubyte[]) s; }
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-//
-// Date/time stuff
-
-void dosToTime(ushort dostime, ushort dosdate, out Time time)
-{
-    uint sec, min, hour, day, mon, year;
-    sec = (dostime & 0b00000_000000_11111) * 2;
-    min = (dostime & 0b00000_111111_00000) >> 5;
-    hour= (dostime & 0b11111_000000_00000) >> 11;
-    day = (dosdate & 0b0000000_0000_11111);
-    mon = (dosdate & 0b0000000_1111_00000) >> 5;
-    year=((dosdate & 0b1111111_0000_00000) >> 9) + 1980;
-
-    // This code rules!
-    time = Gregorian.generic.toTime(year, mon, day, hour, min, sec);
-}
-
-void timeToDos(Time time, out ushort dostime, out ushort dosdate)
-{
-    // Treat Time.min specially
-    if( time == Time.min )
-        time = WallClock.now;
-
-    // *muttering happily*
-    auto date = Gregorian.generic.toDate(time);
-    if( date.year < 1980 )
-        ZipException.tooold;
-
-    auto tod = time.time();
-    dostime = cast(ushort) (
-        (tod.seconds / 2)
-      | (tod.minutes << 5)
-      | (tod.hours   << 11));
-
-    dosdate = cast(ushort) (
-        (date.day)
-      | (date.month << 5)
-      | ((date.year - 1980) << 9));
-}
-
-// ************************************************************************** //
-// ************************************************************************** //
-// ************************************************************************** //
-
-// Dependencies
-private:
-
-import tango.io.device.Conduit : Conduit;
-
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Prerelease
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-//module tangox.io.stream.CounterStream;
-
-//import tango.io.device.Conduit : Conduit;
-//import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-
-/**
- * The counter stream classes are used to keep track of how many bytes flow
- * through a stream.
- *
- * To use them, simply wrap it around an existing stream.  The number of bytes
- * that have flowed through the wrapped stream may be accessed using the
- * count member.
- */
-class CounterInput : InputStream
-{
-    ///
-    this(InputStream input)
-    in
-    {
-        assert( input !is null );
-    }
-    body
-    {
-        this.source = input;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    long seek (long ofs, Anchor anchor = Anchor.Begin) 
-    {
-        return source.seek (ofs, anchor);
-    }
-
-    override void close()
-    {
-        source.close();
-        source = null;
-    }
-
-    override size_t read(void[] dst)
-    {
-        auto read = source.read(dst);
-        if( read != IConduit.Eof )
-            _count += read;
-        return read;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-
-    override InputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    ///
-    long count() { return _count; }
-
-private:
-    InputStream source;
-    long _count;
-}
-
-/// ditto
-class CounterOutput : OutputStream
-{
-    ///
-    this(OutputStream output)
-    in
-    {
-        assert( output !is null );
-    }
-    body
-    {
-        this.sink = output;
-    }
-
-    override IConduit conduit()
-    {
-        return sink.conduit;
-    }
-
-    OutputStream output()
-    {
-        return sink;
-    }
-
-    long seek (long ofs, Anchor anchor = Anchor.Begin) 
-    {
-        return sink.seek (ofs, anchor);
-    }
-
-    override void close()
-    {
-        sink.close();
-        sink = null;
-    }
-
-    override size_t write(void[] dst)
-    {
-        auto wrote = sink.write(dst);
-        if( wrote != IConduit.Eof )
-            _count += wrote;
-        return wrote;
-    }
-
-    override OutputStream copy(InputStream src, size_t max=-1)
-    {
-        Conduit.transfer(src, this, max);
-        return this;
-    }
-
-    override OutputStream flush()
-    {
-        sink.flush();
-        return this;
-    }
-
-  ///
-    long count() { return _count; }
-
-private:
-    OutputStream sink;
-    long _count;
-}
-
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Prerelease
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-//module tangox.io.stream.SliceStream;
-
-//import tango.io.device.Conduit : Conduit;
-//import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-
-/**
- * This stream can be used to provide stream-based access to a subset of
- * another stream.  It is akin to slicing an array.
- *
- * This stream fully supports seeking, and as such requires that the
- * underlying stream also support seeking.
- */
-class SliceSeekInputStream : InputStream
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    /**
-     * Create a new slice stream from the given source, covering the content
-     * starting at position begin, for length bytes.
-     */
-    this(InputStream source, long begin, long length)
-    in
-    {
-        assert( source !is null );
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-        assert( begin >= 0 );
-        assert( length >= 0 );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this.begin = begin;
-        this.length = length;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override void close()
-    {
-        source = null;
-        seeker = null;
-    }
-
-    override size_t read(void[] dst)
-    {
-        // If we're at the end of the slice, return eof
-        if( _position >= length )
-            return IConduit.Eof;
-
-        // Otherwise, make sure we don't try to read past the end of the slice
-        if( _position+dst.length > length )
-            dst.length = cast(size_t) (length-_position);
-
-        // Seek source stream to the appropriate location.
-        if( seeker.seek(0, Anchor.Current) != begin+_position )
-            seeker.seek(begin+_position, Anchor.Begin);
-
-        // Do the read
-        auto read = source.read(dst);
-        if( read == IConduit.Eof )
-            // If we got an Eof, we'll consider that a bug for the moment.
-            // TODO: proper exception
-            throw new Exception("unexpected end-of-stream");
-
-        _position += read;
-        return read;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-
-    override InputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        switch( anchor )
-        {
-            case Anchor.Begin:
-                _position = offset;
-                break;
-
-            case Anchor.Current:
-                _position += offset;
-                if( _position < 0 ) _position = 0;
-                break;
-
-            case Anchor.End:
-                _position = length+offset;
-                if( _position < 0 ) _position = 0;
-                break;
-
-            default:
-                assert(false);
-        }
-
-        return _position;
-    }
-
-private:
-    InputStream source;
-    InputStream seeker;
-
-    long _position, begin, length;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-        assert( begin >= 0 );
-        assert( length >= 0 );
-        assert( _position >= 0 );
-    }
-}
-
-/**
- * This stream can be used to provide stream-based access to a subset of
- * another stream.  It is akin to slicing an array.
- */
-class SliceInputStream : InputStream
-{
-    /**
-     * Create a new slice stream from the given source, covering the content
-     * starting at the current seek position for length bytes.
-     */
-    this(InputStream source, long length)
-    in
-    {
-        assert( source !is null );
-        assert( length >= 0 );
-    }
-    body
-    {
-        this.source = source;
-        this._length = length;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override void close()
-    {
-        source = null;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    long seek (long ofs, Anchor anchor = Anchor.Begin) 
-    {
-        return source.seek (ofs, anchor);
-    }
-
-    override size_t read(void[] dst)
-    {
-        // If we're at the end of the slice, return eof
-        if( _length <= 0 )
-            return IConduit.Eof;
-
-        // Otherwise, make sure we don't try to read past the end of the slice
-        if( dst.length > _length )
-            dst.length = cast(size_t) _length;
-
-        // Do the read
-        auto read = source.read(dst);
-        if( read == IConduit.Eof )
-            // If we got an Eof, we'll consider that a bug for the moment.
-            // TODO: proper exception
-            throw new Exception("unexpected end-of-stream");
-
-        _length -= read;
-        return read;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-
-    override InputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-private:
-    InputStream source;
-    long _length;
-
-    invariant
-    {
-        if( _length > 0 ) assert( source !is null );
-    }
-}
-
-/**
- * This stream can be used to provide stream-based access to a subset of
- * another stream.  It is akin to slicing an array.
- *
- * This stream fully supports seeking, and as such requires that the
- * underlying stream also support seeking.
- */
-class SliceSeekOutputStream : OutputStream
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    /**
-     * Create a new slice stream from the given source, covering the content
-     * starting at position begin, for length bytes.
-     */
-    this(OutputStream source, long begin, long length)
-    in
-    {
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-        assert( begin >= 0 );
-        assert( length >= 0 );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this.begin = begin;
-        this.length = length;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override void close()
-    {
-        source = null;
-        seeker = null;
-    }
-
-    size_t write(void[] src)
-    {
-        // If we're at the end of the slice, return eof
-        if( _position >= length )
-            return IConduit.Eof;
-
-        // Otherwise, make sure we don't try to write past the end of the
-        // slice
-        if( _position+src.length > length )
-            src.length = cast(size_t) (length-_position);
-
-        // Seek source stream to the appropriate location.
-        if( seeker.seek(0, Anchor.Current) != begin+_position )
-            seeker.seek(begin+_position, Anchor.Begin);
-
-        // Do the write
-        auto wrote = source.write(src);
-        if( wrote == IConduit.Eof )
-            // If we got an Eof, we'll consider that a bug for the moment.
-            // TODO: proper exception
-            throw new Exception("unexpected end-of-stream");
-
-        _position += wrote;
-        return wrote;
-    }
-
-    override OutputStream copy(InputStream src, size_t max=-1)
-    {
-        Conduit.transfer(src, this, max);
-        return this;
-    }
-
-    override OutputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    override OutputStream output()
-    {
-        return source;
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        switch( anchor )
-        {
-            case Anchor.Begin:
-                _position = offset;
-                break;
-
-            case Anchor.Current:
-                _position += offset;
-                if( _position < 0 ) _position = 0;
-                break;
-
-            case Anchor.End:
-                _position = length+offset;
-                if( _position < 0 ) _position = 0;
-                break;
-
-            default:
-                assert(false);
-        }
-
-        return _position;
-    }
-
-private:
-    OutputStream source;
-    OutputStream seeker;
-
-    long _position, begin, length;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-        assert( begin >= 0 );
-        assert( length >= 0 );
-        assert( _position >= 0 );
-    }
-}
-
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Prerelease
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-//module tangox.io.stream.WrapStream;
-
-//import tango.io.device.Conduit : Conduit;
-//import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-
-/**
- * This stream can be used to provide access to another stream.
- * Its distinguishing feature is that users cannot close the underlying
- * stream.
- *
- * This stream fully supports seeking, and as such requires that the
- * underlying stream also support seeking.
- */
-class WrapSeekInputStream : InputStream
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    /**
-     * Create a new wrap stream from the given source.
-     */
-    this(InputStream source)
-    in
-    {
-        assert( source !is null );
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this._position = seeker.seek(0, Anchor.Current);
-    }
-
-    /// ditto
-    this(InputStream source, long position)
-    in
-    {
-        assert( position >= 0 );
-    }
-    body
-    {
-        this(source);
-        this._position = position;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override void close()
-    {
-        source = null;
-        seeker = null;
-    }
-
-    override size_t read(void[] dst)
-    {
-        if( seeker.seek(0, Anchor.Current) != _position )
-            seeker.seek(_position, Anchor.Begin);
-
-        auto read = source.read(dst);
-        if( read != IConduit.Eof )
-            _position += read;
-
-        return read;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-
-    override InputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        seeker.seek(_position, Anchor.Begin);
-        return (_position = seeker.seek(offset, anchor));
-    }
-
-private:
-    InputStream source;
-    InputStream seeker;
-    long _position;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-        assert( _position >= 0 );
-    }
-}
-
-/**
- * This stream can be used to provide access to another stream.
- * Its distinguishing feature is that the users cannot close the underlying
- * stream.
- *
- * This stream fully supports seeking, and as such requires that the
- * underlying stream also support seeking.
- */
-class WrapSeekOutputStream : OutputStream
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    /**
-     * Create a new wrap stream from the given source.
-     */
-    this(OutputStream source)
-    in
-    {
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this._position = seeker.seek(0, Anchor.Current);
-    }
-
-    /// ditto
-    this(OutputStream source, long position)
-    in
-    {
-        assert( position >= 0 );
-    }
-    body
-    {
-        this(source);
-        this._position = position;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override void close()
-    {
-        source = null;
-        seeker = null;
-    }
-
-    size_t write(void[] src)
-    {
-        if( seeker.seek(0, Anchor.Current) != _position )
-            seeker.seek(_position, Anchor.Begin);
-
-        auto wrote = source.write(src);
-        if( wrote != IConduit.Eof )
-            _position += wrote;
-        return wrote;
-    }
-
-    override OutputStream copy(InputStream src, size_t max=-1)
-    {
-        Conduit.transfer(src, this, max);
-        return this;
-    }
-
-    override OutputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    override OutputStream output()
-    {
-        return source;
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        seeker.seek(_position, Anchor.Begin);
-        return (_position = seeker.seek(offset, anchor));
-    }
-
-private:
-    OutputStream source;
-    OutputStream seeker;
-    long _position;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-        assert( _position >= 0 );
-    }
-}
-
-
--- a/tango/io/vfs/ZipFolder.d
+++ /dev/null
@@ -1,2152 +0,0 @@
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    The Great Namechange: February 2008
-
-                Initial release: December 2007
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-module tango.io.vfs.ZipFolder;
-
-import Path = tango.io.Path;
-import tango.io.device.File : File;
-import tango.io.FilePath : FilePath;
-import tango.io.device.TempFile : TempFile;
-import tango.io.compress.Zip : ZipReader, ZipBlockReader,
-       ZipWriter, ZipBlockWriter, ZipEntry, ZipEntryInfo, Method;
-import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-import tango.io.vfs.model.Vfs : VfsFolder, VfsFolderEntry, VfsFile,
-       VfsFolders, VfsFiles, VfsFilter, VfsStats, VfsFilterInfo,
-       VfsInfo, VfsSync;
-import tango.time.Time : Time;
-
-debug( ZipFolder )
-{
-    import tango.io.Stdout : Stderr;
-}
-
-// This disables code that is causing heap corruption in Tango 0.99.3
-version = Bug_HeapCorruption;
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-private
-{
-    enum EntryType { Dir, File }
-   
-    /*
-     * Entries are what make up the internal tree that describes the
-     * filesystem of the archive.  Each Entry is either a directory or a file.
-     */
-    struct Entry
-    {
-        EntryType type;
-
-        union
-        {
-            DirEntry dir;
-            FileEntry file;
-        }
-
-        char[] fullname;
-        char[] name;
-
-        /+
-        invariant
-        {
-            assert( (type == EntryType.Dir)
-                 || (type == EntryType.File) );
-
-            assert( fullname.nz() );
-            assert( name.nz() );
-        }
-        +/
-
-        VfsFilterInfo vfsFilterInfo;
-
-        VfsInfo vfsInfo()
-        {
-            return &vfsFilterInfo;
-        }
-
-        /*
-         * Updates the VfsInfo structure for this entry.
-         */
-        void makeVfsInfo()
-        {
-            with( vfsFilterInfo )
-            {
-                // Cheat horribly here
-                name = this.name;
-                path = this.fullname[0..($-name.length+"/".length)];
-
-                folder = isDir;
-                bytes = folder ? 0 : fileSize;
-            }
-        }
-
-        bool isDir()
-        {
-            return (type == EntryType.Dir);
-        }
-
-        bool isFile()
-        {
-            return (type == EntryType.File);
-        }
-
-        ulong fileSize()
-        in
-        {
-            assert( type == EntryType.File );
-        }
-        body
-        {
-            if( file.zipEntry !is null )
-                return file.zipEntry.size;
-
-            else if( file.tempFile !is null )
-            {
-                assert( file.tempFile.length >= 0 );
-                return cast(ulong) file.tempFile.length;
-            }
-            else
-                return 0;
-        }
-
-        /*
-         * Opens a File Entry for reading.
-         *
-         * BUG: Currently, if a user opens a new or unmodified file for input,
-         * and then opens it for output, the two streams will be working with
-         * different underlying conduits.  This means that the result of
-         * openInput should probably be wrapped in some kind of switching
-         * stream that can update when the backing store for the file changes.
-         */
-        InputStream openInput()
-        in
-        {
-            assert( type == EntryType.File );
-        }
-        body
-        {
-            if( file.zipEntry !is null )
-            {
-                file.zipEntry.verify;
-                return file.zipEntry.open;
-            }
-            else if( file.tempFile !is null )
-                return new WrapSeekInputStream(file.tempFile, 0);
-
-            else
-               {
-               throw new Exception ("cannot open input stream for '"~fullname~"'");
-               //return new DummyInputStream;
-               }
-        }
-
-        /*
-         * Opens a file entry for output.
-         */
-        OutputStream openOutput()
-        in
-        {
-            assert( type == EntryType.File );
-        }
-        body
-        {
-            if( file.tempFile !is null )
-                return new WrapSeekOutputStream(file.tempFile);
-
-            else
-            {
-                // Ok; we need to make a temporary file to store output in.
-                // If we already have a zip entry, we need to dump that into
-                // the temp. file and remove the zipEntry.
-                if( file.zipEntry !is null )
-                {
-                    {
-                        auto zi = file.zipEntry.open;
-                        scope(exit) zi.close;
-    
-                        file.tempFile = new TempFile;
-                        file.tempFile.copy(zi).close;
-
-                        debug( ZipFolder )
-                            Stderr.formatln("Entry.openOutput: duplicated"
-                                    " temp file {} for {}",
-                                    file.tempFile, this.fullname);
-                    }
-
-                    // TODO: Copy file info if available
-
-                    file.zipEntry = null;
-                }
-                else
-                {
-                    // Otherwise, just make a new, blank temp file
-                    file.tempFile = new TempFile;
-
-                    debug( ZipFolder )
-                        Stderr.formatln("Entry.openOutput: created"
-                                " temp file {} for {}",
-                                file.tempFile, this.fullname);
-                }
-
-                assert( file.tempFile !is null );
-                return openOutput;
-            }
-        }
-
-        void dispose()
-        {
-            fullname = name = null;
-            
-            with( vfsFilterInfo )
-            {
-                name = path = null;
-            }
-
-            dispose_children;
-        }
-
-        void dispose_children()
-        {
-            switch( type )
-            {
-                case EntryType.Dir:
-                    auto keys = dir.children.keys;
-                    scope(exit) delete keys;
-                    foreach( k ; keys )
-                    {
-                        auto child = dir.children[k];
-                        child.dispose();
-                        dir.children.remove(k);
-                        delete child;
-                    }
-                    dir.children = dir.children.init;
-                    break;
-
-                case EntryType.File:
-                    if( file.zipEntry !is null )
-                    {
-                        // Don't really need to do anything here
-                        file.zipEntry = null;
-                    }
-                    else if( file.tempFile !is null )
-                    {
-                        // Detatch to destroy the physical file itself
-                        file.tempFile.detach();
-                        file.tempFile = null;
-                    }
-                    break;
-
-                default:
-                    debug( ZipFolder ) Stderr.formatln(
-                            "Entry.dispose_children: unknown type {}",
-                            type);
-                    assert(false);
-            }
-        }
-    }
-
-    struct DirEntry
-    {
-        Entry*[char[]] children;
-    }
-
-    struct FileEntry
-    {
-        ZipEntry zipEntry;
-        TempFile tempFile;
-
-        invariant
-        {
-            auto zn = zipEntry is null;
-            auto tn = tempFile is null;
-            assert( (zn && tn)
-          /* zn xor tn */ || (!(zn&&tn)&&(zn||tn)) );
-        }
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-/**
- * ZipFolder serves as the root object for all Zip archives in the VFS.
- * Presently, it can only open archives on the local filesystem.
- */
-class ZipFolder : ZipSubFolder
-{
-    /**
-     * Opens an archive from the local filesystem.  If the readonly argument
-     * is specified as true, then modification of the archive will be
-     * explicitly disallowed.
-     */
-    this(char[] path, bool readonly=false)
-    out { assert( valid ); }
-    body
-    {
-        debug( ZipFolder )
-            Stderr.formatln(`ZipFolder("{}", {})`, path, readonly);
-        this.resetArchive(path, readonly);
-        super(this, root);
-    }
-
-    /**
-     * Closes the archive, and releases all internal resources.  If the commit
-     * argument is true (the default), then changes to the archive will be
-     * flushed out to disk.  If false, changes will simply be discarded.
-     */
-    final override VfsFolder close(bool commit = true)
-    in { assert( valid ); }
-    body
-    {
-        debug( ZipFolder )
-            Stderr.formatln("ZipFolder.close({})",commit);
-
-        // MUTATE
-        if( commit ) sync;
-
-        // Close ZipReader
-        if( zr !is null )
-        {
-            zr.close();
-            delete zr;
-        }
-
-        // Destroy entries
-        root.dispose();
-        version( Bug_HeapCorruption )
-            root = null;
-        else
-            delete root;
-
-        return this;
-    }
-
-    /**
-     * Flushes all changes to the archive out to disk.
-     */
-    final override VfsFolder sync()
-    in { assert( valid ); }
-    out
-    {
-        assert( valid );
-        assert( !modified );
-    }
-    body
-    {
-        debug( ZipFolder )
-            Stderr("ZipFolder.sync()").newline;
-
-        if( !modified )
-            return this;
-
-version( ZipFolder_NonMutating )
-{
-        mutate_error("ZipFolder.sync");
-        assert(false);
-}
-else
-{
-        enforce_mutable;
-        
-        // First, we need to determine if we have any zip entries.  If we
-        // don't, then we can write directly to the path.  If there *are*
-        // zip entries, then we'll need to write to a temporary path instead.
-        OutputStream os;
-        TempFile tempFile;
-        scope(exit) if( tempFile !is null ) delete tempFile;
-
-        auto p = Path.parse (path);
-        foreach( file ; this.tree.catalog )
-        {
-            if( auto zf = cast(ZipFile) file )
-                if( zf.entry.file.zipEntry !is null )
-                {
-                    tempFile = new TempFile(p.path, TempFile.Permanent);
-                    os = tempFile;
-                    debug( ZipFolder )
-                        Stderr.formatln(" sync: created temp file {}",
-                                tempFile.path);
-                    break;
-                }
-        }
-
-        if( tempFile is null )
-        {
-            // Kill the current zip reader so we can re-open the file it's
-            // using.
-            if( zr !is null )
-            {
-                zr.close;
-                delete zr;
-            }
-
-            os = new File(path, File.WriteCreate);
-        }
-
-        // Now, we can create the archive.
-        {
-            scope zw = new ZipBlockWriter(os);
-            foreach( file ; this.tree.catalog )
-            {
-                auto zei = ZipEntryInfo(file.toString[1..$]);
-                // BUG: Passthru doesn't maintain compression for some
-                // reason...
-                if( auto zf = cast(ZipFile) file )
-                {
-                    if( zf.entry.file.zipEntry !is null )
-                        zw.putEntry(zei, zf.entry.file.zipEntry);
-                    else
-                        zw.putStream(zei, file.input);
-                }
-                else
-                    zw.putStream(zei, file.input);
-            }
-            zw.finish;
-        }
-
-        // With that done, we can free all our handles, etc.
-        debug( ZipFolder )
-            Stderr(" sync: close").newline;
-        this.close(/*commit*/ false);
-        os.close;
-
-        // If we wrote the archive into a temporary file, move that over the
-        // top of the old archive.
-        if( tempFile !is null )
-        {
-            debug( ZipFolder )
-                Stderr(" sync: destroying temp file").newline;
-
-            debug( ZipFolder )
-                Stderr.formatln(" sync: renaming {} to {}",
-                        tempFile, path);
-
-            Path.rename (tempFile.toString, path);
-        }
-
-        // Finally, re-open the archive so that we have all the nicely
-        // compressed files.
-        debug( ZipFolder )
-            Stderr(" sync: reset archive").newline;
-        this.resetArchive(path, readonly);
-        
-        debug( ZipFolder )
-            Stderr(" sync: reset folder").newline;
-        this.reset(this, root);
-
-        debug( ZipFolder )
-            Stderr(" sync: done").newline;
-
-        return this;
-}
-    }
-
-    /**
-     * Indicates whether the archive was opened for read-only access.  Note
-     * that in addition to the readonly constructor flag, this is also
-     * influenced by whether the file itself is read-only or not.
-     */
-    final bool readonly() { return _readonly; }
-
-    /**
-     * Allows you to read and specify the path to the archive.  The effect of
-     * setting this is to change where the archive will be written to when
-     * flushed to disk.
-     */
-    final char[] path() { return _path; }
-    final char[] path(char[] v) { return _path = v; } /// ditto
-
-private:
-    ZipReader zr;
-    Entry* root;
-    char[] _path;
-    bool _readonly;
-    bool modified = false;
-
-    final bool readonly(bool v) { return _readonly = v; }
-
-    final bool closed()
-    {
-        debug( ZipFolder )
-            Stderr("ZipFolder.closed()").newline;
-        return (root is null);
-    }
-
-    final bool valid()
-    {
-        debug( ZipFolder )
-            Stderr("ZipFolder.valid()").newline;
-        return !closed;
-    }
-
-    final OutputStream mutateStream(OutputStream source)
-    {
-        return new EventSeekOutputStream(source,
-                EventSeekOutputStream.Callbacks(
-                    null,
-                    null,
-                    &mutate_write,
-                    null));
-    }
-
-    void mutate_write(uint bytes, void[] src)
-    {
-        if( !(bytes == 0 || bytes == IConduit.Eof) )
-            this.modified = true;
-    }
-
-    void resetArchive(char[] path, bool readonly=false)
-    out { assert( valid ); }
-    body
-    {
-        debug( ZipFolder )
-            Stderr.formatln(`ZipFolder.resetArchive("{}", {})`, path, readonly);
-
-        debug( ZipFolder )
-            Stderr.formatln(" .. size of Entry: {0}, {0:x} bytes", Entry.sizeof);
-
-        this.path = path;
-        this.readonly = readonly;
-
-        // Make sure the modified flag is set appropriately
-        scope(exit) modified = false;
-
-        // First, create a root entry
-        root = new Entry;
-        root.type = EntryType.Dir;
-        root.fullname = root.name = "/";
-
-        // If the user allowed writing, also allow creating a new archive.
-        // Note that we MUST drop out here if the archive DOES NOT exist,
-        // since Path.isWriteable will throw an exception if called on a
-        // non-existent path.
-        if( !this.readonly && !Path.exists(path) )
-            return;
-
-        // Update readonly to reflect the write-protected status of the
-        // archive.
-        this.readonly = this.readonly || !Path.isWritable(path);
-
-        // Parse the contents of the archive
-        foreach( zipEntry ; zr )
-        {
-            // Normalise name
-            auto name = FilePath(zipEntry.info.name).standard.toString;
-
-            // If the last character is '/', treat as a directory and skip
-            // TODO: is there a better way of detecting this?
-            if( name[$-1] == '/' )
-                continue;
-
-            // Now, we need to locate the right spot to insert this entry.
-            {
-                // That's CURrent ENTity, not current OR currant...
-                Entry* curent = root;
-                char[] h,t;
-                headTail(name,h,t);
-                while( t.nz() )
-                {
-                    assert( curent.isDir );
-                    if( auto nextent = (h in curent.dir.children) )
-                        curent = *nextent;
-                    
-                    else
-                    {
-                        // Create new directory entry
-                        Entry* dirent = new Entry;
-                        dirent.type = EntryType.Dir;
-                        if( curent.fullname != "/" )
-                            dirent.fullname = curent.fullname ~ "/" ~ h;
-                        else
-                            dirent.fullname = "/" ~ h;
-                        dirent.name = dirent.fullname[$-h.length..$];
-
-                        // Insert into current entry
-                        curent.dir.children[dirent.name] = dirent;
-
-                        // Make it the new current entry
-                        curent = dirent;
-                    }
-
-                    headTail(t,h,t);
-                }
-
-                // Getting here means that t is empty, which means the final
-                // component of the path--the file name--is in h.  The entry
-                // of the containing directory is in curent.
-
-                // Make sure the file isn't already there (you never know!)
-                assert( !(h in curent.dir.children) );
-
-                // Create a new file entry for it.
-                {
-                    // BUG: Bug_HeapCorruption
-                    // with ZipTest, on the resetArchive operation, on
-                    // the second time through this next line, it erroneously
-                    // allocates filent 16 bytes lower than curent.  Entry
-                    // is *way* larger than 16 bytes, and this causes it to
-                    // zero-out the existing root element, which leads to
-                    // segfaults later on at line +12:
-                    //
-                    //      // Insert
-                    //      curent.dir.children[filent.name] = filent;
-
-                    Entry* filent = new Entry;
-                    filent.type = EntryType.File;
-                    if( curent.fullname != "/" )
-                        filent.fullname = curent.fullname ~ "/" ~ h;
-                    else
-                        filent.fullname = "/" ~ h;
-                    filent.name = filent.fullname[$-h.length..$];
-                    filent.file.zipEntry = zipEntry.dup;
-
-                    filent.makeVfsInfo;
-
-                    // Insert
-                    curent.dir.children[filent.name] = filent;
-                }
-            }
-        }
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-/**
- * This class represents a folder in an archive.  In addition to supporting
- * the sync operation, you can also use the archive member to get a reference
- * to the underlying ZipFolder instance.
- */
-class ZipSubFolder : VfsFolder, VfsSync
-{
-    ///
-    final char[] name()
-    in { assert( valid ); }
-    body
-    {
-        return entry.name;
-    }
-
-    ///
-    final override char[] toString()
-    in { assert( valid ); }
-    body
-    {
-        return entry.fullname;
-    }
-
-    ///
-    final VfsFile file(char[] path)
-    in
-    {
-        assert( valid );
-        assert( !Path.parse(path).isAbsolute );
-    }
-    body
-    {
-        auto fp = Path.parse(path);
-        auto dir = fp.path;
-        auto name = fp.file;
-
-        if (dir.length > 0 && '/' == dir[$-1]) {
-            dir = dir[0..$-1];
-        }
-		
-        // If the file is in another directory, then we need to look up that
-        // up first.
-        if( dir.nz() )
-        {
-            auto dir_ent = this.folder(dir);
-            auto dir_obj = dir_ent.open;
-            return dir_obj.file(name);
-        }
-        else
-        {
-            // Otherwise, we need to check and see whether the file is in our
-            // entry list.
-            if( auto file_entry = (name in this.entry.dir.children) )
-            {
-                // It is; create a new object for it.
-                return new ZipFile(archive, this.entry, *file_entry);
-            }
-            else
-            {
-                // Oh dear... return a holding object.
-                return new ZipFile(archive, this.entry, name);
-            }
-        }
-    }
-
-    ///
-    final VfsFolderEntry folder(char[] path)
-    in
-    {
-        assert( valid );
-        assert( !Path.parse(path).isAbsolute );
-    }
-    body
-    {
-        // Locate the folder in question.  We do this by "walking" the
-        // path components.  If we find a component that doesn't exist,
-        // then we create a ZipSubFolderEntry for the remainder.
-        Entry* curent = this.entry;
-
-        // h is the "head" of the path, t is the remainder.  ht is both
-        // joined together.
-        char[] h,t,ht;
-        ht = path;
-
-        do
-        {
-            // Split ht at the first path separator.
-            assert( ht.nz() );
-            headTail(ht,h,t);
-
-            // Look for a pre-existing subentry
-            auto subent = (h in curent.dir.children);
-            if( t.nz() && !!subent )
-            {
-                // Move to the subentry, and split the tail on the next
-                // iteration.
-                curent = *subent;
-                ht = t;
-            }
-            else
-                // If the next component doesn't exist, return a folder entry.
-                // If the tail is empty, return a folder entry as well (let
-                // the ZipSubFolderEntry do the last lookup.)
-                return new ZipSubFolderEntry(archive, curent, ht);
-        }
-        while( true )
-        //assert(false);
-    }
-
-    ///
-    final VfsFolders self()
-    in { assert( valid ); }
-    body
-    {
-        return new ZipSubFolderGroup(archive, this, false);
-    }
-
-    ///
-    final VfsFolders tree()
-    in { assert( valid ); }
-    body
-    {
-        return new ZipSubFolderGroup(archive, this, true);
-    }
-
-    ///
-    final int opApply(int delegate(ref VfsFolder) dg)
-    in { assert( valid ); }
-    body
-    {
-        int result = 0;
-
-        foreach( _,childEntry ; this.entry.dir.children )
-        {
-            if( childEntry.isDir )
-            {
-                VfsFolder childFolder = new ZipSubFolder(archive, childEntry);
-                if( (result = dg(childFolder)) != 0 )
-                    break;
-            }
-        }
-
-        return result;
-    }
-
-    ///
-    final VfsFolder clear()
-    in { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        mutate_error("VfsFolder.clear");
-        assert(false);
-}
-else
-{
-        // MUTATE
-        enforce_mutable;
-
-        // Disposing of the underlying entry subtree should do our job for us.
-        entry.dispose_children;
-        mutate;
-        return this;
-}
-    }
-
-    ///
-    final bool writable()
-    in { assert( valid ); }
-    body
-    {
-        return !archive.readonly;
-    }
-
-    /**
-     * Closes this folder object.  If commit is true, then the folder is
-     * sync'ed before being closed.
-     */
-    override VfsFolder close(bool commit = true)
-    in { assert( valid ); }
-    body
-    {
-        // MUTATE
-        if( commit ) sync;
-
-        // Just clean up our pointers
-        archive = null;
-        entry = null;
-        return this;
-    }
-
-    /**
-     * This will flush any changes to the archive to disk.  Note that this
-     * applies to the entire archive, not just this folder and its contents.
-     */
-    override VfsFolder sync()
-    in { assert( valid ); }
-    body
-    {
-        // MUTATE
-        archive.sync;
-        return this;
-    }
-
-    ///
-    final void verify(VfsFolder folder, bool mounting)
-    in { assert( valid ); }
-    body
-    {
-        auto zipfolder = cast(ZipSubFolder) folder;
-
-        if( mounting
-                && zipfolder !is null
-                && zipfolder.archive is archive )
-        {
-            auto src = this.toString;
-            auto dst = zipfolder.toString;
-
-            auto len = src.length > dst.length ? dst.length : src.length;
-
-            if( src[0..len] == dst[0..len] )
-                error(`folders "`~dst~`" and "`~src~`" in archive "`
-                        ~archive.path~`" overlap`);
-        }
-    }
-
-    /**
-     * Returns a reference to the underlying ZipFolder instance.
-     */
-    final ZipFolder archive() { return _archive; }
-
-private:
-    ZipFolder _archive;
-    Entry* entry;
-    VfsStats stats;
-
-    final ZipFolder archive(ZipFolder v) { return _archive = v; }
-
-    this(ZipFolder archive, Entry* entry)
-    {
-        this.reset(archive, entry);
-    }
-
-    final void reset(ZipFolder archive, Entry* entry)
-    in
-    {
-        assert( archive !is null );
-        assert( entry.isDir );
-    }
-    out { assert( valid ); }
-    body
-    {
-        this.archive = archive;
-        this.entry = entry;
-    }
-
-    final bool valid()
-    {
-        return( (archive !is null) && !archive.closed );
-    }
-
-    final void enforce_mutable()
-    in { assert( valid ); }
-    body
-    {
-        if( archive.readonly )
-            // TODO: exception
-            throw new Exception("cannot mutate a read-only Zip archive");
-    }
-
-    final void mutate()
-    in { assert( valid ); }
-    body
-    {
-        enforce_mutable;
-        archive.modified = true;
-    }
-
-    final ZipSubFolder[] folders(bool collect)
-    in { assert( valid ); }
-    body
-    {
-        ZipSubFolder[] folders;
-        stats = stats.init;
-
-        foreach( _,childEntry ; entry.dir.children )
-        {
-            if( childEntry.isDir )
-            {
-                if( collect ) folders ~= new ZipSubFolder(archive, childEntry);
-                ++ stats.folders;
-            }
-            else
-            {
-                assert( childEntry.isFile );
-                stats.bytes += childEntry.fileSize;
-                ++ stats.files;
-            }
-        }
-
-        return folders;
-    }
-
-    final Entry*[] files(ref VfsStats stats, VfsFilter filter = null)
-    in { assert( valid ); }
-    body
-    {
-        Entry*[] files;
-
-        foreach( _,childEntry ; entry.dir.children )
-        {
-            if( childEntry.isFile )
-                if( filter is null || filter(childEntry.vfsInfo) )
-                {
-                    files ~= childEntry;
-                    stats.bytes += childEntry.fileSize;
-                    ++stats.files;
-                }
-        }
-
-        return files;
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-/**
- * This class represents a file within an archive.
- */
-class ZipFile : VfsFile
-{
-    ///
-    final char[] name()
-    in { assert( valid ); }
-    body
-    {
-        if( entry ) return entry.name;
-        else        return name_;
-    }
-
-    ///
-    final override char[] toString()
-    in { assert( valid ); }
-    body
-    {
-        if( entry ) return entry.fullname;
-        else        return parent.fullname ~ "/" ~ name_;
-    }
-
-    ///
-    final bool exists()
-    in { assert( valid ); }
-    body
-    {
-        // If we've only got a parent and a name, this means we don't actually
-        // exist; EXISTENTIAL CRISIS TEIM!!!
-        return !!entry;
-    }
-
-    ///
-    final ulong size()
-    in { assert( valid ); }
-    body
-    {
-        if( exists )
-            return entry.fileSize;
-        else
-            error("ZipFile.size: cannot reliably determine size of a "
-                    "non-existent file");
-
-        assert(false);
-    }
-
-    ///
-    final VfsFile copy(VfsFile source)
-    in { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        mutate_error("ZipFile.copy");
-        assert(false);
-}
-else
-{
-        // MUTATE
-        enforce_mutable;
-
-        if( !exists ) this.create;
-        this.output.copy(source.input);
-
-        return this;
-}
-    }
-
-    ///
-    final VfsFile move(VfsFile source)
-    in { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        mutate_error("ZipFile.move");
-        assert(false);
-}
-else
-{
-        // MUTATE
-        enforce_mutable;
-
-        this.copy(source);
-        source.remove;
-
-        return this;
-}
-    }
-
-    ///
-    final VfsFile create()
-    in { assert( valid ); }
-    out { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        mutate_error("ZipFile.create");
-        assert(false);
-}
-else
-{
-        if( exists )
-            error("ZipFile.create: cannot create already existing file: "
-                    "this folder ain't big enough for the both of 'em");
-
-        // MUTATE
-        enforce_mutable;
-
-        auto entry = new Entry;
-        entry.type = EntryType.File;
-        entry.fullname = parent.fullname.dir_app(name);
-        entry.name = entry.fullname[$-name.length..$];
-        entry.makeVfsInfo;
-
-        assert( !(entry.name in parent.dir.children) );
-        parent.dir.children[entry.name] = entry;
-        this.reset(archive, parent, entry);
-        mutate;
-
-        // Done
-        return this;
-}
-    }
-
-    ///
-    final VfsFile create(InputStream stream)
-    in { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        mutate_error("ZipFile.create");
-        assert(false);
-}
-else
-{
-        create;
-        output.copy(stream).close;
-        return this;
-}
-    }
-
-    ///
-    final VfsFile remove()
-    in{ assert( valid ); }
-    out { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        mutate_error("ZipFile.remove");
-        assert(false);
-}
-else
-{
-        if( !exists )
-            error("ZipFile.remove: cannot remove non-existent file; "
-                    "rather redundant, really");
-
-        // MUTATE
-        enforce_mutable;
-
-        // Save the old name
-        auto old_name = name;
-
-        // Do the removal
-        assert( !!(name in parent.dir.children) );
-        parent.dir.children.remove(name);
-        entry.dispose;
-        entry = null;
-        mutate;
-
-        // Swap out our now empty entry for the name, so the file can be
-        // directly recreated.
-        this.reset(archive, parent, old_name);
-
-        return this;
-}
-    }
-
-    ///
-    final InputStream input()
-    in { assert( valid ); }
-    body
-    {
-        if( exists )
-            return entry.openInput;
-
-        else
-            error("ZipFile.input: cannot open non-existent file for input; "
-                    "results would not be very useful");
-
-        assert(false);
-    }
-
-    ///
-    final OutputStream output()
-    in { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutable )
-{
-        mutate_error("ZipFile.output");
-        assert(false);
-}
-else
-{
-        // MUTATE
-        enforce_mutable;
-        
-        // Don't call mutate; defer that until the user actually writes to or
-        // modifies the underlying stream.
-        return archive.mutateStream(entry.openOutput);
-}
-    }
-
-    ///
-    final VfsFile dup()
-    in { assert( valid ); }
-    body
-    {
-        if( entry )
-            return new ZipFile(archive, parent, entry);
-        else
-            return new ZipFile(archive, parent, name);
-    }
-
-    ///
-    final Time modified()
-    {
-        return entry.file.zipEntry.info.modified;
-    }
-    
-    private:
-    ZipFolder archive;
-    Entry* entry;
-
-    Entry* parent;
-    char[] name_;
-
-    this()
-    out { assert( !valid ); }
-    body
-    {
-    }
-
-    this(ZipFolder archive, Entry* parent, Entry* entry)
-    in
-    {
-        assert( archive !is null );
-        assert( parent );
-        assert( parent.isDir );
-        assert( entry );
-        assert( entry.isFile );
-        assert( parent.dir.children[entry.name] is entry );
-    }
-    out { assert( valid ); }
-    body
-    {
-        this.reset(archive, parent, entry);
-    }
-
-    this(ZipFolder archive, Entry* parent, char[] name)
-    in
-    {
-        assert( archive !is null );
-        assert( parent );
-        assert( parent.isDir );
-        assert( name.nz() );
-        assert( !(name in parent.dir.children) );
-    }
-    out { assert( valid ); }
-    body
-    {
-        this.reset(archive, parent, name);
-    }
-
-    final bool valid()
-    {
-        return( (archive !is null) && !archive.closed );
-    }
-
-    final void enforce_mutable()
-    in { assert( valid ); }
-    body
-    {
-        if( archive.readonly )
-            // TODO: exception
-            throw new Exception("cannot mutate a read-only Zip archive");
-    }
-
-    final void mutate()
-    in { assert( valid ); }
-    body
-    {
-        enforce_mutable;
-        archive.modified = true;
-    }
-
-    final void reset(ZipFolder archive, Entry* parent, Entry* entry)
-    in
-    {
-        assert( archive !is null );
-        assert( parent );
-        assert( parent.isDir );
-        assert( entry );
-        assert( entry.isFile );
-        assert( parent.dir.children[entry.name] is entry );
-    }
-    out { assert( valid ); }
-    body
-    {
-        this.parent = parent;
-        this.archive = archive;
-        this.entry = entry;
-        this.name_ = null;
-    }
-
-    final void reset(ZipFolder archive, Entry* parent, char[] name)
-    in
-    {
-        assert( archive !is null );
-        assert( parent );
-        assert( parent.isDir );
-        assert( name.nz() );
-        assert( !(name in parent.dir.children) );
-    }
-    out { assert( valid ); }
-    body
-    {
-        this.archive = archive;
-        this.parent = parent;
-        this.entry = null;
-        this.name_ = name;
-    }
-
-    final void close()
-    in { assert( valid ); }
-    out { assert( !valid ); }
-    body
-    {
-        archive = null;
-        parent = null;
-        entry = null;
-        name_ = null;
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-class ZipSubFolderEntry : VfsFolderEntry
-{
-    final VfsFolder open()
-    in { assert( valid ); }
-    body
-    {
-        auto entry = (name in parent.dir.children);
-        if( entry )
-            return new ZipSubFolder(archive, *entry);
-
-        else
-        {
-            // NOTE: this can be called with a multi-part path.
-            error("ZipSubFolderEntry.open: \""
-                    ~ parent.fullname ~ "/" ~ name
-                    ~ "\" does not exist");
-
-            assert(false);
-        }
-    }
-
-    final VfsFolder create()
-    in { assert( valid ); }
-    body
-    {
-version( ZipFolder_NonMutating )
-{
-        // TODO: different exception if folder exists (this operation is
-        // currently invalid either way...)
-        mutate_error("ZipSubFolderEntry.create");
-        assert(false);
-}
-else
-{
-        // MUTATE
-        enforce_mutable;
-
-        // If the folder exists, we can't really create it, now can we?
-        if( this.exists )
-            error("ZipSubFolderEntry.create: cannot create folder that already "
-                    "exists, and believe me, I *tried*");
-        
-        // Ok, I suppose I can do this for ya...
-        auto entry = new Entry;
-        entry.type = EntryType.Dir;
-        entry.fullname = parent.fullname.dir_app(name);
-        entry.name = entry.fullname[$-name.length..$];
-        entry.makeVfsInfo;
-
-        assert( !(entry.name in parent.dir.children) );
-        parent.dir.children[entry.name] = entry;
-        mutate;
-
-        // Done
-        return new ZipSubFolder(archive, entry);
-}
-    }
-
-    final bool exists()
-    in { assert( valid ); }
-    body
-    {
-        return !!(name in parent.dir.children);
-    }
-
-private:
-    ZipFolder archive;
-    Entry* parent;
-    char[] name;
-
-    this(ZipFolder archive, Entry* parent, char[] name)
-    in
-    {
-        assert( archive !is null );
-        assert( parent.isDir );
-        assert( name.nz() );
-        assert( name.single_path_part() );
-    }
-    out { assert( valid ); }
-    body
-    {
-        this.archive = archive;
-        this.parent = parent;
-        this.name = name;
-    }
-
-    final bool valid()
-    {
-        return (archive !is null) && !archive.closed;
-    }
-    
-    final void enforce_mutable()
-    in { assert( valid ); }
-    body
-    {
-        if( archive.readonly )
-            // TODO: exception
-            throw new Exception("cannot mutate a read-only Zip archive");
-    }
-
-    final void mutate()
-    in { assert( valid ); }
-    body
-    {
-        enforce_mutable;
-        archive.modified = true;
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-class ZipSubFolderGroup : VfsFolders
-{
-    final int opApply(int delegate(ref VfsFolder) dg)
-    in { assert( valid ); }
-    body
-    {
-        int result = 0;
-
-        foreach( folder ; members )
-        {
-            VfsFolder x = folder;
-            if( (result = dg(x)) != 0 )
-                break;
-        }
-
-        return result;
-    }
-
-    final uint files()
-    in { assert( valid ); }
-    body
-    {
-        uint files = 0;
-
-        foreach( folder ; members )
-            files += folder.stats.files;
-
-        return files;
-    }
-
-    final uint folders()
-    in { assert( valid ); }
-    body
-    {
-        return members.length;
-    }
-
-    final uint entries()
-    in { assert( valid ); }
-    body
-    {
-        return files + folders;
-    }
-
-    final ulong bytes()
-    in { assert( valid ); }
-    body
-    {
-        ulong bytes = 0;
-
-        foreach( folder ; members )
-            bytes += folder.stats.bytes;
-
-        return bytes;
-    }
-
-    final VfsFolders subset(char[] pattern)
-    in { assert( valid ); }
-    body
-    {
-        ZipSubFolder[] set;
-
-        foreach( folder ; members )
-            if( Path.patternMatch(folder.name, pattern) )
-                set ~= folder;
-
-        return new ZipSubFolderGroup(archive, set);
-    }
-
-    final VfsFiles catalog(char[] pattern)
-    in { assert( valid ); }
-    body
-    {
-        bool filter (VfsInfo info)
-        {
-                return Path.patternMatch(info.name, pattern);
-        }
-
-        return catalog (&filter);
-    }
-
-    final VfsFiles catalog(VfsFilter filter = null)
-    in { assert( valid ); }
-    body
-    {
-        return new ZipFileGroup(archive, this, filter);
-    }
-
-private:
-    ZipFolder archive;
-    ZipSubFolder[] members;
-
-    this(ZipFolder archive, ZipSubFolder root, bool recurse)
-    out { assert( valid ); }
-    body
-    {
-        this.archive = archive;
-        members = root ~ scan(root, recurse);
-    }
-
-    this(ZipFolder archive, ZipSubFolder[] members)
-    out { assert( valid ); }
-    body
-    {
-        this.archive = archive;
-        this.members = members;
-    }
-
-    final bool valid()
-    {
-        return (archive !is null) && !archive.closed;
-    }
-
-    final ZipSubFolder[] scan(ZipSubFolder root, bool recurse)
-    in { assert( valid ); }
-    body
-    {
-        auto folders = root.folders(recurse);
-
-        if( recurse )
-            foreach( child ; folders )
-                folders ~= scan(child, recurse);
-
-        return folders;
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-class ZipFileGroup : VfsFiles
-{
-    final int opApply(int delegate(ref VfsFile) dg)
-    in { assert( valid ); }
-    body
-    {
-        int result = 0;
-        auto file = new ZipFile;
-
-        foreach( entry ; group )
-        {
-            file.reset(archive,entry.parent,entry.entry);
-            VfsFile x = file;
-            if( (result = dg(x)) != 0 )
-                break;
-        }
-
-        return result;
-    }
-
-    final uint files()
-    in { assert( valid ); }
-    body
-    {
-        return group.length;
-    }
-
-    final ulong bytes()
-    in { assert( valid ); }
-    body
-    {
-        return stats.bytes;
-    }
-
-private:
-    ZipFolder archive;
-    FileEntry[] group;
-    VfsStats stats;
-
-    struct FileEntry
-    {
-        Entry* parent;
-        Entry* entry;
-    }
-
-    this(ZipFolder archive, ZipSubFolderGroup host, VfsFilter filter)
-    out { assert( valid ); }
-    body
-    {
-        this.archive = archive;
-        foreach( folder ; host.members )
-            foreach( file ; folder.files(stats, filter) )
-                group ~= FileEntry(folder.entry, file);
-    }
-
-    final bool valid()
-    {
-        return (archive !is null) && !archive.closed;
-    }
-}
-
-// ************************************************************************ //
-// ************************************************************************ //
-
-private:
-
-void error(char[] msg)
-{
-    throw new Exception(msg);
-}
-
-void mutate_error(char[] method)
-{
-    error(method ~ ": mutating the contents of a ZipFolder "
-            "is not supported yet; terribly sorry");
-}
-
-bool nz(char[] s)
-{
-    return s.length > 0;
-}
-
-bool zero(char[] s)
-{
-    return s.length == 0;
-}
-
-bool single_path_part(char[] s)
-{
-    foreach( c ; s )
-        if( c == '/' ) return false;
-    return true;
-}
-
-char[] dir_app(char[] dir, char[] name)
-{
-    return dir ~ (dir[$-1]!='/' ? "/" : "") ~ name;
-}
-
-void headTail(char[] path, out char[] head, out char[] tail)
-{
-    foreach( i,dchar c ; path[1..$] )
-        if( c == '/' )
-        {
-            head = path[0..i+1];
-            tail = path[i+2..$];
-            return;
-        }
-
-    head = path;
-    tail = null;
-}
-
-unittest
-{
-    char[] h,t;
-
-    headTail("/a/b/c", h, t);
-    assert( h == "/a" );
-    assert( t == "b/c" );
-
-    headTail("a/b/c", h, t);
-    assert( h == "a" );
-    assert( t == "b/c" );
-
-    headTail("a/", h, t);
-    assert( h == "a" );
-    assert( t == "" );
-
-    headTail("a", h, t);
-    assert( h == "a" );
-    assert( t == "" );
-}
-
-// ************************************************************************** //
-// ************************************************************************** //
-// ************************************************************************** //
-
-// Dependencies
-private:
-import tango.io.device.Conduit : Conduit;
-
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Prerelease
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-//module tangox.io.stream.DummyStream;
-
-//import tango.io.device.Conduit : Conduit;
-//import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-
-/**
- * The dummy stream classes are used to provide simple, empty stream objects
- * where one is required, but none is available.
- *
- * Note that, currently, these classes return 'null' for the underlying
- * conduit, which will likely break code which expects streams to have an
- * underlying conduit.
- */
-private deprecated class DummyInputStream : InputStream // IConduit.Seek
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    override InputStream input() {return null;}
-    override IConduit conduit() { return null; }
-    override void close() {}
-    override size_t read(void[] dst) { return IConduit.Eof; }
-    override InputStream flush() { return this; }
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-    override long seek(long offset, Anchor anchor = cast(Anchor)0) { return 0; }
-}
-
-/// ditto
-private deprecated class DummyOutputStream : OutputStream //, IConduit.Seek
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    override OutputStream output() {return null;}
-    override IConduit conduit() { return null; }
-    override void close() {}
-    override size_t write(void[] src) { return IConduit.Eof; }
-    override OutputStream copy(InputStream src, size_t max=-1)
-    {
-        Conduit.transfer(src, this, max);
-        return this;
-    }
-    override OutputStream flush() { return this; }
-    override long seek(long offset, Anchor anchor = cast(Anchor)0) { return 0; }
-}
-
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Prerelease
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-//module tangox.io.stream.EventStream;
-
-//import tango.io.device.Conduit : Conduit;
-//import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-
-/**
- * The event stream classes are designed to allow you to receive feedback on
- * how a stream chain is being used.  This is done through the use of
- * delegate callbacks which are invoked just before the associated method is
- * complete.
- */
-class EventSeekInputStream : InputStream //, IConduit.Seek
-{
-    ///
-    struct Callbacks
-    {
-        void delegate()                     close; ///
-        void delegate()                     clear; ///
-        void delegate(uint, void[])         read; ///
-        void delegate(long, long, Anchor)   seek; ///
-    }
-
-    //alias IConduit.Seek.Anchor Anchor;
-
-    ///
-    this(InputStream source, Callbacks callbacks)
-    in
-    {
-        assert( source !is null );
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this.callbacks = callbacks;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    override void close()
-    {
-        source.close;
-        source = null;
-        seeker = null;
-        if( callbacks.close ) callbacks.close();
-    }
-
-    override size_t read(void[] dst)
-    {
-        auto result = source.read(dst);
-        if( callbacks.read ) callbacks.read(result, dst);
-        return result;
-    }
-
-    override InputStream flush()
-    {
-        source.flush();
-        if( callbacks.clear ) callbacks.clear();
-        return this;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        auto result = seeker.seek(offset, anchor);
-        if( callbacks.seek ) callbacks.seek(result, offset, anchor);
-        return result;
-    }
-
-private:
-    InputStream source;
-    InputStream seeker; //IConduit.Seek seeker;
-    Callbacks callbacks;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-    }
-}
-
-/// ditto
-class EventSeekOutputStream : OutputStream //, IConduit.Seek
-{
-    ///
-    struct Callbacks
-    {
-        void delegate()                     close; ///
-        void delegate()                     flush; ///
-        void delegate(uint, void[])         write; ///
-        void delegate(long, long, Anchor)   seek; ///
-    }
-
-    //alias IConduit.Seek.Anchor Anchor;
-
-    ///
-    this(OutputStream source, Callbacks callbacks)
-    in
-    {
-        assert( source !is null );
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this.callbacks = callbacks;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override OutputStream output()
-    {
-        return source;
-    }
-
-    override void close()
-    {
-        source.close;
-        source = null;
-        seeker = null;
-        if( callbacks.close ) callbacks.close();
-    }
-
-    override size_t write(void[] dst)
-    {
-        auto result = source.write(dst);
-        if( callbacks.write ) callbacks.write(result, dst);
-        return result;
-    }
-
-    override OutputStream flush()
-    {
-        source.flush();
-        if( callbacks.flush ) callbacks.flush();
-        return this;
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        auto result = seeker.seek(offset, anchor);
-        if( callbacks.seek ) callbacks.seek(result, offset, anchor);
-        return result;
-    }
-
-    override OutputStream copy(InputStream src, size_t max=-1)
-    {
-        Conduit.transfer(src, this, max);
-        return this;
-    }
-
-private:
-    OutputStream source;
-    OutputStream seeker; //IConduit.Seek seeker;
-    Callbacks callbacks;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-    }
-}
-
-/*******************************************************************************
-
-    copyright:  Copyright © 2007 Daniel Keep.  All rights reserved.
-
-    license:    BSD style: $(LICENSE)
-
-    version:    Prerelease
-
-    author:     Daniel Keep
-
-*******************************************************************************/
-
-//module tangox.io.stream.WrapStream;
-
-//import tango.io.device.Conduit : Conduit;
-//import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
-
-/**
- * This stream can be used to provide access to another stream.
- * Its distinguishing feature is that users cannot close the underlying
- * stream.
- *
- * This stream fully supports seeking, and as such requires that the
- * underlying stream also support seeking.
- */
-class WrapSeekInputStream : InputStream //, IConduit.Seek
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    /**
-     * Create a new wrap stream from the given source.
-     */
-    this(InputStream source)
-    in
-    {
-        assert( source !is null );
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this._position = seeker.seek(0, Anchor.Current);
-    }
-
-    /// ditto
-    this(InputStream source, long position)
-    in
-    {
-        assert( position >= 0 );
-    }
-    body
-    {
-        this(source);
-        this._position = position;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    InputStream input()
-    {
-        return source;
-    }
-
-    override void close()
-    {
-        source = null;
-        seeker = null;
-    }
-
-    override size_t read(void[] dst)
-    {
-        if( seeker.seek(0, Anchor.Current) != _position )
-            seeker.seek(_position, Anchor.Begin);
-
-        auto read = source.read(dst);
-        if( read != IConduit.Eof )
-            _position += read;
-
-        return read;
-    }
-
-    override InputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    override void[] load(size_t max=-1)
-    {
-        return Conduit.load(this, max);
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        seeker.seek(_position, Anchor.Begin);
-        return (_position = seeker.seek(offset, anchor));
-    }
-
-private:
-    InputStream source;
-    InputStream seeker; //IConduit.Seek seeker;
-    long _position;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-        assert( _position >= 0 );
-    }
-}
-
-/**
- * This stream can be used to provide access to another stream.
- * Its distinguishing feature is that the users cannot close the underlying
- * stream.
- *
- * This stream fully supports seeking, and as such requires that the
- * underlying stream also support seeking.
- */
-class WrapSeekOutputStream : OutputStream//, IConduit.Seek
-{
-    //alias IConduit.Seek.Anchor Anchor;
-
-    /**
-     * Create a new wrap stream from the given source.
-     */
-    this(OutputStream source)
-    in
-    {
-        assert( (cast(IConduit.Seek) source.conduit) !is null );
-    }
-    body
-    {
-        this.source = source;
-        this.seeker = source; //cast(IConduit.Seek) source;
-        this._position = seeker.seek(0, Anchor.Current);
-    }
-
-    /// ditto
-    this(OutputStream source, long position)
-    in
-    {
-        assert( position >= 0 );
-    }
-    body
-    {
-        this(source);
-        this._position = position;
-    }
-
-    override IConduit conduit()
-    {
-        return source.conduit;
-    }
-
-    override OutputStream output()
-    {
-        return source;
-    }
-
-    override void close()
-    {
-        source = null;
-        seeker = null;
-    }
-
-    size_t write(void[] src)
-    {
-        if( seeker.seek(0, Anchor.Current) != _position )
-            seeker.seek(_position, Anchor.Begin);
-
-        auto wrote = source.write(src);
-        if( wrote != IConduit.Eof )
-            _position += wrote;
-        return wrote;
-    }
-
-    override OutputStream copy(InputStream src, size_t max=-1)
-    {
-        Conduit.transfer(src, this, max);
-        return this;
-    }
-
-    override OutputStream flush()
-    {
-        source.flush();
-        return this;
-    }
-
-    override long seek(long offset, Anchor anchor = cast(Anchor)0)
-    {
-        seeker.seek(_position, Anchor.Begin);
-        return (_position = seeker.seek(offset, anchor));
-    }
-
-private:
-    OutputStream source;
-    OutputStream seeker; //IConduit.Seek seeker;
-    long _position;
-
-    invariant
-    {
-        assert( cast(Object) source is cast(Object) seeker );
-        assert( _position >= 0 );
-    }
-}
-
-