ReduceStream is async

This commit is contained in:
Adam Hathcock
2026-01-31 13:19:10 +00:00
parent b40e1a002a
commit 0767292bb0
5 changed files with 248 additions and 12 deletions

View File

@@ -148,19 +148,43 @@ internal abstract partial class ZipFilePart
}
case ZipCompressionMethod.Reduce1:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 1);
return await ReduceStream.CreateAsync(
stream,
Header.CompressedSize,
Header.UncompressedSize,
1,
cancellationToken
);
}
case ZipCompressionMethod.Reduce2:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 2);
return await ReduceStream.CreateAsync(
stream,
Header.CompressedSize,
Header.UncompressedSize,
2,
cancellationToken
);
}
case ZipCompressionMethod.Reduce3:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 3);
return await ReduceStream.CreateAsync(
stream,
Header.CompressedSize,
Header.UncompressedSize,
3,
cancellationToken
);
}
case ZipCompressionMethod.Reduce4:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 4);
return await ReduceStream.CreateAsync(
stream,
Header.CompressedSize,
Header.UncompressedSize,
4,
cancellationToken
);
}
case ZipCompressionMethod.Explode:
{

View File

@@ -88,19 +88,19 @@ internal abstract partial class ZipFilePart : FilePart
}
case ZipCompressionMethod.Reduce1:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 1);
return ReduceStream.Create(stream, Header.CompressedSize, Header.UncompressedSize, 1);
}
case ZipCompressionMethod.Reduce2:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 2);
return ReduceStream.Create(stream, Header.CompressedSize, Header.UncompressedSize, 2);
}
case ZipCompressionMethod.Reduce3:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 3);
return ReduceStream.Create(stream, Header.CompressedSize, Header.UncompressedSize, 3);
}
case ZipCompressionMethod.Reduce4:
{
return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 4);
return ReduceStream.Create(stream, Header.CompressedSize, Header.UncompressedSize, 4);
}
case ZipCompressionMethod.Explode:
{

View File

@@ -0,0 +1,206 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Reduce;
public partial class ReduceStream
{
public static async ValueTask<ReduceStream> CreateAsync(
Stream inStr,
long compsize,
long unCompSize,
int factor,
CancellationToken cancellationToken = default
)
{
var stream = new ReduceStream(inStr, compsize, unCompSize, factor);
await stream.LoadNextByteTableAsync(cancellationToken).ConfigureAwait(false);
return stream;
}
private async Task<int> NEXTBYTEAsync(CancellationToken cancellationToken)
{
if (inByteCount == compressedSize)
{
return EOF;
}
byte[] buffer = new byte[1];
int bytesRead = await inStream
.ReadAsync(buffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
if (bytesRead == 0)
{
return EOF;
}
inByteCount++;
return buffer[0];
}
private async Task<byte> READBITSAsync(int nbits, CancellationToken cancellationToken)
{
if (nbits > bitBufferCount)
{
int temp;
while (bitBufferCount <= 8 * (int)(4 - 1))
{
temp = await NEXTBYTEAsync(cancellationToken).ConfigureAwait(false);
if (temp == EOF)
{
break;
}
bitBuffer |= (ulong)temp << bitBufferCount;
bitBufferCount += 8;
}
}
byte zdest = (byte)(bitBuffer & (ulong)mask_bits[nbits]);
bitBuffer >>= nbits;
bitBufferCount -= nbits;
return zdest;
}
private async Task LoadNextByteTableAsync(CancellationToken cancellationToken)
{
nextByteTable = new byte[256][];
for (int x = 255; x >= 0; x--)
{
byte Slen = await READBITSAsync(6, cancellationToken).ConfigureAwait(false);
nextByteTable[x] = new byte[Slen];
for (int i = 0; i < Slen; i++)
{
nextByteTable[x][i] = await READBITSAsync(8, cancellationToken)
.ConfigureAwait(false);
}
}
}
private async Task<byte> GetNextByteAsync(CancellationToken cancellationToken)
{
if (nextByteTable[outByte].Length == 0)
{
outByte = await READBITSAsync(8, cancellationToken).ConfigureAwait(false);
return outByte;
}
byte nextBit = await READBITSAsync(1, cancellationToken).ConfigureAwait(false);
if (nextBit == 1)
{
outByte = await READBITSAsync(8, cancellationToken).ConfigureAwait(false);
return outByte;
}
byte nextByteIndex = await READBITSAsync(
bitCountTable[nextByteTable[outByte].Length],
cancellationToken
)
.ConfigureAwait(false);
outByte = nextByteTable[outByte][nextByteIndex];
return outByte;
}
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
int countIndex = 0;
while (countIndex < count && outBytesCount < unCompressedSize)
{
if (length == 0)
{
byte nextByte = await GetNextByteAsync(cancellationToken).ConfigureAwait(false);
if (nextByte != RunLengthCode)
{
buffer[offset + (countIndex++)] = nextByte;
windowsBuffer[windowIndex++] = nextByte;
outBytesCount++;
if (windowIndex == WSIZE)
{
windowIndex = 0;
}
continue;
}
nextByte = await GetNextByteAsync(cancellationToken).ConfigureAwait(false);
if (nextByte == 0)
{
buffer[offset + (countIndex++)] = RunLengthCode;
windowsBuffer[windowIndex++] = RunLengthCode;
outBytesCount++;
if (windowIndex == WSIZE)
{
windowIndex = 0;
}
continue;
}
int lengthDistanceByte = nextByte;
length = lengthDistanceByte & lengthMask;
if (length == lengthMask)
{
length += await GetNextByteAsync(cancellationToken).ConfigureAwait(false);
}
length += 3;
int distanceHighByte = (lengthDistanceByte << factor) & distanceMask;
distance =
windowIndex
- (
distanceHighByte
+ await GetNextByteAsync(cancellationToken).ConfigureAwait(false)
+ 1
);
distance &= WSIZE - 1;
}
while (length != 0 && countIndex < count)
{
byte nextByte = windowsBuffer[distance++];
buffer[offset + (countIndex++)] = nextByte;
windowsBuffer[windowIndex++] = nextByte;
outBytesCount++;
if (distance == WSIZE)
{
distance = 0;
}
if (windowIndex == WSIZE)
{
windowIndex = 0;
}
length--;
}
}
return countIndex;
}
#if !LEGACY_DOTNET
public override async ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
if (buffer.IsEmpty || outBytesCount >= unCompressedSize)
{
return 0;
}
byte[] arrayBuffer = new byte[buffer.Length];
int result = await ReadAsync(arrayBuffer, 0, arrayBuffer.Length, cancellationToken)
.ConfigureAwait(false);
arrayBuffer.AsMemory(0, result).CopyTo(buffer);
return result;
}
#endif
}

