/* This file is part of SevenZipSharp. SevenZipSharp is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SevenZipSharp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SevenZipSharp. If not, see . */ #define DOTNET20 #define UNMANAGED #define COMPRESS using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Runtime.InteropServices; #if MONO using SevenZip.Mono.COM; #endif namespace SevenZip { #if UNMANAGED /// /// A class that has DisposeStream property. /// internal class DisposeVariableWrapper { public bool DisposeStream { protected get; set; } protected DisposeVariableWrapper(bool disposeStream) { DisposeStream = disposeStream; } } /// /// Stream wrapper used in InStreamWrapper /// internal class StreamWrapper : DisposeVariableWrapper, IDisposable { /// /// File name associated with the stream (for date fix) /// private readonly string _fileName; private readonly DateTime _fileTime; /// /// Worker stream for reading, writing and seeking. /// private Stream _baseStream; /// /// Initializes a new instance of the StreamWrapper class /// /// Worker stream for reading, writing and seeking /// File name associated with the stream (for attributes fix) /// File last write time (for attributes fix) /// Indicates whether to dispose the baseStream protected StreamWrapper(Stream baseStream, string fileName, DateTime time, bool disposeStream) : base(disposeStream) { _baseStream = baseStream; _fileName = fileName; _fileTime = time; } /// /// Initializes a new instance of the StreamWrapper class /// /// Worker stream for reading, writing and seeking /// Indicates whether to dispose the baseStream protected StreamWrapper(Stream baseStream, bool disposeStream) : base(disposeStream) { _baseStream = baseStream; } /// /// Gets the worker stream for reading, writing and seeking. /// protected Stream BaseStream { get { return _baseStream; } } #region IDisposable Members /// /// Cleans up any resources used and fixes file attributes. /// public void Dispose() { if (_baseStream != null && DisposeStream) { try { _baseStream.Dispose(); } catch (ObjectDisposedException) { } _baseStream = null; } if (!String.IsNullOrEmpty(_fileName) && File.Exists(_fileName)) { try { #if !WINCE File.SetLastWriteTime(_fileName, _fileTime); File.SetLastAccessTime(_fileName, _fileTime); File.SetCreationTime(_fileName, _fileTime); #elif WINCE OpenNETCF.IO.FileHelper.SetLastWriteTime(_fileName, _fileTime); OpenNETCF.IO.FileHelper.SetLastAccessTime(_fileName, _fileTime); OpenNETCF.IO.FileHelper.SetCreationTime(_fileName, _fileTime); #endif //TODO: time support for Windows Phone } catch (ArgumentOutOfRangeException) {} } GC.SuppressFinalize(this); } #endregion public virtual void Seek(long offset, SeekOrigin seekOrigin, IntPtr newPosition) { if (BaseStream != null) { long position = BaseStream.Seek(offset, seekOrigin); if (newPosition != IntPtr.Zero) { Marshal.WriteInt64(newPosition, position); } } } } /// /// IInStream wrapper used in stream read operations. /// internal sealed class InStreamWrapper : StreamWrapper, ISequentialInStream, IInStream { /// /// Initializes a new instance of the InStreamWrapper class. /// /// Stream for writing data /// Indicates whether to dispose the baseStream public InStreamWrapper(Stream baseStream, bool disposeStream) : base(baseStream, disposeStream) { } #region ISequentialInStream Members /// /// Reads data from the stream. /// /// A data array. /// The array size. /// The read bytes count. public int Read(byte[] data, uint size) { int readCount = 0; if (BaseStream != null) { readCount = BaseStream.Read(data, 0, (int) size); if (readCount > 0) { OnBytesRead(new IntEventArgs(readCount)); } } return readCount; } #endregion /// /// Occurs when IntEventArgs.Value bytes were read from the source. /// public event EventHandler BytesRead; private void OnBytesRead(IntEventArgs e) { if (BytesRead != null) { BytesRead(this, e); } } } /// /// IOutStream wrapper used in stream write operations. /// internal sealed class OutStreamWrapper : StreamWrapper, ISequentialOutStream, IOutStream { /// /// Initializes a new instance of the OutStreamWrapper class /// /// Stream for writing data /// File name (for attributes fix) /// Time of the file creation (for attributes fix) /// Indicates whether to dispose the baseStream public OutStreamWrapper(Stream baseStream, string fileName, DateTime time, bool disposeStream) : base(baseStream, fileName, time, disposeStream) {} /// /// Initializes a new instance of the OutStreamWrapper class /// /// Stream for writing data /// Indicates whether to dispose the baseStream public OutStreamWrapper(Stream baseStream, bool disposeStream) : base(baseStream, disposeStream) {} #region IOutStream Members public int SetSize(long newSize) { BaseStream.SetLength(newSize); return 0; } #endregion #region ISequentialOutStream Members /// /// Writes data to the stream /// /// Data array /// Array size /// Count of written bytes /// Zero if Ok public int Write(byte[] data, uint size, IntPtr processedSize) { BaseStream.Write(data, 0, (int) size); if (processedSize != IntPtr.Zero) { Marshal.WriteInt32(processedSize, (int) size); } OnBytesWritten(new IntEventArgs((int) size)); return 0; } #endregion /// /// Occurs when IntEventArgs.Value bytes were written. /// public event EventHandler BytesWritten; private void OnBytesWritten(IntEventArgs e) { if (BytesWritten != null) { BytesWritten(this, e); } } } /// /// Base multi volume stream wrapper class. /// internal class MultiStreamWrapper : DisposeVariableWrapper, IDisposable { protected readonly Dictionary> StreamOffsets = new Dictionary>(); protected readonly List Streams = new List(); protected int CurrentStream; protected long Position; protected long StreamLength; /// /// Initializes a new instance of the MultiStreamWrapper class. /// /// Perform Dispose() if requested to. protected MultiStreamWrapper(bool dispose) : base(dispose) {} /// /// Gets the total length of input data. /// public long Length { get { return StreamLength; } } #region IDisposable Members /// /// Cleans up any resources used and fixes file attributes. /// public virtual void Dispose() { if (DisposeStream) { foreach (Stream stream in Streams) { try { stream.Dispose(); } catch (ObjectDisposedException) {} } Streams.Clear(); } GC.SuppressFinalize(this); } #endregion protected static string VolumeNumber(int num) { if (num < 10) { return ".00" + num.ToString(CultureInfo.InvariantCulture); } if (num > 9 && num < 100) { return ".0" + num.ToString(CultureInfo.InvariantCulture); } if (num > 99 && num < 1000) { return "." + num.ToString(CultureInfo.InvariantCulture); } return String.Empty; } private int StreamNumberByOffset(long offset) { foreach (int number in StreamOffsets.Keys) { if (StreamOffsets[number].Key <= offset && StreamOffsets[number].Value >= offset) { return number; } } return -1; } public void Seek(long offset, SeekOrigin seekOrigin, IntPtr newPosition) { long absolutePosition = (seekOrigin == SeekOrigin.Current) ? Position + offset : offset; CurrentStream = StreamNumberByOffset(absolutePosition); long delta = Streams[CurrentStream].Seek( absolutePosition - StreamOffsets[CurrentStream].Key, SeekOrigin.Begin); Position = StreamOffsets[CurrentStream].Key + delta; if (newPosition != IntPtr.Zero) { Marshal.WriteInt64(newPosition, Position); } } } /// /// IInStream wrapper used in stream multi volume read operations. /// internal sealed class InMultiStreamWrapper : MultiStreamWrapper, ISequentialInStream, IInStream { /// /// Initializes a new instance of the InMultiStreamWrapper class. /// /// The archive file name. /// Perform Dispose() if requested to. public InMultiStreamWrapper(string fileName, bool dispose) : base(dispose) { string baseName = fileName.Substring(0, fileName.Length - 4); int i = 0; while (File.Exists(fileName)) { Streams.Add(new FileStream(fileName, FileMode.Open)); long length = Streams[i].Length; StreamOffsets.Add(i++, new KeyValuePair(StreamLength, StreamLength + length)); StreamLength += length; fileName = baseName + VolumeNumber(i + 1); } } #region ISequentialInStream Members /// /// Reads data from the stream. /// /// A data array. /// The array size. /// The read bytes count. public int Read(byte[] data, uint size) { var readSize = (int) size; int readCount = Streams[CurrentStream].Read(data, 0, readSize); readSize -= readCount; Position += readCount; while (readCount < (int) size) { if (CurrentStream == Streams.Count - 1) { return readCount; } CurrentStream++; Streams[CurrentStream].Seek(0, SeekOrigin.Begin); int count = Streams[CurrentStream].Read(data, readCount, readSize); readCount += count; readSize -= count; Position += count; } return readCount; } #endregion } #if COMPRESS /// /// IOutStream wrapper used in multi volume stream write operations. /// internal sealed class OutMultiStreamWrapper : MultiStreamWrapper, ISequentialOutStream, IOutStream { private readonly string _archiveName; private readonly int _volumeSize; private long _overallLength; /// /// Initializes a new instance of the OutMultiStreamWrapper class. /// /// The archive name. /// The volume size. public OutMultiStreamWrapper(string archiveName, int volumeSize) : base(true) { _archiveName = archiveName; _volumeSize = volumeSize; CurrentStream = -1; NewVolumeStream(); } #region IOutStream Members public int SetSize(long newSize) { return 0; } #endregion #region ISequentialOutStream Members public int Write(byte[] data, uint size, IntPtr processedSize) { int offset = 0; var originalSize = (int) size; Position += size; _overallLength = Math.Max(Position + 1, _overallLength); while (size > _volumeSize - Streams[CurrentStream].Position) { var count = (int) (_volumeSize - Streams[CurrentStream].Position); Streams[CurrentStream].Write(data, offset, count); size -= (uint) count; offset += count; NewVolumeStream(); } Streams[CurrentStream].Write(data, offset, (int) size); if (processedSize != IntPtr.Zero) { Marshal.WriteInt32(processedSize, originalSize); } return 0; } #endregion public override void Dispose() { int lastIndex = Streams.Count - 1; Streams[lastIndex].SetLength(lastIndex > 0? Streams[lastIndex].Position : _overallLength); base.Dispose(); } private void NewVolumeStream() { CurrentStream++; Streams.Add(File.Create(_archiveName + VolumeNumber(CurrentStream + 1))); Streams[CurrentStream].SetLength(_volumeSize); StreamOffsets.Add(CurrentStream, new KeyValuePair(0, _volumeSize - 1)); } } #endif internal sealed class FakeOutStreamWrapper : ISequentialOutStream, IDisposable { #region IDisposable Members public void Dispose() { GC.SuppressFinalize(this); } #endregion #region ISequentialOutStream Members /// /// Does nothing except calling the BytesWritten event /// /// Data array /// Array size /// Count of written bytes /// Zero if Ok public int Write(byte[] data, uint size, IntPtr processedSize) { OnBytesWritten(new IntEventArgs((int) size)); if (processedSize != IntPtr.Zero) { Marshal.WriteInt32(processedSize, (int) size); } return 0; } #endregion /// /// Occurs when IntEventArgs.Value bytes were written /// public event EventHandler BytesWritten; private void OnBytesWritten(IntEventArgs e) { if (BytesWritten != null) { BytesWritten(this, e); } } } #endif }