File: Base\MS\Internal\IO\Zip\ZipIOFileItemStream.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//-------------   *** WARNING ***
//-------------    This file is part of a legally monitored development project.  
//-------------    Do not check in changes to this project.  Do not raid bugs on this
//-------------    code in the main PS database.  Do not contact the owner of this
//-------------    code directly.  Contact the legal team at ‘ZSLegal’ for assistance.
//-------------   *** WARNING ***
//-----------------------------------------------------------------------------
 
//-----------------------------------------------------------------------------
//
// <copyright file="ZipIOFileItemStream.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//  This is an internal class that enables interactions with Zip archives
//  for OPC scenarios 
//
// History:
//  11/19/2004: IgorBel: Initial creation.
//  06/21/2005: BruceMac: Add Write-time-streaming support
//-----------------------------------------------------------------------------
 
using System;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections;
using System.Runtime.Serialization;
using System.Windows;
using MS.Internal.IO.Packaging;         // for PackagingUtilities
using MS.Internal.WindowsBase;
    
namespace MS.Internal.IO.Zip
{
    internal class ZipIOFileItemStream :  Stream
    {
        ////////////////////////////////////
        // Stream section  
        /////////////////////////////////
        override public bool CanRead
        {
            get
            {
                return (!_disposedFlag) && (_blockManager.Stream.CanRead);
            }
        }
 
        override public bool CanSeek
        {
            get
            {
                return (!_disposedFlag) && (_blockManager.Stream.CanSeek);
            }
        }
 
        override public bool CanWrite
        {
            get
            {
                return (!_disposedFlag) && (_blockManager.Stream.CanWrite);
            }
        }
 
        override public long Length
        {
            get
            {
                CheckDisposed();            
 
                return  _currentStreamLength;
            }
        }
 
        override public long Position
        {
            get
            {
                CheckDisposed();            
                return _currentStreamPosition;
            }
            set
            {
                CheckDisposed();            
                Seek(value, SeekOrigin.Begin);            
            }
        }
 
        public override void SetLength(long newLength)
        {
            CheckDisposed(); 
 
            Debug.Assert(_cachePrefixStream == null); // we only expect this thing to be not null during Archive Save execution
                                                                                // that would between PreSaveNotofication call and Save SaveStreaming
                
            if (newLength < 0)
            {
                throw new ArgumentOutOfRangeException("newLength");
            }
 
            if (_currentStreamLength != newLength)
            {
                _dirtyFlag = true;
                _dataChanged = true;
 
                if  (newLength <= _persistedSize)
                {
                    // the stream becomes smaller than our disk block, which means that  
                    // we can drop the in-memory-sparse-suffix 
                    if (_sparseMemoryStreamSuffix  != null)
                    {
                        _sparseMemoryStreamSuffix.Close();
                        _sparseMemoryStreamSuffix  = null;                
                    }
                }
                else
                {
                    // we need to construct Sparse Memory stream if we do not have one yet 
                    if (_sparseMemoryStreamSuffix  == null)
                    {
                        _sparseMemoryStreamSuffix  = new SparseMemoryStream(_lowWaterMark, _highWaterMark);
                    }
 
                    // set size on the Sparse Memory Stream 
                    _sparseMemoryStreamSuffix.SetLength(newLength - _persistedSize); // no need for checked as it was verified above 
                }
 
                _currentStreamLength = newLength;
 
                // if stream was truncated to the point that our current position is beyond the end of the stream,
                // we need to reset position so it is at the end of the stream 
                if (_currentStreamLength < _currentStreamPosition)
                    Seek(_currentStreamLength, SeekOrigin.Begin);
            }
        }
 
        override public long Seek(long offset, SeekOrigin origin)
        {
            CheckDisposed();      
 
            Debug.Assert(_cachePrefixStream == null); // we only expect this thing to be not null during Archive Save execution
                                                                                // that would between PreSaveNotofication call and Save SaveStreaming
            
            long newStreamPosition = _currentStreamPosition;
            
            if (origin ==SeekOrigin.Begin) 
            {
                newStreamPosition = offset;
            }   
            else if  (origin == SeekOrigin.Current)
            {
                checked{newStreamPosition += offset;}
            }   
            else if  (origin == SeekOrigin.End) 
            {
                checked{newStreamPosition = _currentStreamLength + offset;}
            }
            else
            {
                throw new ArgumentOutOfRangeException("origin");
            }
 
            if (newStreamPosition  < 0) 
            {
                 throw new ArgumentException(SR.Get(SRID.SeekNegative));
            }
            _currentStreamPosition = newStreamPosition;
 
            return _currentStreamPosition;
        }        
 
