File: Base\MS\Internal\IO\Packaging\streamingZipPartStream.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//------------------------------------------------------------------------------
//  Microsoft Avalon
//  Copyright (c) Microsoft Corporation, 2005
//
//  File:           StreamingZipPartStream.cs
//
//  Description:    The class StreamingZipPartStream is used to create a sequence of
//                  piece streams in order to implement streaming production of packages.
//
//  History:        05/15/05 - johnlarc - Initial implementation.
//------------------------------------------------------------------------------
 
using System;
using System.IO;
using System.IO.Packaging;                  // For ZipPackagePart, etc.
using MS.Internal.IO.Zip;                   // For ZipFileInfo.
using System.Windows;                       // for ExceptionStringTable
 
using MS.Internal;                          // for Invariant
using MS.Internal.WindowsBase;
 
namespace MS.Internal.IO.Packaging
{
    /// <summary>
	/// The class StreamingZipPartStream is used to create a sequence of
    /// piece streams in order to implement streaming production of packages.
    /// </summary>
    /// <remarks>
    /// This class is defined for the benefit of ZipPackage, ZipPackagePart and
    /// InternalRelationshipCollection.
    /// Although it is quite specialized, it would hardly make sense to nest its definition in any
    /// of these clases.
    /// </remarks>
    internal class StreamingZipPartStream : Stream
    {
        #region Constructors
 
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
       /// <summary>
        /// Build a System.IO.Stream to create a multi-piece (i.e. interleaved) part.
        /// Does not require a part, but a proper part name (not a piece name), and a ZipArchive.
        /// </summary>
        internal StreamingZipPartStream(
            string          partName,
            ZipArchive      zipArchive,
            CompressionMethodEnum compressionMethod,
            DeflateOptionEnum deflateOption,
            FileMode        mode,
            FileAccess      access)
        {
            // Right now, only production is supported in streaming mode.
            if (!(   (mode == FileMode.Create || mode == FileMode.CreateNew)
                  && access == FileAccess.Write) )
            {
                throw new NotSupportedException(SR.Get(SRID.OnlyStreamingProductionIsSupported));
            }
 
            _partName = partName;
            _archive = zipArchive;
            _compressionMethod = compressionMethod;
            _deflateOption = deflateOption;
            _mode = mode;
            _access = access;
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        /// <summary>
        /// Return the bytes requested.
        /// </summary>
        /// <param name="buffer">Destination buffer.</param>
        /// <param name="offset">
        /// The zero-based byte offset in buffer at which to begin storing the data read
        /// from the current stream.
        /// </param>
        /// <param name="count">How many bytes requested.</param>
        /// <returns>How many bytes were written into buffer.</returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException(SR.Get(SRID.OnlyWriteOperationsAreSupportedInStreamingCreation));
        }
 
        /// <summary>
        /// Seek
        /// </summary>
        /// <param name="offset">Offset in byte.</param>
        /// <param name="origin">Offset origin (start, current, or end).</param>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException(SR.Get(SRID.OnlyWriteOperationsAreSupportedInStreamingCreation));
        }
 
        /// <summary>
        /// SetLength
        /// </summary>
        public override void SetLength(long newLength)
        {
            throw new InvalidOperationException(SR.Get(SRID.OperationViolatesWriteOnceSemantics, "SetLength"));
        }
 
        /// <summary>
        /// Write. Delegate to the current piece stream.
        /// Lazily create the Zip item since we do not know what name to create it
        /// under until a write or a close occurs.
        /// </summary>
        public override void Write(byte[] buffer, int offset, int count)
        {
            CheckClosed();
 
            // We now know we're creating a non-empty piece, so it's OK to give
            // it a non-terminal name.
            EnsurePieceStream(false /* not last piece */);
            _pieceStream.Write(buffer, offset, count);
        }
 
        /// <summary>
        /// Close the current piece stream and increment the piece number
        /// to allow on-demand creation of the next piece stream in Write
        /// or Close.
        /// </summary>
        /// <remarks>Pass through the Flush calls because there is no need to 
        /// generate a new Piece if we are writing to a single, enormouse stream.</remarks>
        public override void Flush()
        {
            CheckClosed();
 
            // _pieceStream will be null if there's been no write since the last flush.
            if (_pieceStream != null)
            {
                // If CanWrite is false, we know that our underlying stream was closed by ZipIO layer
                // as a part of its logic.  Therefore, we need a new Piece.
                if (_pieceStream.CanWrite)
                    _pieceStream.Flush();
            }
        }
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// Is stream readable?
        /// </summary>
        /// <remarks>
        /// Here, the assumption, as in all capability tests, is that the status of
        /// the current piece reflects the status of all pieces for the part.
        /// This is justified by the fact that (i) all piece streams are opened with the same
        /// parameters against the same archive and (ii) the current piece stream cannot get
        /// closed unless the whole part stream is closed.
        /// </remarks>
        public override bool CanRead
        {
            get
            {
                return false;
            }
        }
 
