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

1451 lines
55 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.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
#if DOTNET20
using System.Threading;
#else
using System.Linq;
#endif
using SevenZip.Sdk.Compression.Lzma;
#if MONO
using SevenZip.Mono.COM;
#endif
namespace SevenZip
{
/// <summary>
/// Class to unpack data from archives supported by 7-Zip.
/// </summary>
/// <example>
/// using (var extr = new SevenZipExtractor(@"C:\Test.7z"))
/// {
/// extr.ExtractArchive(@"C:\TestDirectory");
/// }
/// </example>
public sealed partial class SevenZipExtractor
#if UNMANAGED
: SevenZipBase, IDisposable
#endif
{
#if UNMANAGED
private List<ArchiveFileInfo> _archiveFileData;
private IInArchive _archive;
private IInStream _archiveStream;
private int _offset;
private ArchiveOpenCallback _openCallback;
private string _fileName;
private Stream _inStream;
private long? _packedSize;
private long? _unpackedSize;
private uint? _filesCount;
private bool? _isSolid;
private bool _opened;
private bool _disposed;
private InArchiveFormat _format = (InArchiveFormat)(-1);
private ReadOnlyCollection<ArchiveFileInfo> _archiveFileInfoCollection;
private ReadOnlyCollection<ArchiveProperty> _archiveProperties;
private ReadOnlyCollection<string> _volumeFileNames;
/// <summary>
/// This is used to lock possible Dispose() calls.
/// </summary>
private bool _asynchronousDisposeLock;
#region Constructors
/// <summary>
/// General initialization function.
/// </summary>
/// <param name="archiveFullName">The archive file name.</param>
private void Init(string archiveFullName)
{
_fileName = archiveFullName;
bool isExecutable = false;
if ((int)_format == -1)
{
_format = FileChecker.CheckSignature(archiveFullName, out _offset, out isExecutable);
}
PreserveDirectoryStructure = true;
SevenZipLibraryManager.LoadLibrary(this, _format);
try
{
_archive = SevenZipLibraryManager.InArchive(_format, this);
}
catch (SevenZipLibraryException)
{
SevenZipLibraryManager.FreeLibrary(this, _format);
throw;
}
if (isExecutable && _format != InArchiveFormat.PE)
{
if (!Check())
{
CommonDispose();
_format = InArchiveFormat.PE;
SevenZipLibraryManager.LoadLibrary(this, _format);
try
{
_archive = SevenZipLibraryManager.InArchive(_format, this);
}
catch (SevenZipLibraryException)
{
SevenZipLibraryManager.FreeLibrary(this, _format);
throw;
}
}
}
}
/// <summary>
/// General initialization function.
/// </summary>
/// <param name="stream">The stream to read the archive from.</param>
private void Init(Stream stream)
{
ValidateStream(stream);
bool isExecutable = false;
if ((int)_format == -1)
{
_format = FileChecker.CheckSignature(stream, out _offset, out isExecutable);
}
PreserveDirectoryStructure = true;
SevenZipLibraryManager.LoadLibrary(this, _format);
try
{
_inStream = new ArchiveEmulationStreamProxy(stream, _offset);
_packedSize = stream.Length;
_archive = SevenZipLibraryManager.InArchive(_format, this);
}
catch (SevenZipLibraryException)
{
SevenZipLibraryManager.FreeLibrary(this, _format);
throw;
}
if (isExecutable && _format != InArchiveFormat.PE)
{
if (!Check())
{
CommonDispose();
_format = InArchiveFormat.PE;
try
{
_inStream = new ArchiveEmulationStreamProxy(stream, _offset);
_packedSize = stream.Length;
_archive = SevenZipLibraryManager.InArchive(_format, this);
}
catch (SevenZipLibraryException)
{
SevenZipLibraryManager.FreeLibrary(this, _format);
throw;
}
}
}
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveStream">The stream to read the archive from.
/// Use SevenZipExtractor(string) to extract from disk, though it is not necessary.</param>
/// <remarks>The archive format is guessed by the signature.</remarks>
public SevenZipExtractor(Stream archiveStream)
{
Init(archiveStream);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveStream">The stream to read the archive from.
/// Use SevenZipExtractor(string) to extract from disk, though it is not necessary.</param>
/// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
/// Instead, use SevenZipExtractor(Stream archiveStream), that constructor
/// automatically detects the archive format.</param>
public SevenZipExtractor(Stream archiveStream, InArchiveFormat format)
{
_format = format;
Init(archiveStream);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveFullName">The archive full file name.</param>
public SevenZipExtractor(string archiveFullName)
{
Init(archiveFullName);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveFullName">The archive full file name.</param>
/// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
/// Instead, use SevenZipExtractor(string archiveFullName), that constructor
/// automatically detects the archive format.</param>
public SevenZipExtractor(string archiveFullName, InArchiveFormat format)
{
_format = format;
Init(archiveFullName);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveFullName">The archive full file name.</param>
/// <param name="password">Password for an encrypted archive.</param>
public SevenZipExtractor(string archiveFullName, string password)
: base(password)
{
Init(archiveFullName);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveFullName">The archive full file name.</param>
/// <param name="password">Password for an encrypted archive.</param>
/// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
/// Instead, use SevenZipExtractor(string archiveFullName, string password), that constructor
/// automatically detects the archive format.</param>
public SevenZipExtractor(string archiveFullName, string password, InArchiveFormat format)
: base(password)
{
_format = format;
Init(archiveFullName);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveStream">The stream to read the archive from.</param>
/// <param name="password">Password for an encrypted archive.</param>
/// <remarks>The archive format is guessed by the signature.</remarks>
public SevenZipExtractor(Stream archiveStream, string password)
: base(password)
{
Init(archiveStream);
}
/// <summary>
/// Initializes a new instance of SevenZipExtractor class.
/// </summary>
/// <param name="archiveStream">The stream to read the archive from.</param>
/// <param name="password">Password for an encrypted archive.</param>
/// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
/// Instead, use SevenZipExtractor(Stream archiveStream, string password), that constructor
/// automatically detects the archive format.</param>
public SevenZipExtractor(Stream archiveStream, string password, InArchiveFormat format)
: base(password)
{
_format = format;
Init(archiveStream);
}
#endregion
#region Properties
/// <summary>
/// Gets or sets archive full file name
/// </summary>
public string FileName
{
get
{
DisposedCheck();
return _fileName;
}
}
/// <summary>
/// Gets the size of the archive file
/// </summary>
public long PackedSize
{
get
{
DisposedCheck();
return _packedSize.HasValue
?
_packedSize.Value
:
_fileName != null
?
(new FileInfo(_fileName)).Length
:
-1;
}
}
/// <summary>
/// Gets the size of unpacked archive data
/// </summary>
public long UnpackedSize
{
get
{
DisposedCheck();
if (!_unpackedSize.HasValue)
{
return -1;
}
return _unpackedSize.Value;
}
}
/// <summary>
/// Gets a value indicating whether the archive is solid
/// </summary>
public bool IsSolid
{
get
{
DisposedCheck();
if (!_isSolid.HasValue)
{
GetArchiveInfo(true);
}
Debug.Assert(_isSolid != null);
return _isSolid.Value;
}
}
/// <summary>
/// Gets the number of files in the archive
/// </summary>
public uint FilesCount
{
get
{
DisposedCheck();
if (!_filesCount.HasValue)
{
GetArchiveInfo(true);
}
Debug.Assert(_filesCount != null);
return _filesCount.Value;
}
}
/// <summary>
/// Gets archive format
/// </summary>
public InArchiveFormat Format
{
get
{
DisposedCheck();
return _format;
}
}
/// <summary>
/// Gets or sets the value indicating whether to preserve the directory structure of extracted files.
/// </summary>
public bool PreserveDirectoryStructure { get; set; }
#endregion
/// <summary>
/// Checked whether the class was disposed.
/// </summary>
/// <exception cref="System.ObjectDisposedException" />
private void DisposedCheck()
{
if (_disposed)
{
throw new ObjectDisposedException("SevenZipExtractor");
}
#if !WINCE
RecreateInstanceIfNeeded();
#endif
}
#region Core private functions
private ArchiveOpenCallback GetArchiveOpenCallback()
{
return _openCallback ?? (_openCallback = String.IsNullOrEmpty(Password)
? new ArchiveOpenCallback(_fileName)
: new ArchiveOpenCallback(_fileName, Password));
}
/// <summary>
/// Gets the archive input stream.
/// </summary>
/// <returns>The archive input wrapper stream.</returns>
private IInStream GetArchiveStream(bool dispose)
{
if (_archiveStream != null)
{
if (_archiveStream is DisposeVariableWrapper)
{
(_archiveStream as DisposeVariableWrapper).DisposeStream = dispose;
}
return _archiveStream;
}
if (_inStream != null)
{
_inStream.Seek(0, SeekOrigin.Begin);
_archiveStream = new InStreamWrapper(_inStream, false);
}
else
{
if (!_fileName.EndsWith(".001", StringComparison.OrdinalIgnoreCase)
|| (_volumeFileNames.Count == 1))
{
_archiveStream = new InStreamWrapper(
new ArchiveEmulationStreamProxy(new FileStream(
_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
_offset),
dispose);
}
else
{
_archiveStream = new InMultiStreamWrapper(_fileName, dispose);
_packedSize = (_archiveStream as InMultiStreamWrapper).Length;
}
}
return _archiveStream;
}
/// <summary>
/// Opens the archive and throws exceptions or returns OperationResult.DataError if any error occurs.
/// </summary>
/// <param name="archiveStream">The IInStream compliant class instance, that is, the input stream.</param>
/// <param name="openCallback">The ArchiveOpenCallback instance.</param>
/// <returns>OperationResult.Ok if Open() succeeds.</returns>
private OperationResult OpenArchiveInner(IInStream archiveStream,
IArchiveOpenCallback openCallback)
{
ulong checkPos = 1 << 15;
int res = _archive.Open(archiveStream, ref checkPos, openCallback);
return (OperationResult)res;
}
/// <summary>
/// Opens the archive and throws exceptions or returns OperationResult.DataError if any error occurs.
/// </summary>
/// <param name="archiveStream">The IInStream compliant class instance, that is, the input stream.</param>
/// <param name="openCallback">The ArchiveOpenCallback instance.</param>
/// <returns>True if Open() succeeds; otherwise, false.</returns>
private bool OpenArchive(IInStream archiveStream,
ArchiveOpenCallback openCallback)
{
if (!_opened)
{
if (OpenArchiveInner(archiveStream, openCallback) != OperationResult.Ok)
{
if (!ThrowException(null, new SevenZipArchiveException()))
{
return false;
}
}
_volumeFileNames = new ReadOnlyCollection<string>(openCallback.VolumeFileNames);
_opened = true;
}
return true;
}
/// <summary>
/// Retrieves all information about the archive.
/// </summary>
/// <exception cref="SevenZip.SevenZipArchiveException"/>
private void GetArchiveInfo(bool disposeStream)
{
if (_archive == null)
{
if (!ThrowException(null, new SevenZipArchiveException()))
{
return;
}
}
else
{
IInStream archiveStream;
using ((archiveStream = GetArchiveStream(disposeStream)) as IDisposable)
{
var openCallback = GetArchiveOpenCallback();
if (!_opened)
{
if (!OpenArchive(archiveStream, openCallback))
{
return;
}
_opened = !disposeStream;
}
_filesCount = _archive.GetNumberOfItems();
_archiveFileData = new List<ArchiveFileInfo>((int)_filesCount);
if (_filesCount != 0)
{
var data = new PropVariant();
try
{
#region Getting archive items data
for (uint i = 0; i < _filesCount; i++)
{
try
{
var fileInfo = new ArchiveFileInfo { Index = (int)i };
_archive.GetProperty(i, ItemPropId.Path, ref data);
fileInfo.FileName = NativeMethods.SafeCast(data, "[no name]");
_archive.GetProperty(i, ItemPropId.LastWriteTime, ref data);
fileInfo.LastWriteTime = NativeMethods.SafeCast(data, DateTime.Now);
_archive.GetProperty(i, ItemPropId.CreationTime, ref data);
fileInfo.CreationTime = NativeMethods.SafeCast(data, DateTime.Now);
_archive.GetProperty(i, ItemPropId.LastAccessTime, ref data);
fileInfo.LastAccessTime = NativeMethods.SafeCast(data, DateTime.Now);
_archive.GetProperty(i, ItemPropId.Size, ref data);
fileInfo.Size = NativeMethods.SafeCast<ulong>(data, 0);
if (fileInfo.Size == 0)
{
fileInfo.Size = NativeMethods.SafeCast<uint>(data, 0);
}
_archive.GetProperty(i, ItemPropId.Attributes, ref data);
fileInfo.Attributes = NativeMethods.SafeCast<uint>(data, 0);
_archive.GetProperty(i, ItemPropId.IsDirectory, ref data);
fileInfo.IsDirectory = NativeMethods.SafeCast(data, false);
_archive.GetProperty(i, ItemPropId.Encrypted, ref data);
fileInfo.Encrypted = NativeMethods.SafeCast(data, false);
_archive.GetProperty(i, ItemPropId.Crc, ref data);
fileInfo.Crc = NativeMethods.SafeCast<uint>(data, 0);
_archive.GetProperty(i, ItemPropId.Comment, ref data);
fileInfo.Comment = NativeMethods.SafeCast(data, "");
_archiveFileData.Add(fileInfo);
}
catch (InvalidCastException)
{
ThrowException(null, new SevenZipArchiveException("probably archive is corrupted."));
}
}
#endregion
#region Getting archive properties
uint numProps = _archive.GetNumberOfArchiveProperties();
var archProps = new List<ArchiveProperty>((int)numProps);
for (uint i = 0; i < numProps; i++)
{
string propName;
ItemPropId propId;
ushort varType;
_archive.GetArchivePropertyInfo(i, out propName, out propId, out varType);
_archive.GetArchiveProperty(propId, ref data);
if (propId == ItemPropId.Solid)
{
_isSolid = NativeMethods.SafeCast(data, true);
}
// TODO Add more archive properties
if (PropIdToName.PropIdNames.ContainsKey(propId))
{
archProps.Add(new ArchiveProperty
{
Name = PropIdToName.PropIdNames[propId],
Value = data.Object
});
}
else
{
Debug.WriteLine(
"An unknown archive property encountered (code " +
((int)propId).ToString(CultureInfo.InvariantCulture) + ')');
}
}
_archiveProperties = new ReadOnlyCollection<ArchiveProperty>(archProps);
if (!_isSolid.HasValue && _format == InArchiveFormat.Zip)
{
_isSolid = false;
}
if (!_isSolid.HasValue)
{
_isSolid = true;
}
#endregion
}
catch (Exception)
{
if (openCallback.ThrowException())
{
throw;
}
}
}
}
if (disposeStream)
{
_archive.Close();
_archiveStream = null;
}
_archiveFileInfoCollection = new ReadOnlyCollection<ArchiveFileInfo>(_archiveFileData);
}
}
/// <summary>
/// Ensure that _archiveFileData is loaded.
/// </summary>
/// <param name="disposeStream">Dispose the archive stream after this operation.</param>
private void InitArchiveFileData(bool disposeStream)
{
if (_archiveFileData == null)
{
GetArchiveInfo(disposeStream);
}
}
/// <summary>
/// Produces an array of indexes from 0 to the maximum value in the specified array
/// </summary>
/// <param name="indexes">The source array</param>
/// <returns>The array of indexes from 0 to the maximum value in the specified array</returns>
private static uint[] SolidIndexes(uint[] indexes)
{
#if CS4
int max = indexes.Aggregate(0, (current, i) => Math.Max(current, (int) i));
#else
int max = 0;
foreach (uint i in indexes)
{
max = Math.Max(max, (int)i);
}
#endif
if (max > 0)
{
max++;
var res = new uint[max];
for (int i = 0; i < max; i++)
{
res[i] = (uint)i;
}
return res;
}
return indexes;
}
/// <summary>
/// Checkes whether all the indexes are valid.
/// </summary>
/// <param name="indexes">The indexes to check.</param>
/// <returns>True is valid; otherwise, false.</returns>
private static bool CheckIndexes(params int[] indexes)
{
#if CS4 // Wow, C# 4 is great!
return indexes.All(i => i >= 0);
#else
bool res = true;
foreach (int i in indexes)
{
if (i < 0)
{
res = false;
break;
}
}
return res;
#endif
}
private void ArchiveExtractCallbackCommonInit(ArchiveExtractCallback aec)
{
aec.Open += ((s, e) => { _unpackedSize = (long)e.TotalSize; });
aec.FileExtractionStarted += FileExtractionStartedEventProxy;
aec.FileExtractionFinished += FileExtractionFinishedEventProxy;
aec.Extracting += ExtractingEventProxy;
aec.FileExists += FileExistsEventProxy;
}
/// <summary>
/// Gets the IArchiveExtractCallback callback
/// </summary>
/// <param name="directory">The directory where extract the files</param>
/// <param name="filesCount">The number of files to be extracted</param>
/// <param name="actualIndexes">The list of actual indexes (solid archives support)</param>
/// <returns>The ArchiveExtractCallback callback</returns>
private ArchiveExtractCallback GetArchiveExtractCallback(string directory, int filesCount,
List<uint> actualIndexes)
{
var aec = String.IsNullOrEmpty(Password)
? new ArchiveExtractCallback(_archive, directory, filesCount, PreserveDirectoryStructure, actualIndexes, this)
: new ArchiveExtractCallback(_archive, directory, filesCount, PreserveDirectoryStructure, actualIndexes, Password, this);
ArchiveExtractCallbackCommonInit(aec);
return aec;
}
/// <summary>
/// Gets the IArchiveExtractCallback callback
/// </summary>
/// <param name="stream">The stream where extract the file</param>
/// <param name="index">The file index</param>
/// <param name="filesCount">The number of files to be extracted</param>
/// <returns>The ArchiveExtractCallback callback</returns>
private ArchiveExtractCallback GetArchiveExtractCallback(Stream stream, uint index, int filesCount)
{
var aec = String.IsNullOrEmpty(Password)
? new ArchiveExtractCallback(_archive, stream, filesCount, index, this)
: new ArchiveExtractCallback(_archive, stream, filesCount, index, Password, this);
ArchiveExtractCallbackCommonInit(aec);
return aec;
}
private void FreeArchiveExtractCallback(ArchiveExtractCallback callback)
{
callback.Open -= ((s, e) => { _unpackedSize = (long)e.TotalSize; });
callback.FileExtractionStarted -= FileExtractionStartedEventProxy;
callback.FileExtractionFinished -= FileExtractionFinishedEventProxy;
callback.Extracting -= ExtractingEventProxy;
callback.FileExists -= FileExistsEventProxy;
}
#endregion
#endif
/// <summary>
/// Checks if the specified stream supports extraction.
/// </summary>
/// <param name="stream">The stream to check.</param>
private static void ValidateStream(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
if (!stream.CanSeek || !stream.CanRead)
{
throw new ArgumentException("The specified stream can not seek or read.", "stream");
}
if (stream.Length == 0)
{
throw new ArgumentException("The specified stream has zero length.", "stream");
}
}
#if UNMANAGED
#region IDisposable Members
private void CommonDispose()
{
if (_opened)
{
try
{
if (_archive != null)
{
_archive.Close();
}
}
catch (Exception) { }
}
_archive = null;
_archiveFileData = null;
_archiveProperties = null;
_archiveFileInfoCollection = null;
if (_inStream != null)
{
_inStream.Dispose();
}
_inStream = null;
if (_openCallback != null)
{
try
{
_openCallback.Dispose();
}
catch (ObjectDisposedException) { }
_openCallback = null;
}
if (_archiveStream != null)
{
if (_archiveStream is IDisposable)
{
try
{
if (_archiveStream is DisposeVariableWrapper)
{
(_archiveStream as DisposeVariableWrapper).DisposeStream = true;
}
(_archiveStream as IDisposable).Dispose();
}
catch (ObjectDisposedException) { }
_archiveStream = null;
}
}
SevenZipLibraryManager.FreeLibrary(this, _format);
}
/// <summary>
/// Releases the unmanaged resources used by SevenZipExtractor.
/// </summary>
public void Dispose()
{
if (_asynchronousDisposeLock)
{
throw new InvalidOperationException("SevenZipExtractor instance must not be disposed " +
"while making an asynchronous method call.");
}
if (!_disposed)
{
CommonDispose();
}
_disposed = true;
GC.SuppressFinalize(this);
}
#endregion
#region Core public Members
#region Events
/// <summary>
/// Occurs when a new file is going to be unpacked.
/// </summary>
/// <remarks>Occurs when 7-zip engine requests for an output stream for a new file to unpack in.</remarks>
public event EventHandler<FileInfoEventArgs> FileExtractionStarted;
/// <summary>
/// Occurs when a file has been successfully unpacked.
/// </summary>
public event EventHandler<FileInfoEventArgs> FileExtractionFinished;
/// <summary>
/// Occurs when the archive has been unpacked.
/// </summary>
public event EventHandler<EventArgs> ExtractionFinished;
/// <summary>
/// Occurs when data are being extracted.
/// </summary>
/// <remarks>Use this event for accurate progress handling and various ProgressBar.StepBy(e.PercentDelta) routines.</remarks>
public event EventHandler<ProgressEventArgs> Extracting;
/// <summary>
/// Occurs during the extraction when a file already exists.
/// </summary>
public event EventHandler<FileOverwriteEventArgs> FileExists;
#region Event proxies
/// <summary>
/// Event proxy for FileExtractionStarted.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private void FileExtractionStartedEventProxy(object sender, FileInfoEventArgs e)
{
OnEvent(FileExtractionStarted, e, true);
}
/// <summary>
/// Event proxy for FileExtractionFinished.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private void FileExtractionFinishedEventProxy(object sender, FileInfoEventArgs e)
{
OnEvent(FileExtractionFinished, e, true);
}
/// <summary>
/// Event proxy for Extractng.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private void ExtractingEventProxy(object sender, ProgressEventArgs e)
{
OnEvent(Extracting, e, false);
}
/// <summary>
/// Event proxy for FileExists.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
private void FileExistsEventProxy(object sender, FileOverwriteEventArgs e)
{
OnEvent(FileExists, e, true);
}
#endregion
#endregion
#region Properties
/// <summary>
/// Gets the collection of ArchiveFileInfo with all information about files in the archive
/// </summary>
public ReadOnlyCollection<ArchiveFileInfo> ArchiveFileData
{
get
{
DisposedCheck();
InitArchiveFileData(true);
return _archiveFileInfoCollection;
}
}
/// <summary>
/// Gets the properties for the current archive
/// </summary>
public ReadOnlyCollection<ArchiveProperty> ArchiveProperties
{
get
{
DisposedCheck();
InitArchiveFileData(true);
return _archiveProperties;
}
}
/// <summary>
/// Gets the collection of all file names contained in the archive.
/// </summary>
/// <remarks>
/// Each get recreates the collection
/// </remarks>
public ReadOnlyCollection<string> ArchiveFileNames
{
get
{
DisposedCheck();
InitArchiveFileData(true);
var fileNames = new List<string>(_archiveFileData.Count);
#if CS4
fileNames.AddRange(_archiveFileData.Select(afi => afi.FileName));
#else
foreach (var afi in _archiveFileData)
{
fileNames.Add(afi.FileName);
}
#endif
return new ReadOnlyCollection<string>(fileNames);
}
}
/// <summary>
/// Gets the list of archive volume file names.
/// </summary>
public ReadOnlyCollection<string> VolumeFileNames
{
get
{
DisposedCheck();
InitArchiveFileData(true);
return _volumeFileNames;
}
}
#endregion
/// <summary>
/// Performs the archive integrity test.
/// </summary>
/// <returns>True is the archive is ok; otherwise, false.</returns>
public bool Check()
{
DisposedCheck();
try
{
InitArchiveFileData(false);
var archiveStream = GetArchiveStream(true);
var openCallback = GetArchiveOpenCallback();
if (!OpenArchive(archiveStream, openCallback))
{
return false;
}
using (var aec = GetArchiveExtractCallback("", (int)_filesCount, null))
{
try
{
CheckedExecute(
_archive.Extract(null, UInt32.MaxValue, 1, aec),
SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
}
finally
{
FreeArchiveExtractCallback(aec);
}
}
}
catch (Exception)
{
return false;
}
finally
{
if (_archive != null)
{
_archive.Close();
}
((InStreamWrapper)_archiveStream).Dispose();
_archiveStream = null;
_opened = false;
}
return true;
}
#region ExtractFile overloads
/// <summary>
/// Unpacks the file by its name to the specified stream.
/// </summary>
/// <param name="fileName">The file full name in the archive file table.</param>
/// <param name="stream">The stream where the file is to be unpacked.</param>
public void ExtractFile(string fileName, Stream stream)
{
DisposedCheck();
InitArchiveFileData(false);
int index = -1;
foreach (ArchiveFileInfo afi in _archiveFileData)
{
if (afi.FileName == fileName && !afi.IsDirectory)
{
index = afi.Index;
break;
}
}
if (index == -1)
{
if (!ThrowException(null, new ArgumentOutOfRangeException(
"fileName",
"The specified file name was not found in the archive file table.")))
{
return;
}
}
else
{
ExtractFile(index, stream);
}
}
/// <summary>
/// Unpacks the file by its index to the specified stream.
/// </summary>
/// <param name="index">Index in the archive file table.</param>
/// <param name="stream">The stream where the file is to be unpacked.</param>
public void ExtractFile(int index, Stream stream)
{
DisposedCheck();
ClearExceptions();
if (!CheckIndexes(index))
{
if (!ThrowException(null, new ArgumentException("The index must be more or equal to zero.", "index")))
{
return;
}
}
if (!stream.CanWrite)
{
if (!ThrowException(null, new ArgumentException("The specified stream can not be written.", "stream")))
{
return;
}
}
InitArchiveFileData(false);
if (index > _filesCount - 1)
{
if (!ThrowException(null, new ArgumentOutOfRangeException(
"index", "The specified index is greater than the archive files count.")))
{
return;
}
}
var indexes = new[] {(uint) index};
if (_isSolid.Value)
{
indexes = SolidIndexes(indexes);
}
var archiveStream = GetArchiveStream(false);
var openCallback = GetArchiveOpenCallback();
if (!OpenArchive(archiveStream, openCallback))
{
return;
}
try
{
using (var aec = GetArchiveExtractCallback(stream, (uint) index, indexes.Length))
{
try
{
CheckedExecute(
_archive.Extract(indexes, (uint) indexes.Length, 0, aec),
SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
}
finally
{
FreeArchiveExtractCallback(aec);
}
}
}
catch (Exception)
{
if (openCallback.ThrowException())
{
throw;
}
}
OnEvent(ExtractionFinished, EventArgs.Empty, false);
ThrowUserException();
}
#endregion
#region ExtractFiles overloads
/// <summary>
/// Unpacks files by their indices to the specified directory.
/// </summary>
/// <param name="indexes">indexes of the files in the archive file table.</param>
/// <param name="directory">Directory where the files are to be unpacked.</param>
public void ExtractFiles(string directory, params int[] indexes)
{
DisposedCheck();
ClearExceptions();
if (!CheckIndexes(indexes))
{
if (
!ThrowException(null, new ArgumentException("The indexes must be more or equal to zero.", "indexes")))
{
return;
}
}
InitArchiveFileData(false);
#region Indexes stuff
var uindexes = new uint[indexes.Length];
for (int i = 0; i < indexes.Length; i++)
{
uindexes[i] = (uint) indexes[i];
}
#if CS4
if (uindexes.Where(i => i >= _filesCount).Any(
i => !ThrowException(null,
new ArgumentOutOfRangeException("indexes",
"Index must be less than " +
_filesCount.Value.ToString(
CultureInfo.InvariantCulture) + "!"))))
{
return;
}
#else
foreach (uint i in uindexes)
{
if (i >= _filesCount)
{
if (!ThrowException(null,
new ArgumentOutOfRangeException("indexes",
"Index must be less than " +
_filesCount.Value.ToString(
CultureInfo.InvariantCulture) + "!")))
{
return;
}
}
}
#endif
var origIndexes = new List<uint>(uindexes);
origIndexes.Sort();
uindexes = origIndexes.ToArray();
if (_isSolid.Value)
{
uindexes = SolidIndexes(uindexes);
}
#endregion
try
{
IInStream archiveStream;
using ((archiveStream = GetArchiveStream(origIndexes.Count != 1)) as IDisposable)
{
var openCallback = GetArchiveOpenCallback();
if (!OpenArchive(archiveStream, openCallback))
{
return;
}
try
{
using (var aec = GetArchiveExtractCallback(directory, (int) _filesCount, origIndexes))
{
try
{
CheckedExecute(
_archive.Extract(uindexes, (uint) uindexes.Length, 0, aec),
SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
}
finally
{
FreeArchiveExtractCallback(aec);
}
}
}
catch (Exception)
{
if (openCallback.ThrowException())
{
throw;
}
}
}
OnEvent(ExtractionFinished, EventArgs.Empty, false);
}
finally
{
if (origIndexes.Count > 1)
{
if (_archive != null)
{
_archive.Close();
}
_archiveStream = null;
_opened = false;
}
}
ThrowUserException();
}
/// <summary>
/// Unpacks files by their full names to the specified directory.
/// </summary>
/// <param name="fileNames">Full file names in the archive file table.</param>
/// <param name="directory">Directory where the files are to be unpacked.</param>
public void ExtractFiles(string directory, params string[] fileNames)
{
DisposedCheck();
InitArchiveFileData(false);
var indexes = new List<int>(fileNames.Length);
var archiveFileNames = new List<string>(ArchiveFileNames);
foreach (string fn in fileNames)
{
if (!archiveFileNames.Contains(fn))
{
if (
!ThrowException(null,
new ArgumentOutOfRangeException("fileNames",
"File \"" + fn +
"\" was not found in the archive file table.")))
{
return;
}
}
else
{
foreach (ArchiveFileInfo afi in _archiveFileData)
{
if (afi.FileName == fn && !afi.IsDirectory)
{
indexes.Add(afi.Index);
break;
}
}
}
}
ExtractFiles(directory, indexes.ToArray());
}
/// <summary>
/// Extracts files from the archive, giving a callback the choice what
/// to do with each file. The order of the files is given by the archive.
/// 7-Zip (and any other solid) archives are NOT supported.
/// </summary>
/// <param name="extractFileCallback">The callback to call for each file in the archive.</param>
public void ExtractFiles(ExtractFileCallback extractFileCallback)
{
DisposedCheck();
InitArchiveFileData(false);
if (IsSolid)
{
// solid strategy
}
else
{
foreach (ArchiveFileInfo archiveFileInfo in ArchiveFileData)
{
var extractFileCallbackArgs = new ExtractFileCallbackArgs(archiveFileInfo);
extractFileCallback(extractFileCallbackArgs);
if (extractFileCallbackArgs.CancelExtraction)
{
break;
}
if (extractFileCallbackArgs.ExtractToStream != null || extractFileCallbackArgs.ExtractToFile != null)
{
bool callDone = false;
try
{
if (extractFileCallbackArgs.ExtractToStream != null)
{
ExtractFile(archiveFileInfo.Index, extractFileCallbackArgs.ExtractToStream);
}
else
{
using (var file = new FileStream(extractFileCallbackArgs.ExtractToFile, FileMode.CreateNew,
FileAccess.Write, FileShare.None, 8192))
{
ExtractFile(archiveFileInfo.Index, file);
}
}
callDone = true;
}
catch (Exception ex)
{
extractFileCallbackArgs.Exception = ex;
extractFileCallbackArgs.Reason = ExtractFileCallbackReason.Failure;
extractFileCallback(extractFileCallbackArgs);
if (!ThrowException(null, ex))
{
return;
}
}
if (callDone)
{
extractFileCallbackArgs.Reason = ExtractFileCallbackReason.Done;
extractFileCallback(extractFileCallbackArgs);
}
}
}
}
}
#endregion
/// <summary>
/// Unpacks the whole archive to the specified directory.
/// </summary>
/// <param name="directory">The directory where the files are to be unpacked.</param>
public void ExtractArchive(string directory)
{
DisposedCheck();
ClearExceptions();
InitArchiveFileData(false);
try
{
IInStream archiveStream;
using ((archiveStream = GetArchiveStream(true)) as IDisposable)
{
var openCallback = GetArchiveOpenCallback();
if (!OpenArchive(archiveStream, openCallback))
{
return;
}
try
{
using (var aec = GetArchiveExtractCallback(directory, (int) _filesCount, null))
{
try
{
CheckedExecute(
_archive.Extract(null, UInt32.MaxValue, 0, aec),
SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
OnEvent(ExtractionFinished, EventArgs.Empty, false);
}
finally
{
FreeArchiveExtractCallback(aec);
}
}
}
catch (Exception)
{
if (openCallback.ThrowException())
{
throw;
}
}
}
}
finally
{
if (_archive != null)
{
_archive.Close();
}
_archiveStream = null;
_opened = false;
}
ThrowUserException();
}
#endregion
#endif
#region LZMA SDK functions
internal static byte[] GetLzmaProperties(Stream inStream, out long outSize)
{
var lzmAproperties = new byte[5];
if (inStream.Read(lzmAproperties, 0, 5) != 5)
{
throw new LzmaException();
}
outSize = 0;
for (int i = 0; i < 8; i++)
{
int b = inStream.ReadByte();
if (b < 0)
{
throw new LzmaException();
}
outSize |= ((long) (byte) b) << (i << 3);
}
return lzmAproperties;
}
/// <summary>
/// Decompress the specified stream (C# inside)
/// </summary>
/// <param name="inStream">The source compressed stream</param>
/// <param name="outStream">The destination uncompressed stream</param>
/// <param name="inLength">The length of compressed data (null for inStream.Length)</param>
/// <param name="codeProgressEvent">The event for handling the code progress</param>
public static void DecompressStream(Stream inStream, Stream outStream, int? inLength,
EventHandler<ProgressEventArgs> codeProgressEvent)
{
if (!inStream.CanRead || !outStream.CanWrite)
{
throw new ArgumentException("The specified streams are invalid.");
}
var decoder = new Decoder();
long outSize, inSize = (inLength.HasValue ? inLength.Value : inStream.Length) - inStream.Position;
decoder.SetDecoderProperties(GetLzmaProperties(inStream, out outSize));
decoder.Code(
inStream, outStream, inSize, outSize,
new LzmaProgressCallback(inSize, codeProgressEvent));
}
/// <summary>
/// Decompress byte array compressed with LZMA algorithm (C# inside)
/// </summary>
/// <param name="data">Byte array to decompress</param>
/// <returns>Decompressed byte array</returns>
public static byte[] ExtractBytes(byte[] data)
{
using (var inStream = new MemoryStream(data))
{
var decoder = new Decoder();
inStream.Seek(0, 0);
using (var outStream = new MemoryStream())
{
long outSize;
decoder.SetDecoderProperties(GetLzmaProperties(inStream, out outSize));
decoder.Code(inStream, outStream, inStream.Length - inStream.Position, outSize, null);
return outStream.ToArray();
}
}
}
#endregion
}
}