// 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 HH.WMS.Utils.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
|
HH.WMS.Utils.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
|
HH.WMS.Utils.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
|
HH.WMS.Utils.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);
|
}
|
}
|
|
}
|
|
}
|