|  | // ZipSegmentedStream.cs | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // Copyright (c) 2009-2011 Dino Chiesa. | 
|  | // All rights reserved. | 
|  | // | 
|  | // This code module is part of DotNetZip, a zipfile class library. | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This code is licensed under the Microsoft Public License. | 
|  | // See the file License.txt for the license details. | 
|  | // More info on: http://dotnetzip.codeplex.com | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // last saved (in emacs): | 
|  | // Time-stamp: <2011-July-13 22:25:45> | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This module defines logic for zip streams that span disk files. | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  |  | 
|  |  | 
|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.IO; | 
|  |  | 
|  | namespace OfficeOpenXml.Packaging.Ionic.Zip | 
|  | { | 
|  | internal class ZipSegmentedStream : System.IO.Stream | 
|  | { | 
|  | enum RwMode | 
|  | { | 
|  | None = 0, | 
|  | ReadOnly = 1, | 
|  | Write = 2, | 
|  | //Update = 3 | 
|  | } | 
|  |  | 
|  | private RwMode rwMode; | 
|  | private bool _exceptionPending; // **see note below | 
|  | private string _baseName; | 
|  | private string _baseDir; | 
|  | //private bool _isDisposed; | 
|  | private string _currentName; | 
|  | private string _currentTempName; | 
|  | private uint _currentDiskNumber; | 
|  | private uint _maxDiskNumber; | 
|  | private int _maxSegmentSize; | 
|  | private System.IO.Stream _innerStream; | 
|  |  | 
|  | // **Note regarding exceptions: | 
|  | // | 
|  | // When ZipSegmentedStream is employed within a using clause, | 
|  | // which is the typical scenario, and an exception is thrown | 
|  | // within the scope of the using, Dispose() is invoked | 
|  | // implicitly before processing the initial exception.  If that | 
|  | // happens, this class sets _exceptionPending to true, and then | 
|  | // within the Dispose(bool), takes special action as | 
|  | // appropriate. Need to be careful: any additional exceptions | 
|  | // will mask the original one. | 
|  |  | 
|  | private ZipSegmentedStream() : base() | 
|  | { | 
|  | _exceptionPending = false; | 
|  | } | 
|  |  | 
|  | public static ZipSegmentedStream ForReading(string name, | 
|  | uint initialDiskNumber, | 
|  | uint maxDiskNumber) | 
|  | { | 
|  | ZipSegmentedStream zss = new ZipSegmentedStream() | 
|  | { | 
|  | rwMode = RwMode.ReadOnly, | 
|  | CurrentSegment = initialDiskNumber, | 
|  | _maxDiskNumber = maxDiskNumber, | 
|  | _baseName = name, | 
|  | }; | 
|  |  | 
|  | // Console.WriteLine("ZSS: ForReading ({0})", | 
|  | //                    Path.GetFileName(zss.CurrentName)); | 
|  |  | 
|  | zss._SetReadStream(); | 
|  |  | 
|  | return zss; | 
|  | } | 
|  |  | 
|  |  | 
|  | public static ZipSegmentedStream ForWriting(string name, int maxSegmentSize) | 
|  | { | 
|  | ZipSegmentedStream zss = new ZipSegmentedStream() | 
|  | { | 
|  | rwMode = RwMode.Write, | 
|  | CurrentSegment = 0, | 
|  | _baseName = name, | 
|  | _maxSegmentSize = maxSegmentSize, | 
|  | _baseDir = Path.GetDirectoryName(name) | 
|  | }; | 
|  |  | 
|  | // workitem 9522 | 
|  | if (zss._baseDir=="") zss._baseDir="."; | 
|  |  | 
|  | zss._SetWriteStream(0); | 
|  |  | 
|  | // Console.WriteLine("ZSS: ForWriting ({0})", | 
|  | //                    Path.GetFileName(zss.CurrentName)); | 
|  |  | 
|  | return zss; | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Sort-of like a factory method, ForUpdate is used only when | 
|  | ///   the application needs to update the zip entry metadata for | 
|  | ///   a segmented zip file, when the starting segment is earlier | 
|  | ///   than the ending segment, for a particular entry. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | ///   <para> | 
|  | ///     The update is always contiguous, never rolls over.  As a | 
|  | ///     result, this method doesn't need to return a ZSS; it can | 
|  | ///     simply return a FileStream.  That's why it's "sort of" | 
|  | ///     like a Factory method. | 
|  | ///   </para> | 
|  | ///   <para> | 
|  | ///     Caller must Close/Dispose the stream object returned by | 
|  | ///     this method. | 
|  | ///   </para> | 
|  | /// </remarks> | 
|  | public static Stream ForUpdate(string name, uint diskNumber) | 
|  | { | 
|  | if (diskNumber >= 99) | 
|  | throw new ArgumentOutOfRangeException("diskNumber"); | 
|  |  | 
|  | string fname = | 
|  | String.Format("{0}.z{1:D2}", | 
|  | Path.Combine(Path.GetDirectoryName(name), | 
|  | Path.GetFileNameWithoutExtension(name)), | 
|  | diskNumber + 1); | 
|  |  | 
|  | // Console.WriteLine("ZSS: ForUpdate ({0})", | 
|  | //                   Path.GetFileName(fname)); | 
|  |  | 
|  | // This class assumes that the update will not expand the | 
|  | // size of the segment. Update is used only for an in-place | 
|  | // update of zip metadata. It never will try to write beyond | 
|  | // the end of a segment. | 
|  |  | 
|  | return File.Open(fname, | 
|  | FileMode.Open, | 
|  | FileAccess.ReadWrite, | 
|  | FileShare.None); | 
|  | } | 
|  |  | 
|  | public bool ContiguousWrite | 
|  | { | 
|  | get; | 
|  | set; | 
|  | } | 
|  |  | 
|  |  | 
|  | public UInt32 CurrentSegment | 
|  | { | 
|  | get | 
|  | { | 
|  | return _currentDiskNumber; | 
|  | } | 
|  | private set | 
|  | { | 
|  | _currentDiskNumber = value; | 
|  | _currentName = null; // it will get updated next time referenced | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | ///   Name of the filesystem file corresponding to the current segment. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | ///   <para> | 
|  | ///     The name is not always the name currently being used in the | 
|  | ///     filesystem.  When rwMode is RwMode.Write, the filesystem file has a | 
|  | ///     temporary name until the stream is closed or until the next segment is | 
|  | ///     started. | 
|  | ///   </para> | 
|  | /// </remarks> | 
|  | public String CurrentName | 
|  | { | 
|  | get | 
|  | { | 
|  | if (_currentName==null) | 
|  | _currentName = _NameForSegment(CurrentSegment); | 
|  |  | 
|  | return _currentName; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public String CurrentTempName | 
|  | { | 
|  | get | 
|  | { | 
|  | return _currentTempName; | 
|  | } | 
|  | } | 
|  |  | 
|  | private string _NameForSegment(uint diskNumber) | 
|  | { | 
|  | if (diskNumber >= 99) | 
|  | { | 
|  | _exceptionPending = true; | 
|  | throw new OverflowException("The number of zip segments would exceed 99."); | 
|  | } | 
|  |  | 
|  | return String.Format("{0}.z{1:D2}", | 
|  | Path.Combine(Path.GetDirectoryName(_baseName), | 
|  | Path.GetFileNameWithoutExtension(_baseName)), | 
|  | diskNumber + 1); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Returns the segment that WILL be current if writing | 
|  | // a block of the given length. | 
|  | // This isn't exactly true. It could roll over beyond | 
|  | // this number. | 
|  | public UInt32 ComputeSegment(int length) | 
|  | { | 
|  | if (_innerStream.Position + length > _maxSegmentSize) | 
|  | // the block will go AT LEAST into the next segment | 
|  | return CurrentSegment + 1; | 
|  |  | 
|  | // it will fit in the current segment | 
|  | return CurrentSegment; | 
|  | } | 
|  |  | 
|  |  | 
|  | public override String ToString() | 
|  | { | 
|  | return String.Format("{0}[{1}][{2}], pos=0x{3:X})", | 
|  | "ZipSegmentedStream", CurrentName, | 
|  | rwMode.ToString(), | 
|  | this.Position); | 
|  | } | 
|  |  | 
|  |  | 
|  | private void _SetReadStream() | 
|  | { | 
|  | if (_innerStream != null) | 
|  | { | 
|  | #if NETCF | 
|  | _innerStream.Close(); | 
|  | #else | 
|  | _innerStream.Dispose(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | if (CurrentSegment + 1 == _maxDiskNumber) | 
|  | _currentName = _baseName; | 
|  |  | 
|  | // Console.WriteLine("ZSS: SRS ({0})", | 
|  | //                   Path.GetFileName(CurrentName)); | 
|  |  | 
|  | _innerStream = File.OpenRead(CurrentName); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Read from the stream | 
|  | /// </summary> | 
|  | /// <param name="buffer">the buffer to read</param> | 
|  | /// <param name="offset">the offset at which to start</param> | 
|  | /// <param name="count">the number of bytes to read</param> | 
|  | /// <returns>the number of bytes actually read</returns> | 
|  | public override int Read(byte[] buffer, int offset, int count) | 
|  | { | 
|  | if (rwMode != RwMode.ReadOnly) | 
|  | { | 
|  | _exceptionPending = true; | 
|  | throw new InvalidOperationException("Stream Error: Cannot Read."); | 
|  | } | 
|  |  | 
|  | int r = _innerStream.Read(buffer, offset, count); | 
|  | int r1 = r; | 
|  |  | 
|  | while (r1 != count) | 
|  | { | 
|  | if (_innerStream.Position != _innerStream.Length) | 
|  | { | 
|  | _exceptionPending = true; | 
|  | throw new ZipException(String.Format("Read error in file {0}", CurrentName)); | 
|  |  | 
|  | } | 
|  |  | 
|  | if (CurrentSegment + 1 == _maxDiskNumber) | 
|  | return r; // no more to read | 
|  |  | 
|  | CurrentSegment++; | 
|  | _SetReadStream(); | 
|  | offset += r1; | 
|  | count -= r1; | 
|  | r1 = _innerStream.Read(buffer, offset, count); | 
|  | r += r1; | 
|  | } | 
|  | return r; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | private void _SetWriteStream(uint increment) | 
|  | { | 
|  | if (_innerStream != null) | 
|  | { | 
|  | #if NETCF | 
|  | _innerStream.Close(); | 
|  | #else | 
|  | _innerStream.Dispose(); | 
|  | #endif | 
|  | if (File.Exists(CurrentName)) | 
|  | File.Delete(CurrentName); | 
|  | File.Move(_currentTempName, CurrentName); | 
|  | // Console.WriteLine("ZSS: SWS close ({0})", | 
|  | //                   Path.GetFileName(CurrentName)); | 
|  | } | 
|  |  | 
|  | if (increment > 0) | 
|  | CurrentSegment += increment; | 
|  |  | 
|  | SharedUtilities.CreateAndOpenUniqueTempFile(_baseDir, | 
|  | out _innerStream, | 
|  | out _currentTempName); | 
|  |  | 
|  | // Console.WriteLine("ZSS: SWS open ({0})", | 
|  | //                   Path.GetFileName(_currentTempName)); | 
|  |  | 
|  | if (CurrentSegment == 0) | 
|  | _innerStream.Write(BitConverter.GetBytes(ZipConstants.SplitArchiveSignature), 0, 4); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Write to the stream. | 
|  | /// </summary> | 
|  | /// <param name="buffer">the buffer from which to write</param> | 
|  | /// <param name="offset">the offset at which to start writing</param> | 
|  | /// <param name="count">the number of bytes to write</param> | 
|  | public override void Write(byte[] buffer, int offset, int count) | 
|  | { | 
|  | if (rwMode != RwMode.Write) | 
|  | { | 
|  | _exceptionPending = true; | 
|  | throw new InvalidOperationException("Stream Error: Cannot Write."); | 
|  | } | 
|  |  | 
|  |  | 
|  | if (ContiguousWrite) | 
|  | { | 
|  | // enough space for a contiguous write? | 
|  | if (_innerStream.Position + count > _maxSegmentSize) | 
|  | _SetWriteStream(1); | 
|  | } | 
|  | else | 
|  | { | 
|  | while (_innerStream.Position + count > _maxSegmentSize) | 
|  | { | 
|  | int c = unchecked(_maxSegmentSize - (int)_innerStream.Position); | 
|  | _innerStream.Write(buffer, offset, c); | 
|  | _SetWriteStream(1); | 
|  | count -= c; | 
|  | offset += c; | 
|  | } | 
|  | } | 
|  |  | 
|  | _innerStream.Write(buffer, offset, count); | 
|  | } | 
|  |  | 
|  |  | 
|  | public long TruncateBackward(uint diskNumber, long offset) | 
|  | { | 
|  | // Console.WriteLine("***ZSS.Trunc to disk {0}", diskNumber); | 
|  | // Console.WriteLine("***ZSS.Trunc:  current disk {0}", CurrentSegment); | 
|  | if (diskNumber >= 99) | 
|  | throw new ArgumentOutOfRangeException("diskNumber"); | 
|  |  | 
|  | if (rwMode != RwMode.Write) | 
|  | { | 
|  | _exceptionPending = true; | 
|  | throw new ZipException("bad state."); | 
|  | } | 
|  |  | 
|  | // Seek back in the segmented stream to a (maybe) prior segment. | 
|  |  | 
|  | // Check if it is the same segment.  If it is, very simple. | 
|  | if (diskNumber == CurrentSegment) | 
|  | { | 
|  | var x =_innerStream.Seek(offset, SeekOrigin.Begin); | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream); | 
|  | return x; | 
|  | } | 
|  |  | 
|  | // Seeking back to a prior segment. | 
|  | // The current segment and any intervening segments must be removed. | 
|  | // First, close the current segment, and then remove it. | 
|  | if (_innerStream != null) | 
|  | { | 
|  | #if NETCF | 
|  | _innerStream.Close(); | 
|  | #else | 
|  | _innerStream.Dispose(); | 
|  | #endif | 
|  | if (File.Exists(_currentTempName)) | 
|  | File.Delete(_currentTempName); | 
|  | } | 
|  |  | 
|  | // Now, remove intervening segments. | 
|  | for (uint j= CurrentSegment-1; j > diskNumber; j--) | 
|  | { | 
|  | string s = _NameForSegment(j); | 
|  | // Console.WriteLine("***ZSS.Trunc:  removing file {0}", s); | 
|  | if (File.Exists(s)) | 
|  | File.Delete(s); | 
|  | } | 
|  |  | 
|  | // now, open the desired segment.  It must exist. | 
|  | CurrentSegment = diskNumber; | 
|  |  | 
|  | // get a new temp file, try 3 times: | 
|  | for (int i = 0; i < 3; i++) | 
|  | { | 
|  | try | 
|  | { | 
|  | _currentTempName = SharedUtilities.InternalGetTempFileName(); | 
|  | // move the .z0x file back to a temp name | 
|  | File.Move(CurrentName, _currentTempName); | 
|  | break; // workitem 12403 | 
|  | } | 
|  | catch(IOException) | 
|  | { | 
|  | if (i == 2) throw; | 
|  | } | 
|  | } | 
|  |  | 
|  | // open it | 
|  | _innerStream = new FileStream(_currentTempName, FileMode.Open); | 
|  |  | 
|  | var r =  _innerStream.Seek(offset, SeekOrigin.Begin); | 
|  |  | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | public override bool CanRead | 
|  | { | 
|  | get | 
|  | { | 
|  | return (rwMode == RwMode.ReadOnly && | 
|  | (_innerStream != null) && | 
|  | _innerStream.CanRead); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public override bool CanSeek | 
|  | { | 
|  | get | 
|  | { | 
|  | return (_innerStream != null) && | 
|  | _innerStream.CanSeek; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public override bool CanWrite | 
|  | { | 
|  | get | 
|  | { | 
|  | return (rwMode == RwMode.Write) && | 
|  | (_innerStream != null) && | 
|  | _innerStream.CanWrite; | 
|  | } | 
|  | } | 
|  |  | 
|  | public override void Flush() | 
|  | { | 
|  | _innerStream.Flush(); | 
|  | } | 
|  |  | 
|  | public override long Length | 
|  | { | 
|  | get | 
|  | { | 
|  | return _innerStream.Length; | 
|  | } | 
|  | } | 
|  |  | 
|  | public override long Position | 
|  | { | 
|  | get { return _innerStream.Position; } | 
|  | set { _innerStream.Position = value; } | 
|  | } | 
|  |  | 
|  | public override long Seek(long offset, System.IO.SeekOrigin origin) | 
|  | { | 
|  | var x = _innerStream.Seek(offset, origin); | 
|  | // workitem 10178 | 
|  | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream); | 
|  | return x; | 
|  | } | 
|  |  | 
|  | public override void SetLength(long value) | 
|  | { | 
|  | if (rwMode != RwMode.Write) | 
|  | { | 
|  | _exceptionPending = true; | 
|  | throw new InvalidOperationException(); | 
|  | } | 
|  | _innerStream.SetLength(value); | 
|  | } | 
|  |  | 
|  |  | 
|  | protected override void Dispose(bool disposing) | 
|  | { | 
|  | // this gets called by Stream.Close() | 
|  |  | 
|  | // if (_isDisposed) return; | 
|  | // _isDisposed = true; | 
|  | //Console.WriteLine("Dispose (mode={0})\n", rwMode.ToString()); | 
|  |  | 
|  | try | 
|  | { | 
|  | if (_innerStream != null) | 
|  | { | 
|  | #if NETCF | 
|  | _innerStream.Close(); | 
|  | #else | 
|  | _innerStream.Dispose(); | 
|  | #endif | 
|  | //_innerStream = null; | 
|  | if (rwMode == RwMode.Write) | 
|  | { | 
|  | if (_exceptionPending) | 
|  | { | 
|  | // possibly could try to clean up all the | 
|  | // temp files created so far... | 
|  | } | 
|  | else | 
|  | { | 
|  | // // move the final temp file to the .zNN name | 
|  | // if (File.Exists(CurrentName)) | 
|  | //     File.Delete(CurrentName); | 
|  | // if (File.Exists(_currentTempName)) | 
|  | //     File.Move(_currentTempName, CurrentName); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | finally | 
|  | { | 
|  | base.Dispose(disposing); | 
|  | } | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | } |