/// <summary> /// Provides a filter for decompressing an LZMA encoded <see cref="Stream"/>. /// </summary> /// <param name="stream">The underlying <see cref="Stream"/> providing the compressed data.</param> /// <param name="bufferSize">The maximum number of uncompressed bytes to buffer. 32k (the step size of <see cref="SevenZip"/>) is a sensible minimum.</param> /// <exception cref="IOException">The <paramref name="stream"/> doesn't start with a valid 5-bit LZMA header.</exception> /// <remarks> /// This method internally uses multi-threading and a <see cref="CircularBufferStream"/>. /// The <paramref name="stream"/> may be closed with a delay. /// </remarks> private static Stream GetDecompressionStream(Stream stream, int bufferSize = 128 * 1024) { var bufferStream = new CircularBufferStream(bufferSize); var decoder = new Decoder(); // Read LZMA header if (stream.CanSeek) stream.Position = 0; try { decoder.SetDecoderProperties(stream.Read(5)); } #region Error handling catch (IOException ex) { // Wrap exception to add context throw new IOException(Resources.ArchiveInvalid, ex); } catch (ApplicationException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(Resources.ArchiveInvalid, ex); } #endregion // Detmerine uncompressed length long uncompressedLength = 0; if (BitConverter.IsLittleEndian) { for (int i = 0; i < 8; i++) { int v = stream.ReadByte(); if (v < 0) throw new IOException(Resources.ArchiveInvalid); uncompressedLength |= ((long)(byte)v) << (8 * i); } } // If the uncompressed length is unknown, use original size * 1.5 as an estimate unchecked { bufferStream.SetLength(uncompressedLength == -1 ? stream.Length : (long)(uncompressedLength * 1.5)); } // Initialize the producer thread that will deliver uncompressed data var thread = new Thread(() => { try { decoder.Code(stream, bufferStream, stream.Length, uncompressedLength, null); } #region Error handling catch (ThreadAbortException) {} catch (ObjectDisposedException) { // If the buffer stream is closed too early the user probably just canceled the extraction process } catch (ApplicationException ex) { // Wrap exception since only certain exception types are allowed bufferStream.RelayErrorToReader(new IOException(ex.Message, ex)); } #endregion finally { bufferStream.DoneWriting(); } }) {IsBackground = true}; thread.Start(); return new DisposeWarpperStream(bufferStream, () => { // Stop producer thread when the buffer stream is closed thread.Abort(); thread.Join(); stream.Dispose(); }); }
/// <summary> /// Provides a filter for decompressing an LZMA encoded <see cref="Stream"/>. /// </summary> /// <param name="stream">The underlying <see cref="Stream"/> providing the compressed data.</param> /// <param name="bufferSize">The maximum number of uncompressed bytes to buffer. 32k (the step size of <see cref="SevenZip"/>) is a sensible minimum.</param> /// <exception cref="IOException">The <paramref name="stream"/> doesn't start with a valid 5-bit LZMA header.</exception> /// <remarks> /// This method internally uses multi-threading and a <see cref="CircularBufferStream"/>. /// The <paramref name="stream"/> may be closed with a delay. /// </remarks> internal static Stream GetDecompressionStream(Stream stream, int bufferSize = 128 * 1024) { var bufferStream = new CircularBufferStream(bufferSize); var decoder = new Decoder(); // Read LZMA header if (stream.CanSeek) stream.Position = 0; try { decoder.SetDecoderProperties(stream.Read(5)); } #region Error handling catch (IOException ex) { // Wrap exception to add context throw new IOException(Resources.ArchiveInvalid, ex); } catch (ApplicationException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(Resources.ArchiveInvalid, ex); } #endregion // Read "uncompressed length" header var uncompressedLengthData = stream.Read(8); if (!BitConverter.IsLittleEndian) Array.Reverse(uncompressedLengthData); long uncompressedLength = BitConverter.ToInt64(uncompressedLengthData, startIndex: 0); bufferStream.SetLength((uncompressedLength == UnknownSize) ? (long)(stream.Length * 1.5) : uncompressedLength); var producerThread = new Thread(() => { try { decoder.Code( inStream: stream, outStream: bufferStream, inSize: UnknownSize, outSize: uncompressedLength, progress: null); } catch (ThreadAbortException) {} catch (ObjectDisposedException) { // If the buffer stream is closed too early the user probably just canceled the extraction process } catch (ApplicationException ex) { bufferStream.RelayErrorToReader(new IOException(ex.Message, ex)); } finally { bufferStream.DoneWriting(); } }) {IsBackground = true}; producerThread.Start(); return new DisposeWarpperStream(bufferStream, disposeHandler: () => { producerThread.Abort(); producerThread.Join(); stream.Dispose(); }); }