View File

@@ -4,7 +4,7 @@ using SharpCompress.IO;
namespace SharpCompress.Compressors.Reduce;
public class ReduceStream : Stream, IStreamStack
public partial class ReduceStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
@@ -44,7 +44,7 @@ public class ReduceStream : Stream, IStreamStack
private int length;
private int distance;
public ReduceStream(Stream inStr, long compsize, long unCompSize, int factor)
private ReduceStream(Stream inStr, long compsize, long unCompSize, int factor)
{
inStream = inStr;
compressedSize = compsize;
@@ -69,7 +69,13 @@ public class ReduceStream : Stream, IStreamStack
outByte = 0;
LoadBitLengthTable();
LoadNextByteTable();
}
public static ReduceStream Create(Stream inStr, long compsize, long unCompSize, int factor)
{
var stream = new ReduceStream(inStr, compsize, unCompSize, factor);
stream.LoadNextByteTable();
return stream;
}
protected override void Dispose(bool disposing)

View File

@@ -174,7 +174,7 @@ public class DisposalTests
public void ReduceStream_Disposal()
{
// ReduceStream does not dispose inner stream
VerifyNeverDispose(stream => new ReduceStream(stream, 0, 0, 1));
VerifyNeverDispose(stream => ReduceStream.Create(stream, 0, 0, 1));
}
[Fact]