/// <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();
});
}