123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- namespace SevenZip
- {
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.IO;
- #if UNMANAGED
- /// <summary>
- /// Archive extraction callback to handle the process of unpacking files
- /// </summary>
- internal sealed class ArchiveExtractCallback : CallbackBase, IArchiveExtractCallback, ICryptoGetTextPassword, IDisposable
- {
- private List<uint> _actualIndexes;
- private IInArchive _archive;
- /// <summary>
- /// For Compressing event.
- /// </summary>
- private long _bytesCount;
- private long _bytesWritten;
- private long _bytesWrittenOld;
- private string _directory;
- /// <summary>
- /// Rate of the done work from [0, 1].
- /// </summary>
- private float _doneRate;
- private SevenZipExtractor _extractor;
- private FakeOutStreamWrapper _fakeStream;
- private uint? _fileIndex;
- private int _filesCount;
- private OutStreamWrapper _fileStream;
- private bool _directoryStructure;
- private int _currentIndex;
- private const int MemoryPressure = 64 * 1024 * 1024; //64mb seems to be the maximum value
- #region Constructors
- /// <summary>
- /// Initializes a new instance of the ArchiveExtractCallback class
- /// </summary>
- /// <param name="archive">IInArchive interface for the archive</param>
- /// <param name="directory">Directory where files are to be unpacked to</param>
- /// <param name="filesCount">The archive files count</param>'
- /// <param name="extractor">The owner of the callback</param>
- /// <param name="actualIndexes">The list of actual indexes (solid archives support)</param>
- /// <param name="directoryStructure">The value indicating whether to preserve directory structure of extracted files.</param>
- public ArchiveExtractCallback(IInArchive archive, string directory, int filesCount, bool directoryStructure,
- List<uint> actualIndexes, SevenZipExtractor extractor)
- {
- Init(archive, directory, filesCount, directoryStructure, actualIndexes, extractor);
- }
- /// <summary>
- /// Initializes a new instance of the ArchiveExtractCallback class
- /// </summary>
- /// <param name="archive">IInArchive interface for the archive</param>
- /// <param name="directory">Directory where files are to be unpacked to</param>
- /// <param name="filesCount">The archive files count</param>
- /// <param name="password">Password for the archive</param>
- /// <param name="extractor">The owner of the callback</param>
- /// <param name="actualIndexes">The list of actual indexes (solid archives support)</param>
- /// <param name="directoryStructure">The value indicating whether to preserve directory structure of extracted files.</param>
- public ArchiveExtractCallback(IInArchive archive, string directory, int filesCount, bool directoryStructure,
- List<uint> actualIndexes, string password, SevenZipExtractor extractor)
- : base(password)
- {
- Init(archive, directory, filesCount, directoryStructure, actualIndexes, extractor);
- }
- /// <summary>
- /// Initializes a new instance of the ArchiveExtractCallback class
- /// </summary>
- /// <param name="archive">IInArchive interface for the archive</param>
- /// <param name="stream">The stream where files are to be unpacked to</param>
- /// <param name="filesCount">The archive files count</param>
- /// <param name="fileIndex">The file index for the stream</param>
- /// <param name="extractor">The owner of the callback</param>
- public ArchiveExtractCallback(IInArchive archive, Stream stream, int filesCount, uint fileIndex, SevenZipExtractor extractor)
- {
- Init(archive, stream, filesCount, fileIndex, extractor);
- }
- /// <summary>
- /// Initializes a new instance of the ArchiveExtractCallback class
- /// </summary>
- /// <param name="archive">IInArchive interface for the archive</param>
- /// <param name="stream">The stream where files are to be unpacked to</param>
- /// <param name="filesCount">The archive files count</param>
- /// <param name="fileIndex">The file index for the stream</param>
- /// <param name="password">Password for the archive</param>
- /// <param name="extractor">The owner of the callback</param>
- public ArchiveExtractCallback(IInArchive archive, Stream stream, int filesCount, uint fileIndex, string password, SevenZipExtractor extractor)
- : base(password)
- {
- Init(archive, stream, filesCount, fileIndex, extractor);
- }
- private void Init(IInArchive archive, string directory, int filesCount, bool directoryStructure, List<uint> actualIndexes, SevenZipExtractor extractor)
- {
- CommonInit(archive, filesCount, extractor);
- _directory = directory;
- _actualIndexes = actualIndexes;
- _directoryStructure = directoryStructure;
- if (!directory.EndsWith("" + Path.DirectorySeparatorChar, StringComparison.CurrentCulture))
- {
- _directory += Path.DirectorySeparatorChar;
- }
- }
- private void Init(IInArchive archive, Stream stream, int filesCount, uint fileIndex, SevenZipExtractor extractor)
- {
- CommonInit(archive, filesCount, extractor);
- _fileStream = new OutStreamWrapper(stream, false);
- _fileStream.BytesWritten += IntEventArgsHandler;
- _fileIndex = fileIndex;
- }
- private void CommonInit(IInArchive archive, int filesCount, SevenZipExtractor extractor)
- {
- _archive = archive;
- _filesCount = filesCount;
- _fakeStream = new FakeOutStreamWrapper();
- _fakeStream.BytesWritten += IntEventArgsHandler;
- _extractor = extractor;
- GC.AddMemoryPressure(MemoryPressure);
- }
- #endregion
- /// <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 is opened and 7-zip sends the size of unpacked data
- /// </summary>
- public event EventHandler<OpenEventArgs> Open;
- /// <summary>
- /// Occurs when the extraction is performed
- /// </summary>
- public event EventHandler<ProgressEventArgs> Extracting;
- /// <summary>
- /// Occurs during the extraction when a file already exists
- /// </summary>
- public event EventHandler<FileOverwriteEventArgs> FileExists;
- private void IntEventArgsHandler(object sender, IntEventArgs e)
- {
- // If _bytesCount is not set, we can't update the progress.
- if (_bytesCount == 0)
- {
- return;
- }
- var pold = (int)(_bytesWrittenOld * 100 / _bytesCount);
- _bytesWritten += e.Value;
- var pnow = (int)(_bytesWritten * 100 / _bytesCount);
- if (pnow > pold)
- {
- if (pnow > 100)
- {
- pold = pnow = 0;
- }
- _bytesWrittenOld = _bytesWritten;
- Extracting?.Invoke(this, new ProgressEventArgs((byte)pnow, (byte)(pnow - pold)));
- }
- }
- #region IArchiveExtractCallback Members
- /// <summary>
- /// Gives the size of the unpacked archive files
- /// </summary>
- /// <param name="total">Size of the unpacked archive files (in bytes)</param>
- public void SetTotal(ulong total)
- {
- _bytesCount = (long)total;
- Open?.Invoke(this, new OpenEventArgs(total));
- }
- public void SetCompleted(ref ulong completeValue) { }
- /// <summary>
- /// Sets output stream for writing unpacked data
- /// </summary>
- /// <param name="index">Current file index</param>
- /// <param name="outStream">Output stream pointer</param>
- /// <param name="askExtractMode">Extraction mode</param>
- /// <returns>0 if OK</returns>
- public int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode)
- {
- outStream = null;
- if (Canceled)
- {
- return -1;
- }
- _currentIndex = (int)index;
- if (askExtractMode == AskMode.Extract)
- {
- var fileName = _directory;
- if (!_fileIndex.HasValue)
- {
- // Extraction to a file
- if (_actualIndexes == null || _actualIndexes.Contains(index))
- {
- var data = new PropVariant();
- _archive.GetProperty(index, ItemPropId.Path, ref data);
- var entryName = NativeMethods.SafeCast(data, "");
- #region Get entryName
- if (string.IsNullOrEmpty(entryName))
- {
- if (_filesCount == 1)
- {
- var archName = Path.GetFileName(_extractor.FileName);
- archName = archName.Substring(0,
- archName.LastIndexOf('.'));
- if (!archName.EndsWith(".tar",
- StringComparison.OrdinalIgnoreCase))
- {
- archName += ".tar";
- }
- entryName = archName;
- }
- else
- {
- entryName = "[no name] " + index.ToString(CultureInfo.InvariantCulture);
- }
- }
- #endregion
- try
- {
- fileName = Path.Combine(RemoveIllegalCharacters(_directory, true), RemoveIllegalCharacters(_directoryStructure ? entryName : Path.GetFileName(entryName)));
-
- if (string.IsNullOrEmpty(fileName))
- {
- throw new SevenZipArchiveException("Some archive name is null or empty.");
- }
- }
- catch (Exception e)
- {
- AddException(e);
- outStream = _fakeStream;
- return 0;
- }
- _archive.GetProperty(index, ItemPropId.IsDirectory, ref data);
- if (!NativeMethods.SafeCast(data, false))
- {
- _archive.GetProperty(index, ItemPropId.LastWriteTime, ref data);
- var time = NativeMethods.SafeCast(data, DateTime.MinValue);
-
- if (File.Exists(fileName))
- {
- var fnea = new FileOverwriteEventArgs(fileName);
- FileExists?.Invoke(this, fnea);
-
- if (fnea.Cancel)
- {
- Canceled = true;
- return -1;
- }
- if (string.IsNullOrEmpty(fnea.FileName))
- {
- outStream = _fakeStream;
- }
- else
- {
- fileName = fnea.FileName;
- }
- }
- _doneRate += 1.0f / _filesCount;
- var iea = new FileInfoEventArgs(_extractor.ArchiveFileData[(int) index], PercentDoneEventArgs.ProducePercentDone(_doneRate));
- FileExtractionStarted?.Invoke(this, iea);
-
- if (iea.Cancel)
- {
- Canceled = true;
- return -1;
- }
- if (iea.Skip)
- {
- outStream = _fakeStream;
- return 0;
- }
- CreateDirectory(fileName);
- try
- {
- _fileStream = new OutStreamWrapper(File.Create(fileName), fileName, time, true);
- }
- catch (Exception e)
- {
- AddException(e is FileNotFoundException
- ? new IOException($"The file \"{fileName}\" was not extracted due to the File.Create fail.")
- : e);
- outStream = _fakeStream;
- return 0;
- }
- _fileStream.BytesWritten += IntEventArgsHandler;
- outStream = _fileStream;
- }
- else
- {
- _doneRate += 1.0f / _filesCount;
- var iea = new FileInfoEventArgs(_extractor.ArchiveFileData[(int)index], PercentDoneEventArgs.ProducePercentDone(_doneRate));
- FileExtractionStarted?.Invoke(this, iea);
-
- if (iea.Cancel)
- {
- Canceled = true;
- return -1;
- }
- if (iea.Skip)
- {
- outStream = _fakeStream;
- return 0;
- }
- if (!Directory.Exists(fileName))
- {
- try
- {
- Directory.CreateDirectory(fileName);
- }
- catch (Exception e)
- {
- AddException(e);
- }
- outStream = _fakeStream;
- }
- }
- }
- else
- {
- outStream = _fakeStream;
- }
- }
- else
- {
- // Extraction to a stream.
- if (index == _fileIndex)
- {
- outStream = _fileStream;
- _fileIndex = null;
- }
- else
- {
- outStream = _fakeStream;
- }
- }
- }
- return 0;
- }
- /// <inheritdoc />
- public void PrepareOperation(AskMode askExtractMode) { }
- /// <inheritdoc />
- 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;
- case OperationResult.Unavailable:
- AddException(new ExtractionFailedException("File is unavailable."));
- break;
- case OperationResult.UnexpectedEnd:
- AddException(new ExtractionFailedException("Unexpected end of file."));
- break;
- case OperationResult.DataAfterEnd:
- AddException(new ExtractionFailedException("Data after end of archive."));
- break;
- case OperationResult.IsNotArc:
- AddException(new ExtractionFailedException("File is not archive."));
- break;
- case OperationResult.HeadersError:
- AddException(new ExtractionFailedException("Archive headers error."));
- break;
- case OperationResult.WrongPassword:
- AddException(new ExtractionFailedException("Wrong password."));
- break;
- default:
- AddException(new ExtractionFailedException($"Unexpected operation result: {operationResult}"));
- break;
- }
- }
- else
- {
- if (_fileStream != null && !_fileIndex.HasValue)
- {
- try
- {
- _fileStream.BytesWritten -= IntEventArgsHandler;
- _fileStream.Dispose();
- }
- catch (ObjectDisposedException) { }
- _fileStream = null;
- }
- var iea = new FileInfoEventArgs(_extractor.ArchiveFileData[_currentIndex], PercentDoneEventArgs.ProducePercentDone(_doneRate));
- FileExtractionFinished?.Invoke(this, iea);
-
- if (iea.Cancel)
- {
- Canceled = true;
- }
- }
- }
- #endregion
- /// <inheritdoc />
- public int CryptoGetTextPassword(out string password)
- {
- password = Password;
- return 0;
- }
- /// <inheritdoc />
- public void Dispose()
- {
- GC.RemoveMemoryPressure(MemoryPressure);
- if (_fileStream != null)
- {
- try
- {
- _fileStream.Dispose();
- }
- catch (ObjectDisposedException) { }
- _fileStream = null;
- }
- if (_fakeStream != null)
- {
- try
- {
- _fakeStream.Dispose();
- }
- catch (ObjectDisposedException) { }
- _fakeStream = null;
- }
- }
- /// <summary>
- /// Ensures that the directory to the file name is valid and creates intermediate directories if necessary
- /// </summary>
- /// <param name="fileName">File name</param>
- private static void CreateDirectory(string fileName)
- {
- var destinationDirectory = Path.GetDirectoryName(fileName);
- if (!string.IsNullOrEmpty(destinationDirectory))
- {
- Directory.CreateDirectory(destinationDirectory);
- }
- }
- /// <summary>
- /// removes the invalid character in file path.
- /// </summary>
- /// <param name="str"></param>
- /// <param name="isDirectory"></param>
- /// <returns></returns>
- private static string RemoveIllegalCharacters(string str, bool isDirectory = false)
- {
- var splitFileName = new List<string>(str.Split(Path.DirectorySeparatorChar));
- foreach (var chr in Path.GetInvalidFileNameChars())
- {
- for (var i = 0; i < splitFileName.Count; i++)
- {
- if (isDirectory && chr == ':' && i == 0)
- {
- continue;
- }
- if (string.IsNullOrEmpty(splitFileName[i]))
- {
- continue;
- }
- while (splitFileName[i].IndexOf(chr) > -1)
- {
- splitFileName[i] = splitFileName[i].Replace(chr, '_');
- }
- }
- }
- if (str.StartsWith(new string(Path.DirectorySeparatorChar, 2), StringComparison.CurrentCultureIgnoreCase))
- {
- splitFileName.RemoveAt(0);
- splitFileName.RemoveAt(0);
- splitFileName[0] = new string(Path.DirectorySeparatorChar, 2) + splitFileName[0];
- }
- return string.Join(new string(Path.DirectorySeparatorChar, 1), splitFileName.ToArray());
- }
- }
- #endif
- }
|