| // 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); | 
 |             } | 
 |         } | 
 |  | 
 |     } | 
 |  | 
 | } |