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

807 lines
31 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.IO;
using System.Runtime.InteropServices;
#if MONO
using SevenZip.Mono.COM;
#endif
namespace SevenZip
{
#if UNMANAGED
#if COMPRESS
/// <summary>
/// Archive update callback to handle the process of packing files
/// </summary>
internal sealed class ArchiveUpdateCallback : CallbackBase, IArchiveUpdateCallback, ICryptoGetTextPassword2,
IDisposable
{
#region Fields
/// <summary>
/// _files.Count if do not count directories
/// </summary>
private int _actualFilesCount;
/// <summary>
/// For Compressing event.
/// </summary>
private long _bytesCount;
private long _bytesWritten;
private long _bytesWrittenOld;
private SevenZipCompressor _compressor;
/// <summary>
/// No directories.
/// </summary>
private bool _directoryStructure;
/// <summary>
/// Rate of the done work from [0, 1]
/// </summary>
private float _doneRate;
/// <summary>
/// The names of the archive entries
/// </summary>
private string[] _entries;
/// <summary>
/// Array of files to pack
/// </summary>
private FileInfo[] _files;
private InStreamWrapper _fileStream;
private uint _indexInArchive;
private uint _indexOffset;
/// <summary>
/// Common root of file names length.
/// </summary>
private int _rootLength;
/// <summary>
/// Input streams to be compressed.
/// </summary>
private Stream[] _streams;
private UpdateData _updateData;
private List<InStreamWrapper> _wrappersToDispose;
/// <summary>
/// Gets or sets the default item name used in MemoryStream compression.
/// </summary>
public string DefaultItemName { private get; set; }
/// <summary>
/// Gets or sets the value indicating whether to compress as fast as possible, without calling events.
/// </summary>
public bool FastCompression { private get; set; }
#if !WINCE
private int _memoryPressure;
#endif
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the ArchiveUpdateCallback class
/// </summary>
/// <param name="files">Array of files to pack</param>
/// <param name="rootLength">Common file names root length</param>
/// <param name="compressor">The owner of the callback</param>
/// <param name="updateData">The compression parameters.</param>
/// <param name="directoryStructure">Preserve directory structure.</param>
public ArchiveUpdateCallback(
FileInfo[] files, int rootLength,
SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
{
Init(files, rootLength, compressor, updateData, directoryStructure);
}
/// <summary>
/// Initializes a new instance of the ArchiveUpdateCallback class
/// </summary>
/// <param name="files">Array of files to pack</param>
/// <param name="rootLength">Common file names root length</param>
/// <param name="password">The archive password</param>
/// <param name="compressor">The owner of the callback</param>
/// <param name="updateData">The compression parameters.</param>
/// <param name="directoryStructure">Preserve directory structure.</param>
public ArchiveUpdateCallback(
FileInfo[] files, int rootLength, string password,
SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
: base(password)
{
Init(files, rootLength, compressor, updateData, directoryStructure);
}
/// <summary>
/// Initializes a new instance of the ArchiveUpdateCallback class
/// </summary>
/// <param name="stream">The input stream</param>
/// <param name="compressor">The owner of the callback</param>
/// <param name="updateData">The compression parameters.</param>
/// <param name="directoryStructure">Preserve directory structure.</param>
public ArchiveUpdateCallback(
Stream stream, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
{
Init(stream, compressor, updateData, directoryStructure);
}
/// <summary>
/// Initializes a new instance of the ArchiveUpdateCallback class
/// </summary>
/// <param name="stream">The input stream</param>
/// <param name="password">The archive password</param>
/// <param name="compressor">The owner of the callback</param>
/// <param name="updateData">The compression parameters.</param>
/// <param name="directoryStructure">Preserve directory structure.</param>
public ArchiveUpdateCallback(
Stream stream, string password, SevenZipCompressor compressor, UpdateData updateData,
bool directoryStructure)
: base(password)
{
Init(stream, compressor, updateData, directoryStructure);
}
/// <summary>
/// Initializes a new instance of the ArchiveUpdateCallback class
/// </summary>
/// <param name="streamDict">Dictionary&lt;file stream, name of the archive entry&gt;</param>
/// <param name="compressor">The owner of the callback</param>
/// <param name="updateData">The compression parameters.</param>
/// <param name="directoryStructure">Preserve directory structure.</param>
public ArchiveUpdateCallback(
Dictionary<string, Stream> streamDict,
SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
{
Init(streamDict, compressor, updateData, directoryStructure);
}
/// <summary>
/// Initializes a new instance of the ArchiveUpdateCallback class
/// </summary>
/// <param name="streamDict">Dictionary&lt;file stream, name of the archive entry&gt;</param>
/// <param name="password">The archive password</param>
/// <param name="compressor">The owner of the callback</param>
/// <param name="updateData">The compression parameters.</param>
/// <param name="directoryStructure">Preserve directory structure.</param>
public ArchiveUpdateCallback(
Dictionary<string, Stream> streamDict, string password,
SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
: base(password)
{
Init(streamDict, compressor, updateData, directoryStructure);
}
private void CommonInit(SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
{
_compressor = compressor;
_indexInArchive = updateData.FilesCount;
_indexOffset = updateData.Mode != InternalCompressionMode.Append ? 0 : _indexInArchive;
if (_compressor.ArchiveFormat == OutArchiveFormat.Zip)
{
_wrappersToDispose = new List<InStreamWrapper>();
}
_updateData = updateData;
_directoryStructure = directoryStructure;
DefaultItemName = "default";
}
private void Init(
FileInfo[] files, int rootLength, SevenZipCompressor compressor,
UpdateData updateData, bool directoryStructure)
{
_files = files;
_rootLength = rootLength;
if (files != null)
{
foreach (var fi in files)
{
if (fi.Exists)
{
_bytesCount += fi.Length;
if ((fi.Attributes & FileAttributes.Directory) == 0)
{
_actualFilesCount++;
}
}
}
}
CommonInit(compressor, updateData, directoryStructure);
}
private void Init(
Stream stream, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
{
_fileStream = new InStreamWrapper(stream, false);
_fileStream.BytesRead += IntEventArgsHandler;
_actualFilesCount = 1;
try
{
_bytesCount = stream.Length;
}
catch (NotSupportedException)
{
_bytesCount = -1;
}
try
{
stream.Seek(0, SeekOrigin.Begin);
}
catch (NotSupportedException)
{
_bytesCount = -1;
}
CommonInit(compressor, updateData, directoryStructure);
}
private void Init(
Dictionary<string, Stream> streamDict,
SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure)
{
_streams = new Stream[streamDict.Count];
streamDict.Values.CopyTo(_streams, 0);
_entries = new string[streamDict.Count];
streamDict.Keys.CopyTo(_entries, 0);
_actualFilesCount = streamDict.Count;
foreach (Stream str in _streams)
{
if (str != null)
{
_bytesCount += str.Length;
}
}
CommonInit(compressor, updateData, directoryStructure);
}
#endregion
/// <summary>
/// Gets or sets the dictionary size.
/// </summary>
public float DictionarySize
{
set
{
#if !WINCE
_memoryPressure = (int)(value * 1024 * 1024);
GC.AddMemoryPressure(_memoryPressure);
#endif
}
}
/// <summary>
/// Raises events for the GetStream method.
/// </summary>
/// <param name="index">The current item index.</param>
/// <returns>True if not cancelled; otherwise, false.</returns>
private bool EventsForGetStream(uint index)
{
if (!FastCompression)
{
if (_fileStream != null)
{
_fileStream.BytesRead += IntEventArgsHandler;
}
_doneRate += 1.0f / _actualFilesCount;
var fiea = new FileNameEventArgs(_files != null? _files[index].Name : _entries[index],
PercentDoneEventArgs.ProducePercentDone(_doneRate));
OnFileCompression(fiea);
if (fiea.Cancel)
{
Canceled = true;
return false;
}
}
return true;
}
#region Events
/// <summary>
/// Occurs when the next file is going to be packed.
/// </summary>
/// <remarks>Occurs when 7-zip engine requests for an input stream for the next file to pack it</remarks>
public event EventHandler<FileNameEventArgs> FileCompressionStarted;
/// <summary>
/// Occurs when data are being compressed.
/// </summary>
public event EventHandler<ProgressEventArgs> Compressing;
/// <summary>
/// Occurs when the current file was compressed.
/// </summary>
public event EventHandler FileCompressionFinished;
private void OnFileCompression(FileNameEventArgs e)
{
if (FileCompressionStarted != null)
{
FileCompressionStarted(this, e);
}
}
private void OnCompressing(ProgressEventArgs e)
{
if (Compressing != null)
{
Compressing(this, e);
}
}
private void OnFileCompressionFinished(EventArgs e)
{
if (FileCompressionFinished != null)
{
FileCompressionFinished(this, e);
}
}
#endregion
#region IArchiveUpdateCallback Members
public void SetTotal(ulong total) {}
public void SetCompleted(ref ulong completeValue) {}
public int GetUpdateItemInfo(uint index, ref int newData, ref int newProperties, ref uint indexInArchive)
{
switch (_updateData.Mode)
{
case InternalCompressionMode.Create:
newData = 1;
newProperties = 1;
indexInArchive = UInt32.MaxValue;
break;
case InternalCompressionMode.Append:
if (index < _indexInArchive)
{
newData = 0;
newProperties = 0;
indexInArchive = index;
}
else
{
newData = 1;
newProperties = 1;
indexInArchive = UInt32.MaxValue;
}
break;
case InternalCompressionMode.Modify:
newData = 0;
newProperties = Convert.ToInt32(_updateData.FileNamesToModify.ContainsKey((int)index)
&& _updateData.FileNamesToModify[(int)index] != null);
if (_updateData.FileNamesToModify.ContainsKey((int)index)
&& _updateData.FileNamesToModify[(int)index] == null)
{
indexInArchive = (UInt32)_updateData.ArchiveFileData.Count;
foreach (KeyValuePair<Int32, string> pairModification in _updateData.FileNamesToModify)
if ((pairModification.Key <= index) && (pairModification.Value == null))
{
do
{
indexInArchive--;
}
while ((indexInArchive > 0) && _updateData.FileNamesToModify.ContainsKey((Int32)indexInArchive)
&& (_updateData.FileNamesToModify[(Int32)indexInArchive] == null));
}
}
else
{
indexInArchive = index;
}
break;
}
return 0;
}
public int GetProperty(uint index, ItemPropId propID, ref PropVariant value)
{
index -= _indexOffset;
try
{
switch (propID)
{
case ItemPropId.IsAnti:
value.VarType = VarEnum.VT_BOOL;
value.UInt64Value = 0;
break;
case ItemPropId.Path:
#region Path
value.VarType = VarEnum.VT_BSTR;
string val = DefaultItemName;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
if (_files == null)
{
if (_entries != null)
{
val = _entries[index];
}
}
else
{
if (_directoryStructure)
{
if (_rootLength > 0)
{
val = _files[index].FullName.Substring(_rootLength);
}
else
{
val = _files[index].FullName[0] + _files[index].FullName.Substring(2);
}
}
else
{
val = _files[index].Name;
}
}
}
else
{
val = _updateData.FileNamesToModify[(int) index];
}
value.Value = Marshal.StringToBSTR(val);
#endregion
break;
case ItemPropId.IsDirectory:
value.VarType = VarEnum.VT_BOOL;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
if (_files == null)
{
if (_streams == null)
{
value.UInt64Value = 0;
}
else
{
value.UInt64Value = (ulong)(_streams[index] == null ? 1 : 0);
}
}
else
{
value.UInt64Value = (byte)(_files[index].Attributes & FileAttributes.Directory);
}
}
else
{
value.UInt64Value = Convert.ToUInt64(_updateData.ArchiveFileData[(int) index].IsDirectory);
}
break;
case ItemPropId.Size:
#region Size
value.VarType = VarEnum.VT_UI8;
UInt64 size;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
if (_files == null)
{
if (_streams == null)
{
size = _bytesCount > 0 ? (ulong) _bytesCount : 0;
}
else
{
size = (ulong) (_streams[index] == null? 0 : _streams[index].Length);
}
}
else
{
size = (_files[index].Attributes & FileAttributes.Directory) == 0
? (ulong) _files[index].Length
: 0;
}
}
else
{
size = _updateData.ArchiveFileData[(int) index].Size;
}
value.UInt64Value = size;
#endregion
break;
case ItemPropId.Attributes:
value.VarType = VarEnum.VT_UI4;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
if (_files == null)
{
if (_streams == null)
{
value.UInt32Value = (uint)FileAttributes.Normal;
}
else
{
value.UInt32Value = (uint)(_streams[index] == null ? FileAttributes.Directory : FileAttributes.Normal);
}
}
else
{
value.UInt32Value = (uint) _files[index].Attributes;
}
}
else
{
value.UInt32Value = _updateData.ArchiveFileData[(int) index].Attributes;
}
break;
#region Times
case ItemPropId.CreationTime:
value.VarType = VarEnum.VT_FILETIME;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
value.Int64Value = _files == null
? DateTime.Now.ToFileTime()
: _files[index].CreationTime.ToFileTime();
}
else
{
value.Int64Value = _updateData.ArchiveFileData[(int) index].CreationTime.ToFileTime();
}
break;
case ItemPropId.LastAccessTime:
value.VarType = VarEnum.VT_FILETIME;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
value.Int64Value = _files == null
? DateTime.Now.ToFileTime()
: _files[index].LastAccessTime.ToFileTime();
}
else
{
value.Int64Value = _updateData.ArchiveFileData[(int) index].LastAccessTime.ToFileTime();
}
break;
case ItemPropId.LastWriteTime:
value.VarType = VarEnum.VT_FILETIME;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
value.Int64Value = _files == null
? DateTime.Now.ToFileTime()
: _files[index].LastWriteTime.ToFileTime();
}
else
{
value.Int64Value = _updateData.ArchiveFileData[(int) index].LastWriteTime.ToFileTime();
}
break;
#endregion
case ItemPropId.Extension:
#region Extension
value.VarType = VarEnum.VT_BSTR;
if (_updateData.Mode != InternalCompressionMode.Modify)
{
try
{
val = _files != null
? _files[index].Extension.Substring(1)
: _entries == null
? ""
: Path.GetExtension(_entries[index]);
value.Value = Marshal.StringToBSTR(val);
}
catch (ArgumentException)
{
value.Value = Marshal.StringToBSTR("");
}
}
else
{
val = Path.GetExtension(_updateData.ArchiveFileData[(int) index].FileName);
value.Value = Marshal.StringToBSTR(val);
}
#endregion
break;
}
}
catch (Exception e)
{
AddException(e);
}
return 0;
}
/// <summary>
/// Gets the stream for 7-zip library.
/// </summary>
/// <param name="index">File index</param>
/// <param name="inStream">Input file stream</param>
/// <returns>Zero if Ok</returns>
public int GetStream(uint index, out
#if !MONO
ISequentialInStream
#else
HandleRef
#endif
inStream)
{
index -= _indexOffset;
if (_files != null)
{
_fileStream = null;
try
{
if (File.Exists(_files[index].FullName))
{
_fileStream = new InStreamWrapper(
new FileStream(_files[index].FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
true);
}
}
catch (Exception e)
{
AddException(e);
inStream = null;
return -1;
}
inStream = _fileStream;
if (!EventsForGetStream(index))
{
return -1;
}
}
else
{
if (_streams == null)
{
inStream = _fileStream;
}
else
{
_fileStream = new InStreamWrapper(_streams[index], true);
inStream = _fileStream;
if (!EventsForGetStream(index))
{
return -1;
}
}
}
return 0;
}
public long EnumProperties(IntPtr enumerator)
{
//Not implemented HRESULT
return 0x80004001L;
}
public void SetOperationResult(OperationResult operationResult)
{
if (operationResult != OperationResult.Ok && ReportErrors)
{
switch (operationResult)
{
case OperationResult.CrcError:
AddException(new ExtractionFailedException("File is corrupted. Crc check has failed."));
break;
case OperationResult.DataError:
AddException(new ExtractionFailedException("File is corrupted. Data error has occured."));
break;
case OperationResult.UnsupportedMethod:
AddException(new ExtractionFailedException("Unsupported method error has occured."));
break;
}
}
if (_fileStream != null)
{
_fileStream.BytesRead -= IntEventArgsHandler;
//Specific Zip implementation - can not Dispose files for Zip.
if (_compressor.ArchiveFormat != OutArchiveFormat.Zip)
{
try
{
_fileStream.Dispose();
}
catch (ObjectDisposedException) {}
}
else
{
_wrappersToDispose.Add(_fileStream);
}
_fileStream = null;
GC.Collect();
// Issue #6987
//GC.WaitForPendingFinalizers();
}
OnFileCompressionFinished(EventArgs.Empty);
}
#endregion
#region ICryptoGetTextPassword2 Members
public int CryptoGetTextPassword2(ref int passwordIsDefined, out string password)
{
passwordIsDefined = String.IsNullOrEmpty(Password) ? 0 : 1;
password = Password;
return 0;
}
#endregion
#region IDisposable Members
public void Dispose()
{
#if !WINCE
GC.RemoveMemoryPressure(_memoryPressure);
#endif
if (_fileStream != null)
{
try
{
_fileStream.Dispose();
}
catch (ObjectDisposedException) {}
}
if (_wrappersToDispose != null)
{
foreach (var wrapper in _wrappersToDispose)
{
try
{
wrapper.Dispose();
}
catch (ObjectDisposedException) {}
}
}
GC.SuppressFinalize(this);
}
#endregion
private void IntEventArgsHandler(object sender, IntEventArgs e)
{
lock (this)
{
var pold = (byte) ((_bytesWrittenOld*100)/_bytesCount);
_bytesWritten += e.Value;
byte pnow;
if (_bytesCount < _bytesWritten) //Holy shit, this check for ZIP is golden
{
pnow = 100;
}
else
{
pnow = (byte)((_bytesWritten * 100) / _bytesCount);
}
if (pnow > pold)
{
_bytesWrittenOld = _bytesWritten;
OnCompressing(new ProgressEventArgs(pnow, (byte) (pnow - pold)));
}
}
}
}
#endif
#endif
}