Files
sharpcompress/SharpCompress/Compressor/LZMA/LzmaStream.cs
Adam Hathcock 0ab103e1b1 Made streams Dispose resilient
Made streams resilient to dispose being called multiple times.  Removed
unused stream.
2013-05-28 19:29:54 +01:00

308 lines
9.6 KiB
C#

using System;
using System.IO;
using SharpCompress.Compressor.LZMA.LZ;
namespace SharpCompress.Compressor.LZMA
{
public class LzmaStream : Stream
{
private Stream inputStream;
private long inputSize;
private long outputSize;
private int dictionarySize;
private OutWindow outWindow = new OutWindow();
private RangeCoder.Decoder rangeDecoder = new RangeCoder.Decoder();
private Decoder decoder;
private long position = 0;
private bool endReached = false;
private long availableBytes;
private long rangeDecoderLimit;
private long inputPosition = 0;
// LZMA2
private bool isLZMA2;
private bool uncompressedChunk = false;
private bool needDictReset = true;
private bool needProps = true;
private byte[] props = new byte[5];
private Encoder encoder;
private bool isDisposed;
public LzmaStream(byte[] properties, Stream inputStream)
: this(properties, inputStream, -1, -1, null, properties.Length < 5)
{
}
public LzmaStream(byte[] properties, Stream inputStream, long inputSize)
: this(properties, inputStream, inputSize, -1, null, properties.Length < 5)
{
}
public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize)
: this(properties, inputStream, inputSize, outputSize, null, properties.Length < 5)
{
}
public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize,
Stream presetDictionary, bool isLZMA2)
{
this.inputStream = inputStream;
this.inputSize = inputSize;
this.outputSize = outputSize;
this.isLZMA2 = isLZMA2;
if (!isLZMA2)
{
dictionarySize = BitConverter.ToInt32(properties, 1);
outWindow.Create(dictionarySize);
if (presetDictionary != null)
outWindow.Train(presetDictionary);
rangeDecoder.Init(inputStream);
decoder = new Decoder();
decoder.SetDecoderProperties(properties);
props = properties;
availableBytes = outputSize < 0 ? long.MaxValue : outputSize;
rangeDecoderLimit = inputSize;
}
else
{
dictionarySize = 2 | (properties[0] & 1);
dictionarySize <<= (properties[0] >> 1) + 11;
outWindow.Create(dictionarySize);
if (presetDictionary != null)
{
outWindow.Train(presetDictionary);
needDictReset = false;
}
props = new byte[1];
availableBytes = 0;
}
}
public LzmaStream(LzmaEncoderProperties properties, bool isLZMA2, Stream outputStream)
: this(properties, isLZMA2, null, outputStream)
{
}
public LzmaStream(LzmaEncoderProperties properties, bool isLZMA2, Stream presetDictionary, Stream outputStream)
{
this.isLZMA2 = isLZMA2;
availableBytes = 0;
endReached = true;
if (isLZMA2)
throw new NotImplementedException();
encoder = new Encoder();
encoder.SetCoderProperties(properties.propIDs, properties.properties);
MemoryStream propStream = new MemoryStream(5);
encoder.WriteCoderProperties(propStream);
props = propStream.ToArray();
encoder.SetStreams(null, outputStream, -1, -1);
if (presetDictionary != null)
encoder.Train(presetDictionary);
}
public override bool CanRead
{
get { return encoder == null; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return encoder != null; }
}
public override void Flush()
{
}
protected override void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
isDisposed = true;
if (disposing)
{
if (encoder != null)
{
position = encoder.Code(null, true);
}
if (inputStream != null)
{
inputStream.Dispose();
}
}
base.Dispose(disposing);
}
public override long Length
{
get { return position + availableBytes; }
}
public override long Position
{
get { return position; }
set { throw new NotImplementedException(); }
}
public override int Read(byte[] buffer, int offset, int count)
{
if (endReached)
return 0;
int total = 0;
while (total < count)
{
if (availableBytes == 0)
{
if (isLZMA2)
decodeChunkHeader();
else
endReached = true;
if (endReached)
break;
}
int toProcess = count - total;
if (toProcess > availableBytes)
toProcess = (int)availableBytes;
outWindow.SetLimit(toProcess);
if (uncompressedChunk)
{
inputPosition += outWindow.CopyStream(inputStream, toProcess);
}
else if (decoder.Code(dictionarySize, outWindow, rangeDecoder)
&& outputSize < 0)
{
availableBytes = outWindow.AvailableBytes;
}
int read = outWindow.Read(buffer, offset, toProcess);
total += read;
offset += read;
position += read;
availableBytes -= read;
if (availableBytes == 0 && !uncompressedChunk)
{
rangeDecoder.ReleaseStream();
if (!rangeDecoder.IsFinished || (rangeDecoderLimit >= 0 && rangeDecoder.Total != rangeDecoderLimit))
throw new DataErrorException();
inputPosition += rangeDecoder.Total;
if (outWindow.HasPending)
throw new DataErrorException();
}
}
if (endReached)
{
if (inputSize >= 0 && inputPosition != inputSize)
throw new DataErrorException();
if (outputSize >= 0 && position != outputSize)
throw new DataErrorException();
}
return total;
}
private void decodeChunkHeader()
{
int control = inputStream.ReadByte();
inputPosition++;
if (control == 0x00)
{
endReached = true;
return;
}
if (control >= 0xE0 || control == 0x01)
{
needProps = true;
needDictReset = false;
outWindow.Reset();
}
else if (needDictReset)
throw new DataErrorException();
if (control >= 0x80)
{
uncompressedChunk = false;
availableBytes = (control & 0x1F) << 16;
availableBytes += (inputStream.ReadByte() << 8) + inputStream.ReadByte() + 1;
inputPosition += 2;
rangeDecoderLimit = (inputStream.ReadByte() << 8) + inputStream.ReadByte() + 1;
inputPosition += 2;
if (control >= 0xC0)
{
needProps = false;
props[0] = (byte)inputStream.ReadByte();
inputPosition++;
decoder = new Decoder();
decoder.SetDecoderProperties(props);
}
else if (needProps)
throw new DataErrorException();
else if (control >= 0xA0)
{
decoder = new Decoder();
decoder.SetDecoderProperties(props);
}
rangeDecoder.Init(inputStream);
}
else if (control > 0x02)
throw new DataErrorException();
else
{
uncompressedChunk = true;
availableBytes = (inputStream.ReadByte() << 8) + inputStream.ReadByte() + 1;
inputPosition += 2;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (encoder != null)
position = encoder.Code(new MemoryStream(buffer, offset, count), false);
}
public byte[] Properties
{
get { return props; }
}
}
}