add shrink stream async

This commit is contained in:
Adam Hathcock
2026-01-31 11:18:16 +00:00
parent b48e938c98
commit c096164486
3 changed files with 182 additions and 30 deletions

View File

@@ -136,12 +136,15 @@ internal abstract partial class ZipFilePart
}
case ZipCompressionMethod.Shrink:
{
return new ShrinkStream(
stream,
CompressionMode.Decompress,
Header.CompressedSize,
Header.UncompressedSize
);
return await ShrinkStream
.CreateAsync(
stream,
CompressionMode.Decompress,
Header.CompressedSize,
Header.UncompressedSize,
cancellationToken
)
.ConfigureAwait(false);
}
case ZipCompressionMethod.Reduce1:
{

View File

@@ -0,0 +1,138 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Compressors.Shrink;
internal partial class ShrinkStream : Stream, IStreamStack
{
internal static async ValueTask<ShrinkStream> CreateAsync(
Stream stream,
CompressionMode compressionMode,
long compressedSize,
long uncompressedSize,
CancellationToken cancellationToken = default
)
{
var shrinkStream = new ShrinkStream(
stream,
compressionMode,
compressedSize,
uncompressedSize
);
await shrinkStream.DecompressAsync(cancellationToken).ConfigureAwait(false);
return shrinkStream;
}
private async Task DecompressAsync(CancellationToken cancellationToken)
{
if (_decompressed)
{
return;
}
if (inStream.Position == (long)_compressedSize)
{
return;
}
// Read all compressed data asynchronously
var src = new byte[_compressedSize];
int bytesRead = 0;
int totalBytesRead = 0;
while (totalBytesRead < (int)_compressedSize)
{
bytesRead = await inStream
.ReadAsync(
src,
totalBytesRead,
(int)_compressedSize - totalBytesRead,
cancellationToken
)
.ConfigureAwait(false);
if (bytesRead == 0)
{
throw new EndOfStreamException(
"Unexpected end of stream while reading compressed data"
);
}
totalBytesRead += bytesRead;
}
// Decompress synchronously (CPU-bound operation)
var srcUsed = 0;
var dstUsed = 0;
HwUnshrink.Unshrink(
src,
(int)_compressedSize,
out srcUsed,
_byteOut,
(int)_uncompressedSize,
out dstUsed
);
_outBytesCount = dstUsed;
_decompressed = true;
}
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
if (!_decompressed)
{
await DecompressAsync(cancellationToken).ConfigureAwait(false);
}
// Copy from decompressed buffer
long remaining = _outBytesCount - _position;
if (remaining <= 0)
{
return 0;
}
int toCopy = (int)Math.Min(count, remaining);
Buffer.BlockCopy(_byteOut, (int)_position, buffer, offset, toCopy);
_position += toCopy;
return toCopy;
}
#if !LEGACY_DOTNET
public override async ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
if (!_decompressed)
{
await DecompressAsync(cancellationToken).ConfigureAwait(false);
}
if (buffer.IsEmpty)
{
return 0;
}
long remaining = _outBytesCount - _position;
if (remaining <= 0)
{
return 0;
}
int toCopy = (int)Math.Min(buffer.Length, remaining);
_byteOut.AsMemory((int)_position, toCopy).CopyTo(buffer);
_position += toCopy;
return toCopy;
}
#endif
}

View File

@@ -4,7 +4,7 @@ using SharpCompress.IO;
namespace SharpCompress.Compressors.Shrink;
internal class ShrinkStream : Stream, IStreamStack
internal partial class ShrinkStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
@@ -33,6 +33,8 @@ internal class ShrinkStream : Stream, IStreamStack
private long _uncompressedSize;
private byte[] _byteOut;
private long _outBytesCount;
private bool _decompressed;
private long _position;
public ShrinkStream(
Stream stream,
@@ -72,7 +74,7 @@ internal class ShrinkStream : Stream, IStreamStack
public override long Position
{
get => _outBytesCount;
get => _position;
set => throw new NotImplementedException();
}
@@ -80,32 +82,41 @@ internal class ShrinkStream : Stream, IStreamStack
public override int Read(byte[] buffer, int offset, int count)
{
if (inStream.Position == (long)_compressedSize)
if (!_decompressed)
{
if (inStream.Position == (long)_compressedSize)
{
return 0;
}
var src = new byte[_compressedSize];
inStream.Read(src, 0, (int)_compressedSize);
var srcUsed = 0;
var dstUsed = 0;
HwUnshrink.Unshrink(
src,
(int)_compressedSize,
out srcUsed,
_byteOut,
(int)_uncompressedSize,
out dstUsed
);
_outBytesCount = dstUsed;
_decompressed = true;
_position = 0;
}
long remaining = _outBytesCount - _position;
if (remaining <= 0)
{
return 0;
}
var src = new byte[_compressedSize];
inStream.Read(src, offset, (int)_compressedSize);
var srcUsed = 0;
var dstUsed = 0;
HwUnshrink.Unshrink(
src,
(int)_compressedSize,
out srcUsed,
_byteOut,
(int)_uncompressedSize,
out dstUsed
);
_outBytesCount = _byteOut.Length;
for (var index = 0; index < _outBytesCount; ++index)
{
buffer[offset + index] = _byteOut[index];
}
var tmp = _outBytesCount;
_outBytesCount = 0;
return (int)tmp;
int toCopy = (int)Math.Min(count, remaining);
Buffer.BlockCopy(_byteOut, (int)_position, buffer, offset, toCopy);
_position += toCopy;
return toCopy;
}
public override long Seek(long offset, SeekOrigin origin) =>