diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs index e222a835..1dd81886 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.Async.cs @@ -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: { diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs index 64867f4c..5adc5310 100644 --- a/src/SharpCompress/Common/Zip/ZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs @@ -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: { diff --git a/src/SharpCompress/Compressors/Reduce/ReduceStream.Async.cs b/src/SharpCompress/Compressors/Reduce/ReduceStream.Async.cs new file mode 100644 index 00000000..35248e51 --- /dev/null +++ b/src/SharpCompress/Compressors/Reduce/ReduceStream.Async.cs @@ -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 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 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 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 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 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 ReadAsync( + Memory 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 +} diff --git a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs index ea8e70d2..ff885002 100644 --- a/src/SharpCompress/Compressors/Reduce/ReduceStream.cs +++ b/src/SharpCompress/Compressors/Reduce/ReduceStream.cs @@ -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) diff --git a/tests/SharpCompress.Test/Streams/DisposalTests.cs b/tests/SharpCompress.Test/Streams/DisposalTests.cs index 1766ec3a..75d6c69e 100644 --- a/tests/SharpCompress.Test/Streams/DisposalTests.cs +++ b/tests/SharpCompress.Test/Streams/DisposalTests.cs @@ -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]