BizHawk/BizHawk.Client.Common/7z/StreamWrappers.cs

545 lines
18 KiB
C#

/* 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 <http://www.gnu.org/licenses/>.
*/
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
/// <summary>
/// A class that has DisposeStream property.
/// </summary>
internal class DisposeVariableWrapper
{
public bool DisposeStream { protected get; set; }
protected DisposeVariableWrapper(bool disposeStream) { DisposeStream = disposeStream; }
}
/// <summary>
/// Stream wrapper used in InStreamWrapper
/// </summary>
internal class StreamWrapper : DisposeVariableWrapper, IDisposable
{
/// <summary>
/// File name associated with the stream (for date fix)
/// </summary>
private readonly string _fileName;
private readonly DateTime _fileTime;
/// <summary>
/// Worker stream for reading, writing and seeking.
/// </summary>
private Stream _baseStream;
/// <summary>
/// Initializes a new instance of the StreamWrapper class
/// </summary>
/// <param name="baseStream">Worker stream for reading, writing and seeking</param>
/// <param name="fileName">File name associated with the stream (for attributes fix)</param>
/// <param name="time">File last write time (for attributes fix)</param>
/// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
protected StreamWrapper(Stream baseStream, string fileName, DateTime time, bool disposeStream)
: base(disposeStream)
{
_baseStream = baseStream;
_fileName = fileName;
_fileTime = time;
}
/// <summary>
/// Initializes a new instance of the StreamWrapper class
/// </summary>
/// <param name="baseStream">Worker stream for reading, writing and seeking</param>
/// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
protected StreamWrapper(Stream baseStream, bool disposeStream)
: base(disposeStream)
{
_baseStream = baseStream;
}
/// <summary>
/// Gets the worker stream for reading, writing and seeking.
/// </summary>
protected Stream BaseStream
{
get
{
return _baseStream;
}
}
#region IDisposable Members
/// <summary>
/// Cleans up any resources used and fixes file attributes.
/// </summary>
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);
}
}
}
}
/// <summary>
/// IInStream wrapper used in stream read operations.
/// </summary>
internal sealed class InStreamWrapper : StreamWrapper, ISequentialInStream, IInStream
{
/// <summary>
/// Initializes a new instance of the InStreamWrapper class.
/// </summary>
/// <param name="baseStream">Stream for writing data</param>
/// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
public InStreamWrapper(Stream baseStream, bool disposeStream) : base(baseStream, disposeStream) { }
#region ISequentialInStream Members
/// <summary>
/// Reads data from the stream.
/// </summary>
/// <param name="data">A data array.</param>
/// <param name="size">The array size.</param>
/// <returns>The read bytes count.</returns>
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
/// <summary>
/// Occurs when IntEventArgs.Value bytes were read from the source.
/// </summary>
public event EventHandler<IntEventArgs> BytesRead;
private void OnBytesRead(IntEventArgs e)
{
if (BytesRead != null)
{
BytesRead(this, e);
}
}
}
/// <summary>
/// IOutStream wrapper used in stream write operations.
/// </summary>
internal sealed class OutStreamWrapper : StreamWrapper, ISequentialOutStream, IOutStream
{
/// <summary>
/// Initializes a new instance of the OutStreamWrapper class
/// </summary>
/// <param name="baseStream">Stream for writing data</param>
/// <param name="fileName">File name (for attributes fix)</param>
/// <param name="time">Time of the file creation (for attributes fix)</param>
/// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
public OutStreamWrapper(Stream baseStream, string fileName, DateTime time, bool disposeStream) :
base(baseStream, fileName, time, disposeStream) {}
/// <summary>
/// Initializes a new instance of the OutStreamWrapper class
/// </summary>
/// <param name="baseStream">Stream for writing data</param>
/// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
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
/// <summary>
/// Writes data to the stream
/// </summary>
/// <param name="data">Data array</param>
/// <param name="size">Array size</param>
/// <param name="processedSize">Count of written bytes</param>
/// <returns>Zero if Ok</returns>
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
/// <summary>
/// Occurs when IntEventArgs.Value bytes were written.
/// </summary>
public event EventHandler<IntEventArgs> BytesWritten;
private void OnBytesWritten(IntEventArgs e)
{
if (BytesWritten != null)
{
BytesWritten(this, e);
}
}
}
/// <summary>
/// Base multi volume stream wrapper class.
/// </summary>
internal class MultiStreamWrapper : DisposeVariableWrapper, IDisposable
{
protected readonly Dictionary<int, KeyValuePair<long, long>> StreamOffsets =
new Dictionary<int, KeyValuePair<long, long>>();
protected readonly List<Stream> Streams = new List<Stream>();
protected int CurrentStream;
protected long Position;
protected long StreamLength;
/// <summary>
/// Initializes a new instance of the MultiStreamWrapper class.
/// </summary>
/// <param name="dispose">Perform Dispose() if requested to.</param>
protected MultiStreamWrapper(bool dispose) : base(dispose) {}
/// <summary>
/// Gets the total length of input data.
/// </summary>
public long Length
{
get
{
return StreamLength;
}
}
#region IDisposable Members
/// <summary>
/// Cleans up any resources used and fixes file attributes.
/// </summary>
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 "";
}
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);
}
}
}
/// <summary>
/// IInStream wrapper used in stream multi volume read operations.
/// </summary>
internal sealed class InMultiStreamWrapper : MultiStreamWrapper, ISequentialInStream, IInStream
{
/// <summary>
/// Initializes a new instance of the InMultiStreamWrapper class.
/// </summary>
/// <param name="fileName">The archive file name.</param>
/// <param name="dispose">Perform Dispose() if requested to.</param>
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<long, long>(StreamLength, StreamLength + length));
StreamLength += length;
fileName = baseName + VolumeNumber(i + 1);
}
}
#region ISequentialInStream Members
/// <summary>
/// Reads data from the stream.
/// </summary>
/// <param name="data">A data array.</param>
/// <param name="size">The array size.</param>
/// <returns>The read bytes count.</returns>
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
/// <summary>
/// IOutStream wrapper used in multi volume stream write operations.
/// </summary>
internal sealed class OutMultiStreamWrapper : MultiStreamWrapper, ISequentialOutStream, IOutStream
{
private readonly string _archiveName;
private readonly int _volumeSize;
private long _overallLength;
/// <summary>
/// Initializes a new instance of the OutMultiStreamWrapper class.
/// </summary>
/// <param name="archiveName">The archive name.</param>
/// <param name="volumeSize">The volume size.</param>
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<long, long>(0, _volumeSize - 1));
}
}
#endif
internal sealed class FakeOutStreamWrapper : ISequentialOutStream, IDisposable
{
#region IDisposable Members
public void Dispose()
{
GC.SuppressFinalize(this);
}
#endregion
#region ISequentialOutStream Members
/// <summary>
/// Does nothing except calling the BytesWritten event
/// </summary>
/// <param name="data">Data array</param>
/// <param name="size">Array size</param>
/// <param name="processedSize">Count of written bytes</param>
/// <returns>Zero if Ok</returns>
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
/// <summary>
/// Occurs when IntEventArgs.Value bytes were written
/// </summary>
public event EventHandler<IntEventArgs> BytesWritten;
private void OnBytesWritten(IntEventArgs e)
{
if (BytesWritten != null)
{
BytesWritten(this, e);
}
}
}
#endif
}