Skip to content

Instantly share code, notes, and snippets.

@nbevans
Created November 17, 2014 05:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nbevans/db695ad5e1ad02126b2e to your computer and use it in GitHub Desktop.
Save nbevans/db695ad5e1ad02126b2e to your computer and use it in GitHub Desktop.
LzmaDecodeStream that actually works in a: streaming / chunked / blocked fashion.
/* 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.IO;
using SevenZip.Sdk;
using SevenZip.Sdk.Compression.Lzma;
namespace Singapore.Util.Lzma
{
/// <summary>
/// The stream which decompresses data with LZMA on the fly.
/// </summary>
public class LzmaDecodeStream : Stream
{
private readonly MemoryStream _buffer = new MemoryStream();
private readonly Decoder _decoder = new Decoder();
private readonly Stream _input;
private byte[] _commonProperties;
private bool _error;
private bool _firstChunkRead;
/// <summary>
/// Initializes a new instance of the LzmaDecodeStream class.
/// </summary>
/// <param name="encodedStream">A compressed stream.</param>
public LzmaDecodeStream(Stream encodedStream)
{
if (!encodedStream.CanRead)
{
throw new ArgumentException("The specified stream can not be read.", "encodedStream");
}
_input = encodedStream;
}
/// <summary>
/// Gets the chunk size.
/// </summary>
public int ChunkSize
{
get
{
return (int) _buffer.Length;
}
}
/// <summary>
/// Gets a value indicating whether the current stream supports reading.
/// </summary>
public override bool CanRead
{
get
{
return true;
}
}
/// <summary>
/// Gets a value indicating whether the current stream supports seeking.
/// </summary>
public override bool CanSeek
{
get
{
return false;
}
}
/// <summary>
/// Gets a value indicating whether the current stream supports writing.
/// </summary>
public override bool CanWrite
{
get
{
return false;
}
}
/// <summary>
/// Gets the length in bytes of the output stream.
/// </summary>
public override long Length
{
get
{
if (_input.CanSeek)
{
return _input.Length;
}
return _buffer.Length;
}
}
/// <summary>
/// Gets or sets the position within the output stream.
/// </summary>
public override long Position
{
get
{
if (_input.CanSeek)
{
return _input.Position;
}
return _buffer.Position;
}
set
{
throw new NotSupportedException();
}
}
private void ReadChunk()
{
long size;
byte[] properties;
try
{
properties = SevenZipExtractor.GetLzmaProperties(_input, out size);
}
catch (Exception)
{
_error = true;
return;
}
if (!_firstChunkRead)
{
_commonProperties = properties;
}
if (_commonProperties[0] != properties[0] ||
_commonProperties[1] != properties[1] ||
_commonProperties[2] != properties[2] ||
_commonProperties[3] != properties[3] ||
_commonProperties[4] != properties[4])
{
_error = true;
return;
}
_buffer.Capacity = 1024*32; // I MODIFIED THESE LINES FROM THE ORIGINAL. IT NOW USES A 32KB BUFFER FOR ALL CHUNK READS. RATHER THAN THE 'outSize' FOR ME WAS ALWAYS RETURNING -1 AND HENCE SETLENGTH WOULD THROW AN EXCEPTION. PAIR THIS STREAM WITH A SIMPLE COPYTO() OPERATION AND YOU HAVE A WORKING STREAMED EXTRACTOR.
_buffer.SetLength(_buffer.Capacity);
_decoder.SetDecoderProperties(properties);
_buffer.Position = 0;
_decoder.Code(_input, _buffer, 0, size, null);
_buffer.Position = 0;
}
/// <summary>
/// Does nothing.
/// </summary>
public override void Flush() {}
/// <summary>
/// Reads a sequence of bytes from the current stream and decompresses data if necessary.
/// </summary>
/// <param name="buffer">An array of bytes.</param>
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>The total number of bytes read into the buffer.</returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (_error)
{
return 0;
}
if (!_firstChunkRead)
{
ReadChunk();
_firstChunkRead = true;
}
int readCount = 0;
while (count > _buffer.Length - _buffer.Position && !_error)
{
var buf = new byte[_buffer.Length - _buffer.Position];
_buffer.Read(buf, 0, buf.Length);
buf.CopyTo(buffer, offset);
offset += buf.Length;
count -= buf.Length;
readCount += buf.Length;
ReadChunk();
}
if (!_error)
{
_buffer.Read(buffer, offset, count);
readCount += count;
}
return readCount;
}
/// <summary>
/// Sets the position within the current stream.
/// </summary>
/// <param name="offset">A byte offset relative to the origin parameter.</param>
/// <param name="origin">A value of type System.IO.SeekOrigin indicating the reference point used to obtain the new position.</param>
/// <returns>The new position within the current stream.</returns>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
/// <summary>
/// Sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <summary>
/// Writes a sequence of bytes to the current stream.
/// </summary>
/// <param name="buffer">An array of bytes.</param>
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment