/* 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.IO; using System.Runtime.InteropServices; #if MONO using SevenZip.Mono.COM; #endif namespace SevenZip { #if UNMANAGED #if COMPRESS /// /// Archive update callback to handle the process of packing files /// internal sealed class ArchiveUpdateCallback : CallbackBase, IArchiveUpdateCallback, ICryptoGetTextPassword2, IDisposable { #region Fields /// /// _files.Count if do not count directories /// private int _actualFilesCount; /// /// For Compressing event. /// private long _bytesCount; private long _bytesWritten; private long _bytesWrittenOld; private SevenZipCompressor _compressor; /// /// No directories. /// private bool _directoryStructure; /// /// Rate of the done work from [0, 1] /// private float _doneRate; /// /// The names of the archive entries /// private string[] _entries; /// /// Array of files to pack /// private FileInfo[] _files; private InStreamWrapper _fileStream; private uint _indexInArchive; private uint _indexOffset; /// /// Common root of file names length. /// private int _rootLength; /// /// Input streams to be compressed. /// private Stream[] _streams; private UpdateData _updateData; private List _wrappersToDispose; /// /// Gets or sets the default item name used in MemoryStream compression. /// public string DefaultItemName { private get; set; } /// /// Gets or sets the value indicating whether to compress as fast as possible, without calling events. /// public bool FastCompression { private get; set; } #if !WINCE private int _memoryPressure; #endif #endregion #region Constructors /// /// Initializes a new instance of the ArchiveUpdateCallback class /// /// Array of files to pack /// Common file names root length /// The owner of the callback /// The compression parameters. /// Preserve directory structure. public ArchiveUpdateCallback( FileInfo[] files, int rootLength, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure) { Init(files, rootLength, compressor, updateData, directoryStructure); } /// /// Initializes a new instance of the ArchiveUpdateCallback class /// /// Array of files to pack /// Common file names root length /// The archive password /// The owner of the callback /// The compression parameters. /// Preserve directory structure. public ArchiveUpdateCallback( FileInfo[] files, int rootLength, string password, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure) : base(password) { Init(files, rootLength, compressor, updateData, directoryStructure); } /// /// Initializes a new instance of the ArchiveUpdateCallback class /// /// The input stream /// The owner of the callback /// The compression parameters. /// Preserve directory structure. public ArchiveUpdateCallback( Stream stream, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure) { Init(stream, compressor, updateData, directoryStructure); } /// /// Initializes a new instance of the ArchiveUpdateCallback class /// /// The input stream /// The archive password /// The owner of the callback /// The compression parameters. /// Preserve directory structure. public ArchiveUpdateCallback( Stream stream, string password, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure) : base(password) { Init(stream, compressor, updateData, directoryStructure); } /// /// Initializes a new instance of the ArchiveUpdateCallback class /// /// Dictionary<file stream, name of the archive entry> /// The owner of the callback /// The compression parameters. /// Preserve directory structure. public ArchiveUpdateCallback( Dictionary streamDict, SevenZipCompressor compressor, UpdateData updateData, bool directoryStructure) { Init(streamDict, compressor, updateData, directoryStructure); } /// /// Initializes a new instance of the ArchiveUpdateCallback class /// /// Dictionary<file stream, name of the archive entry> /// The archive password /// The owner of the callback /// The compression parameters. /// Preserve directory structure. public ArchiveUpdateCallback( Dictionary 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(); } _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 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 /// /// Gets or sets the dictionary size. /// public float DictionarySize { set { #if !WINCE _memoryPressure = (int)(value * 1024 * 1024); GC.AddMemoryPressure(_memoryPressure); #endif } } /// /// Raises events for the GetStream method. /// /// The current item index. /// True if not cancelled; otherwise, false. 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 /// /// Occurs when the next file is going to be packed. /// /// Occurs when 7-zip engine requests for an input stream for the next file to pack it public event EventHandler FileCompressionStarted; /// /// Occurs when data are being compressed. /// public event EventHandler Compressing; /// /// Occurs when the current file was compressed. /// 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 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; } /// /// Gets the stream for 7-zip library. /// /// File index /// Input file stream /// Zero if Ok 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 (_files) { 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 }