        /// <summary>
        /// Is stream seekable?
        /// </summary>
        /// <remarks>
        /// Here, the assumption, as in all capability tests, is that the status of
        /// the current piece reflects the status of all pieces for the part.
        /// This is justified by the fact that (i) all piece streams are opened with the same
        /// parameters against the same archive and (ii) the current piece stream cannot get
        /// closed unless the whole part stream is closed.
        /// </remarks>
        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }
 
        /// <summary>
        /// Is stream writable?
        /// </summary>
        /// <remarks>
        /// Here, the assumption, as in all capability tests, is that the status of
        /// the current piece reflects the status of all pieces for the part.
        /// This is justified by the fact that (i) all piece streams are opened with the same
        /// parameters against the same archive and (ii) the current piece stream cannot get
        /// closed unless the whole part stream is closed.
        /// </remarks>
        public override bool CanWrite
        {
            get
            {
                return !_closed;
            }
        }
 
        /// <summary>
        /// Logical byte position in this stream.
        /// </summary>
        public override long Position
        {
            get
            {
                return -1;
            }
            set
            {
                throw new InvalidOperationException(SR.Get(SRID.OperationViolatesWriteOnceSemantics, "set_Position"));
            }
        }
 
        /// <summary>
        /// Length.
        /// </summary>
        public override long Length
        {
            get
            {
                return -1;
            }
        }
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        /// Dispose(bool)
        /// </summary>
        /// <param name="disposing"></param>
        /// <remarks>
        /// An instance of streams' peculiar dispose pattern, whereby
        /// the inherited abstract Stream class implements Close by calling
        /// this virtual protected function.
        /// In turn, each implementation is responsible for calling back
        /// its base's implementation.
        /// </remarks>
        protected override void Dispose(bool disposing)
        {  
            try
            {
                if (disposing)
                {
                    if (!_closed)
                    {
                        // Flush pending changes into a piece, if any.
                        Flush();
 
                        // Create an empty last piece.
                        EnsurePieceStream(true /* last piece */);
                        _pieceStream.Close();                      
                    }
                }
            }
            finally
            {
                _closed = true;
                base.Dispose(disposing);
            }
        }
 
        #endregion Protected Methods
 
        //------------------------------------------------------
        //
        //   Private Methods
        //
        //------------------------------------------------------
 
        private void EnsurePieceStream(bool isLastPiece)
        {
            if (_pieceStream != null)
            {
                // Normally, the pieces are actually closed automatically for us by the
                // underlying ZipIO logic, but in the case of the last piece (when we
                // are called by our own Dispose(bool) method) we must close it explicitly.
                if (isLastPiece)
                    _pieceStream.Close();
 
                // We detect that the stream has been closed by inspecting the CanWrite property
                // since this is guaranteed not to throw even when the stream is disposed.
                if (!_pieceStream.CanWrite)
                {
                    // increment our piece number so we can generate the correct
                    // one below
                    checked { ++_currentPieceNumber; }
 
                    // release it to trigger the new piece creation below
                    _pieceStream = null;        
                }
            }
 
            if (_pieceStream == null)
            {
                string pieceName = PieceNameHelper.CreatePieceName(
                    _partName,
                    _currentPieceNumber,
                    isLastPiece);
                string pieceZipName = ZipPackage.GetZipItemNameFromOpcName(pieceName);
 
                ZipFileInfo zipFileInfo = _archive.AddFile(pieceZipName, _compressionMethod, _deflateOption);
                // We've just created the file, so the mode can only be Create, not CreateNew.
                // (At least, this is part of ZipFileInfo's belief system.)
                _pieceStream = zipFileInfo.GetStream(FileMode.Create, _access);
            }
        }
 
        private void CheckClosed()
        {
            if (_closed)
                throw new ObjectDisposedException(null, SR.Get(SRID.StreamObjectDisposed));
        }
 
        //------------------------------------------------------
        //
        //   Private Properties
        //
        //------------------------------------------------------
        // None
 
        #region Private Fields
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        private Stream              _pieceStream;               // write-only stream on the current piece
        private string              _partName;                  // part name used to generate correct piece names
        private ZipArchive          _archive;
        private int                 _currentPieceNumber = 0;    // incremented with each piece Close() cycle
        private CompressionMethodEnum _compressionMethod;
        private DeflateOptionEnum   _deflateOption;
        private FileMode            _mode;
        private FileAccess          _access;
        private bool                _closed = false;
 
        #endregion Private Fields
    }
}