123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- // 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 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)
- {
- 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)
- {
- 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 (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 = Path.Combine(Path.GetDirectoryName(CurrentName),
- 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);
- }
- }
- }
- }
|