/* 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 .
*/
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
}