Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/BizHawk.Client.Common/7z/ArchiveUpdateCallback.cs
2 views
/*  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;

#pragma warning disable

#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;
					//zero 11-oct-2014 - we want sequential streams only. length is unknown.
						//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

												//zero 11-oct-2014 - this doesnt matter, we think it's only used for updating the progress indicator
												value.VarType = VarEnum.VT_UI8;
												//value.UInt64Value = unchecked((ulong)-1);
												break;

                        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)
        {
					//zero 11-oct-2014 - first of all, its possible for _files to be null (if we're compressing streams)
					//second of all, if we're compressing streams, we cant possibly have _bytesCount anyway. so.. goodbye
						//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
}