        override public int Read(byte[] buffer, int offset, int count)
        {
            CheckDisposed();   
 
            PackagingUtilities.VerifyStreamReadArgs(this, buffer, offset, count);
 
            Debug.Assert(_cachePrefixStream == null); // we only expect this thing to be not null during Archive Save execution
                                                      // that would between PreSaveNotofication call and Save SaveStreaming
 
            Debug.Assert(_currentStreamPosition >= 0);
 
            if (count == 0)
            {
                return 0; 
            }
 
            if (_currentStreamLength <= _currentStreamPosition)
            {
                // we are past the end of the stream so let's just return 0
                return 0; 
            }
 
            int totalBytesRead;
            int diskBytesRead = 0;
            int diskBytesToRead = 0;
            long persistedTailSize = 0;
 
            int memoryBytesRead = 0;
            long newStreamPosition = _currentStreamPosition;
 
            checked
            {
                // Try to satisfy request with the Read from the Disk 
                if  (newStreamPosition < _persistedSize)
                {
                    // we have at least partial overlap between request and the data on disk 
 
                    //first let's get min between size of the stream's tail and the tail of the persisted chunk
                    // in some cases stream might be smaller 
                    // e.g. _currentStreamLength  < _persistedSize, if let's say stream was truncated
                    persistedTailSize = Math.Min(_currentStreamLength, _persistedSize) - newStreamPosition;
                    Debug.Assert(persistedTailSize > 0);
 
                    // we also do not want to read more data than was requested by the user
                    diskBytesToRead = (int)Math.Min((long)count, persistedTailSize); // this is a safe cast as count has int type 
                    Debug.Assert(diskBytesToRead > 0);
                    
                    // and now we can actually read it 
                    _blockManager.Stream.Seek(_persistedOffset + newStreamPosition, SeekOrigin.Begin);
 
                    // we are ready for getting fewer bytes than reqested 
                    diskBytesRead = _blockManager.Stream.Read(buffer, offset, diskBytesToRead);
 
                    newStreamPosition += diskBytesRead;
                     count -= diskBytesRead;
                     offset +=diskBytesRead;
 
                    if (diskBytesRead  <  diskBytesToRead)
                    {
                        // we didn't everything that we hae asked for. In such case we shouldn't 
                        // try to get data from the   _sparseMemoryStreamSuffix  
                        _currentStreamPosition = newStreamPosition;
 
                        return diskBytesRead;
                    }
                }
 
                // check whether we need to get data from the memory Stream;
                if  ((_sparseMemoryStreamSuffix  != null) && (newStreamPosition + count > _persistedSize))
                {
                    // we are either trying to finish the request partially satisfied by the 
                    // on disk data  or  the read is entirely within the suffix 
                     _sparseMemoryStreamSuffix.Seek(newStreamPosition - _persistedSize, SeekOrigin.Begin);
                    memoryBytesRead = _sparseMemoryStreamSuffix.Read(buffer, offset, count);
                    
                    newStreamPosition += memoryBytesRead;
                }
     
                totalBytesRead = diskBytesRead + memoryBytesRead;
            }
 
            _currentStreamPosition = newStreamPosition;
            return totalBytesRead ;
        }
 
        /// <summary>
        /// Write
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <remarks>In streaming mode, write should accumulate data into the SparseMemoryStream.</remarks>
        override public void Write(byte[] buffer, int offset, int count)
        {
            CheckDisposed();   
 
            PackagingUtilities.VerifyStreamWriteArgs(this, buffer, offset, count);
 
            Debug.Assert(_cachePrefixStream == null); // we only expect this thing to be not null during Archive Save execution
                                                      // that would between PreSaveNotofication call and Save SaveStreaming
 
            Debug.Assert(_currentStreamPosition >= 0);
 
            if (count == 0)
            {
                return; 
            }
        
            int diskBytesToWrite = 0;
    
            _dirtyFlag = true;
            _dataChanged = true;
            long newStreamPosition = _currentStreamPosition;
            checked
            {
                // Try to satisfy request with the Write to the Disk 
                if  (newStreamPosition  < _persistedSize)
                {
                    Debug.Assert(!_blockManager.Streaming);
 
                    // we have at least partial overlap between request and the data on disk 
                    _blockManager.Stream.Seek(_persistedOffset + newStreamPosition, SeekOrigin.Begin);
                    // Note on casting:
                    //  It is safe to cast the result of Math.Min(count, _persistedSize - newStreamPosition))
                    //      from long to int since it cannot be bigger than count and count is int type
                    diskBytesToWrite = (int) (Math.Min(count, _persistedSize - newStreamPosition));  // this is a safe cast as count has int type 
 
                    _blockManager.Stream.Write(buffer, offset, diskBytesToWrite);
                    newStreamPosition += diskBytesToWrite;
                    count -= diskBytesToWrite;
                    offset += diskBytesToWrite;
                }
 
                // check whether we need to save data to the memory Stream;
                if  (newStreamPosition + count > _persistedSize)
                {
                    if (_sparseMemoryStreamSuffix  == null)
                    {
                         _sparseMemoryStreamSuffix  = new SparseMemoryStream(_lowWaterMark, _highWaterMark);
                    }
                    
                    _sparseMemoryStreamSuffix.Seek(newStreamPosition - _persistedSize, SeekOrigin.Begin);
 
                    _sparseMemoryStreamSuffix.Write(buffer, offset, count);
                    newStreamPosition += count;
                }
 
                _currentStreamPosition = newStreamPosition;
                _currentStreamLength = Math.Max(_currentStreamLength, _currentStreamPosition);                                 
            }
            return;
        }
        
