// QuickLZDecompressionStream
// Public domain by Pascal Craponne (picrap@gmail.com). No copyright pending
// Released 5-May-2011: Fixed a missing Dispose call on _sourceStream
namespace QuickLZ
{
using System;
using System.IO;
using QuickLZSharp;
///
/// Stream to decompress a given input stream
///
public class QuickLZDecompressionStream : Stream
{
#region overrides without any interested
///
/// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
///
///
/// An I/O error occurs.
///
public override void Flush()
{
}
///
/// Sets the position within the current stream.
///
/// A byte offset relative to the parameter.
/// A value of type indicating the reference point used to obtain the new position.
///
/// The new position within the current stream.
///
///
/// An I/O error occurs.
///
///
/// The stream does not support seeking, such as if the stream is constructed from a pipe or console output.
///
///
/// Methods were called after the stream was closed.
///
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
///
/// Sets the length of the current stream.
///
/// The desired length of the current stream in bytes.
///
/// An I/O error occurs.
///
///
/// The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output.
///
///
/// Methods were called after the stream was closed.
///
public override void SetLength(long value)
{
throw new NotSupportedException();
}
///
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
///
/// An array of bytes. This method copies bytes from to the current stream.
/// The zero-based byte offset in at which to begin copying bytes to the current stream.
/// The number of bytes to be written to the current stream.
///
/// The sum of and is greater than the buffer length.
///
///
/// is null.
///
///
/// or is negative.
///
///
/// An I/O error occurs.
///
///
/// The stream does not support writing.
///
///
/// Methods were called after the stream was closed.
///
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
///
/// Gets a value indicating whether the current stream supports reading.
///
///
/// true if the stream supports reading; otherwise, false.
///
public override bool CanRead
{
get { return true; }
}
///
/// Gets a value indicating whether the current stream supports seeking.
///
///
/// true if the stream supports seeking; otherwise, false.
///
public override bool CanSeek
{
get { return false; }
}
///
/// Gets a value indicating whether the current stream supports writing.
///
///
/// true if the stream supports writing; otherwise, false.
///
public override bool CanWrite
{
get { return false; }
}
///
/// Gets the length in bytes of the stream.
///
///
///
/// A long value representing the length of the stream in bytes.
///
///
/// A class derived from Stream does not support seeking.
///
///
/// Methods were called after the stream was closed.
///
public override long Length
{
get { throw new NotSupportedException(); }
}
///
/// Gets or sets the position within the current stream.
///
///
///
/// The current position within the stream.
///
///
/// An I/O error occurs.
///
///
/// The stream does not support seeking.
///
///
/// Methods were called after the stream was closed.
///
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
#endregion
///
/// QuickLZ engine
///
private readonly QuickLZ _quickLZ;
///
/// Stream we're reading from
///
private readonly Stream _sourceStream;
///
/// Temporary buffer where raw data is stored.
/// Kept to be reused from one buffer fill to another
///
private byte[] _readBuffer;
///
/// Unpacked buffer
///
private byte[] _unpackedBuffer;
///
/// Read position un unpacked buffer
///
private int _unpackedOffset;
///
/// Length for unpacked data
///
private int _unpackedLength;
///
/// Room for header
///
private readonly byte[] _header = new byte[9];
///
/// Allocates the specified length.
///
/// The length.
///
protected virtual byte[] Alloc(int length)
{
return new byte[length];
}
///
/// Frees the specified data.
///
/// The data.
protected virtual void Free(byte[] data)
{
}
///
/// Ensures the unpacked buffer has enough space.
///
/// The packed buffer.
private void EnsureUnpackedBuffer(byte[] packedBuffer)
{
int unpackedLength = _quickLZ.sizeDecompressed(packedBuffer);
if (_unpackedBuffer == null || _unpackedBuffer.Length < unpackedLength)
{
if (_unpackedBuffer != null)
Free(_unpackedBuffer);
_unpackedBuffer = Alloc(unpackedLength);
}
}
///
/// Fills or refills the read buffer.
///
private void Fill()
{
int headerLength = _sourceStream.Read(_header, 0, _header.Length);
// the normal end is here
if (headerLength == 0)
{
_unpackedBuffer = null;
return;
}
if (headerLength != _header.Length)
throw new InvalidDataException("QuickLZ input buffer corrupted (header)");
int sizeCompressed = _quickLZ.sizeCompressed(_header);
if (_readBuffer == null || _readBuffer.Length < sizeCompressed)
{
if (_readBuffer != null)
Free(_readBuffer);
_readBuffer = Alloc(sizeCompressed);
}
Buffer.BlockCopy(_header, 0, _readBuffer, 0, _header.Length);
int bodyLength = _sourceStream.Read(_readBuffer, _header.Length, sizeCompressed - _header.Length);
if (bodyLength != sizeCompressed - _header.Length)
throw new InvalidDataException("QuickLZ input buffer corrupted (body)");
EnsureUnpackedBuffer(_readBuffer);
_unpackedLength = _quickLZ.decompress(_readBuffer, _unpackedBuffer);
_unpackedOffset = 0;
}
///
/// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
///
/// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source.
/// The zero-based byte offset in at which to begin storing the data read from the current stream.
/// The maximum number of bytes to be read from the current stream.
///
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
///
///
/// The sum of and is larger than the buffer length.
///
///
/// is null.
///
///
/// or is negative.
///
///
/// An I/O error occurs.
///
///
/// The stream does not support reading.
///
///
/// Methods were called after the stream was closed.
///
public override int Read(byte[] buffer, int offset, int count)
{
if (_unpackedBuffer == null || _unpackedOffset == _unpackedLength)
Fill();
// to do: something smarter than the double test
if (_unpackedBuffer == null)
return 0;
// 1. If we don't have enough data available, then split
if (_unpackedOffset + count > _unpackedLength)
{
int available = _unpackedLength - _unpackedOffset;
// this is the part we're sure to get
int r1 = Read(buffer, offset, available);
// this is the part we're not
int r2 = Read(buffer, offset + available, count - available);
return r1 + r2;
}
// 2. we have enough buffer, use it
Buffer.BlockCopy(_unpackedBuffer, _unpackedOffset, buffer, offset, count);
_unpackedOffset += count;
return count;
}
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
if (disposing)
Pool.Default.Free(_quickLZ);
base.Dispose(disposing);
_sourceStream.Dispose();
}
///
/// Initializes a new instance of the class.
///
/// The source stream.
public QuickLZDecompressionStream(Stream sourceStream)
{
_quickLZ = Pool.Default.Alloc();
_sourceStream = sourceStream;
Fill();
}
}
}