        override public void Flush()
        {
            CheckDisposed();
 
            // tell the BlockManager that the caller called Flush on us. Block manager will process this 
            // and possibly call us back on Save or SaveStreaming 
            _blockManager.SaveStream(_block, false);  // second parameter is a closing indicator 
        }  
 
        /////////////////////////////
        // Internal Constructor
        /////////////////////////////        
        internal  ZipIOFileItemStream(ZipIOBlockManager blockManager,   // blockManager is only needed 
                                                                        // to pass through to it Flush requests 
                                      ZipIOLocalFileBlock block,        // our owning block - needed for Streaming scenarios
                                      long persistedOffset,             // to map to the stream 
                                      long persistedSize)               // to map to the stream )
        {
            Debug.Assert(blockManager != null);
            Debug.Assert(persistedOffset >=0);
            Debug.Assert(persistedSize >= 0);
            Debug.Assert(block != null);
                
            _persistedOffset = persistedOffset; 
            _offset = persistedOffset; 
            _persistedSize = persistedSize;
                
            _blockManager = blockManager;
            _block = block;
            
            _currentStreamLength = persistedSize;
        }
 
        /////////////////////////////
        // Internal Methods for the LocalFileBlock to call in order to know Dirty status and the new size 
        /////////////////////////////        
        internal PreSaveNotificationScanControlInstruction PreSaveNotification(long offset, long size)
        {
            return ZipIOBlockManager.CommonPreSaveNotificationHandler(
                    _blockManager.Stream,
                    offset, 
                    size,
                    _persistedOffset, 
                    Math.Min(_persistedSize, _currentStreamLength),  // in case the stream is smaller then our persisted block on 
                                                                            // disk, there is no need to preserve the meaningless persisted suffix 
                    ref _cachePrefixStream); 
        }
    
        internal bool DirtyFlag
        {
            get
            {
                return _dirtyFlag;
            }
        }
 
        internal bool DataChanged
        {
            get
            {
                return _dataChanged;
            }
        }
 
        internal long Offset
        {
            get
            {
                return _offset;
            }
        }
 
        internal void Move(long shiftSize)
        {
            CheckDisposed();
            if (shiftSize != 0)
            {
                checked{_offset +=shiftSize;}
                _dirtyFlag = true;
                Debug.Assert(_offset >=0);
            }
        }
 
        /// <summary>
        /// Streaming-specific variant of Save()
        /// </summary>
        /// <remarks>Writes current data to the underlying stream.
        /// Assumes the stream is in the correct place.</remarks>
        internal void SaveStreaming()
        {
            CheckDisposed();
 
            Debug.Assert(_cachePrefixStream == null); // _cachePrefixStream must not be used in streaming cases at all 
            
            Debug.Assert(_blockManager.Streaming);
 
            if (_dirtyFlag)
            {
                // in streaming cases all the data collected in the _sparseMemoryStreamSuffix 
                // and now we can save the SparseMemoryStream 
                if (_sparseMemoryStreamSuffix  != null)
                {
                    _sparseMemoryStreamSuffix.WriteToStream(_blockManager.Stream);
 
                    // update so that subsequent MemoryStreams will know where they begin
                    checked{_persistedSize += _sparseMemoryStreamSuffix.Length;}   
                    _sparseMemoryStreamSuffix.Close();
                    _sparseMemoryStreamSuffix  = null;
                }
 
                _dirtyFlag = false;
                _dataChanged = false;
            }
        }
 
        /// <summary>
        /// Save - called by the BlockManager to cause us to Flush to the underlying stream
        /// </summary>
        internal void Save()
        {
            CheckDisposed();
            Debug.Assert(!_blockManager.Streaming);
            
            if(_dirtyFlag)
            {                
                // we need to move the whole persisted block to the new position 
                long moveBlockSourceOffset = _persistedOffset;
 
                // in case the stream is smaller then our persisted block on disk there is 
                // no need to move meaningless persisted suffix 
                long moveBlockSize = Math.Min(_persistedSize, _currentStreamLength); 
                
                long moveBlockTargetOffset = _offset;
 
                long newPersistedSize = 0;
    
                if (_cachePrefixStream != null)
                {
                    // if we have something in cache we only should move whatever isn't cached
                    checked{moveBlockSourceOffset += _cachePrefixStream.Length;}
                    checked{moveBlockTargetOffset += _cachePrefixStream.Length;}
                    checked{moveBlockSize -= _cachePrefixStream.Length;}
                    Debug.Assert(moveBlockSize >=0);                    
                }
 
                _blockManager.MoveData(moveBlockSourceOffset, moveBlockTargetOffset, moveBlockSize);
                checked{newPersistedSize += moveBlockSize;}
    
                // only after data on disk was moved it is safe to flush the cached prefix buffer 
                if (_cachePrefixStream != null)
                {
                    // we need to seek and it is safe to do as we are not in the streaming mode 
                    _blockManager.Stream.Seek(_offset, SeekOrigin.Begin);
                    
                    Debug.Assert(_cachePrefixStream.Length > 0); // should be taken care of by the constructor 
                                                                                        // and PreSaveNotification                
 
                    _cachePrefixStream.WriteToStream(_blockManager.Stream);
                    checked{newPersistedSize += _cachePrefixStream.Length;}
                
                    // we can free the memory
                    _cachePrefixStream.Close();
                    _cachePrefixStream = null;
                }
 
 
                // and now we can save the SparseMemoryStream 
                if (_sparseMemoryStreamSuffix  != null)
                {
                    if (_blockManager.Stream.Position != checked (_offset + _persistedSize))
                    {
                        // we need to seek 
                        _blockManager.Stream.Seek(_offset + _persistedSize, SeekOrigin.Begin);
                    }
                    _sparseMemoryStreamSuffix.WriteToStream(_blockManager.Stream);
                    checked{newPersistedSize += _sparseMemoryStreamSuffix.Length;}
 
                    _sparseMemoryStreamSuffix.Close();
                    _sparseMemoryStreamSuffix  = null;
                }
 
                _blockManager.Stream.Flush();
            
                // we are not shifted between on disk image and in memory image any more 
                _persistedOffset = _offset;
                _persistedSize = newPersistedSize;
 
                Debug.Assert(newPersistedSize == _currentStreamLength);
 
                Debug.Assert(_cachePrefixStream == null); // we only expect this thing to be not null during Archive Save execution
                                                                                // after we are saved this field must be clear 
 
                _dirtyFlag = false;
                _dataChanged = false;
            }
        }
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// Dispose(bool)
        /// </summary>
        /// <param name="disposing"></param>
        /// <remarks>We implement this because we want a consistent experience (essentially Flush our data) if the user chooses to 
        /// call Dispose() instead of Close().</remarks>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    //streams wrapping this stream shouldn't pass Dispose calls through 
                    // it is responsibility of the BlockManager or LocalFileBlock (in case of Remove) to call 
                    // this dispose as appropriate (that is the reason why Flush isn't called here)
 
                    // multiple calls are fine - just ignore them
                    if (!_disposedFlag)
                    {
                        if (_sparseMemoryStreamSuffix  != null)
                        {
                            _sparseMemoryStreamSuffix.Close();
                        }
 
                        if (_cachePrefixStream != null)
                        {
                            _cachePrefixStream.Close();
                        }
                    }
                }
            }
            finally
            {
                _sparseMemoryStreamSuffix  = null;                
                _cachePrefixStream = null;                
                _disposedFlag = true;
                
                base.Dispose(disposing);
            }
        }
 
        /////////////////////////////
        // Private Methods
        /////////////////////////////        
        private void CheckDisposed()
        {
            if (_disposedFlag)
            {
                throw new ObjectDisposedException(null, SR.Get(SRID.ZipFileItemDisposed));            
            }
        }
 
        private ZipIOBlockManager _blockManager;
        private ZipIOLocalFileBlock _block;         // our owning block
 
        private long _offset;
        private long _persistedOffset;
        private long _persistedSize;
 
        private SparseMemoryStream _cachePrefixStream;
    
        private bool  _dirtyFlag;
        private bool  _dataChanged;
 
        //support for Stream methods 
        private bool _disposedFlag;
 
        private long _currentStreamLength;
        private long _currentStreamPosition;
 
        private SparseMemoryStream _sparseMemoryStreamSuffix;
 
        private const long _lowWaterMark = 0x19000;                 // we definately would like to keep everythuing under 100 KB in memory  
        private const long _highWaterMark = 0xA00000;   // we would like to keep everything over 10 MB on disk
    }
}