fix async parts of arc

This commit is contained in:
Adam Hathcock
2026-02-03 08:14:25 +00:00
parent 6af612fd54
commit 236ee215b9
8 changed files with 311 additions and 41 deletions

View File

@@ -2,6 +2,8 @@ using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Common.Arc
{
@@ -32,6 +34,23 @@ namespace SharpCompress.Common.Arc
return LoadFrom(headerBytes);
}
public async ValueTask<ArcEntryHeader?> ReadHeaderAsync(
Stream stream,
CancellationToken cancellationToken = default
)
{
byte[] headerBytes = new byte[29];
if (
await stream.ReadAsync(headerBytes, 0, headerBytes.Length, cancellationToken)
!= headerBytes.Length
)
{
return null;
}
DataStartPosition = stream.Position;
return LoadFrom(headerBytes);
}
public ArcEntryHeader LoadFrom(byte[] headerBytes)
{
CompressionMethod = GetCompressionType(headerBytes[1]);

View File

@@ -0,0 +1,68 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.RLE90;
using SharpCompress.Compressors.Squeezed;
using SharpCompress.IO;
namespace SharpCompress.Common.Arc
{
public partial class ArcFilePart
{
internal override async ValueTask<Stream?> GetCompressedStreamAsync(
CancellationToken cancellationToken = default
)
{
if (_stream != null)
{
Stream compressedStream;
switch (Header.CompressionMethod)
{
case CompressionType.None:
compressedStream = new ReadOnlySubStream(
_stream,
Header.DataStartPosition,
Header.CompressedSize
);
break;
case CompressionType.Packed:
compressedStream = new RunLength90Stream(
_stream,
(int)Header.CompressedSize
);
break;
case CompressionType.Squeezed:
compressedStream = await SqueezeStream.CreateAsync(
_stream,
(int)Header.CompressedSize,
cancellationToken
);
break;
case CompressionType.Crunched:
if (Header.OriginalSize > 128 * 1024)
{
throw new NotSupportedException(
"CompressionMethod: "
+ Header.CompressionMethod
+ " with size > 128KB"
);
}
compressedStream = new ArcLzwStream(
_stream,
(int)Header.CompressedSize,
true
);
break;
default:
throw new NotSupportedException(
"CompressionMethod: " + Header.CompressionMethod
);
}
return compressedStream;
}
return _stream;
}
}
}

View File

@@ -15,7 +15,7 @@ using SharpCompress.IO;
namespace SharpCompress.Common.Arc
{
public class ArcFilePart : FilePart
public partial class ArcFilePart : FilePart
{
private readonly Stream? _stream;
@@ -51,7 +51,10 @@ namespace SharpCompress.Common.Arc
);
break;
case CompressionType.Squeezed:
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
compressedStream = SqueezeStream.Create(
_stream,
(int)Header.CompressedSize
);
break;
case CompressionType.Crunched:
if (Header.OriginalSize > 128 * 1024)

View File

@@ -0,0 +1,33 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Compressors.Squeezed
{
public partial class BitReader
{
public async ValueTask<bool> ReadBitAsync(CancellationToken cancellationToken = default)
{
if (_bitCount == 0)
{
byte[] buffer = new byte[1];
int bytesRead = await _stream
.ReadAsync(buffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
if (bytesRead == 0)
{
throw new EndOfStreamException();
}
_bitBuffer = buffer[0];
_bitCount = 8;
}
bool bit = (_bitBuffer & 1) != 0;
_bitBuffer >>= 1;
_bitCount--;
return bit;
}
}
}

View File

@@ -1,51 +1,57 @@
using System;
using System.IO;
public class BitReader
namespace SharpCompress.Compressors.Squeezed
{
private readonly Stream _stream;
private int _bitBuffer;
private int _bitCount;
public BitReader(Stream stream)
public partial class BitReader
{
_stream = stream;
_bitBuffer = 0;
_bitCount = 0;
}
private readonly Stream _stream;
private int _bitBuffer;
private int _bitCount;
public bool ReadBit()
{
if (_bitCount == 0)
public BitReader(Stream stream)
{
int nextByte = _stream.ReadByte();
if (nextByte == -1)
_stream = stream;
_bitBuffer = 0;
_bitCount = 0;
}
public bool ReadBit()
{
if (_bitCount == 0)
{
throw new EndOfStreamException();
int nextByte = _stream.ReadByte();
if (nextByte == -1)
{
throw new EndOfStreamException();
}
_bitBuffer = nextByte;
_bitCount = 8;
}
_bitBuffer = nextByte;
_bitCount = 8;
bool bit = (_bitBuffer & 1) != 0;
_bitBuffer >>= 1;
_bitCount--;
return bit;
}
bool bit = (_bitBuffer & 1) != 0;
_bitBuffer >>= 1;
_bitCount--;
return bit;
}
public int ReadBits(int count)
{
if (count < 1 || count > 32)
public int ReadBits(int count)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count must be between 1 and 32.");
}
if (count < 1 || count > 32)
{
throw new ArgumentOutOfRangeException(
nameof(count),
"Count must be between 1 and 32."
);
}
int value = 0;
for (int i = 0; i < count; i++)
{
value = (value << 1) | (ReadBit() ? 1 : 0);
int value = 0;
for (int i = 0; i < count; i++)
{
value = (value << 1) | (ReadBit() ? 1 : 0);
}
return value;
}
return value;
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Compressors.RLE90;
namespace SharpCompress.Compressors.Squeezed
{
public partial class SqueezeStream
{
public static async ValueTask<SqueezeStream> CreateAsync(
Stream stream,
int compressedSize,
CancellationToken cancellationToken = default
)
{
var squeezeStream = new SqueezeStream(stream, compressedSize);
squeezeStream._decodedStream = await squeezeStream
.BuildDecodedStreamAsync(cancellationToken)
.ConfigureAwait(false);
#if DEBUG_STREAMS
squeezeStream.DebugConstruct(typeof(SqueezeStream));
#endif
return squeezeStream;
}
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
return await _decodedStream
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
}
#if !LEGACY_DOTNET
public override async ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
return await _decodedStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
}
#endif
private async Task<Stream> BuildDecodedStreamAsync(CancellationToken cancellationToken)
{
byte[] numNodesBytes = new byte[2];
int bytesRead = await _stream
.ReadAsync(numNodesBytes, 0, 2, cancellationToken)
.ConfigureAwait(false);
if (bytesRead != 2)
{
return new MemoryStream(Array.Empty<byte>());
}
int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8);
if (numnodes >= NUMVALS || numnodes == 0)
{
return new MemoryStream(Array.Empty<byte>());
}
var dnode = new int[numnodes, 2];
for (int j = 0; j < numnodes; j++)
{
byte[] nodeBytes = new byte[4];
bytesRead = await _stream
.ReadAsync(nodeBytes, 0, 4, cancellationToken)
.ConfigureAwait(false);
if (bytesRead != 4)
{
throw new EndOfStreamException();
}
dnode[j, 0] = (short)(nodeBytes[0] | (nodeBytes[1] << 8));
dnode[j, 1] = (short)(nodeBytes[2] | (nodeBytes[3] << 8));
}
var bitReader = new BitReader(_stream);
var huffmanDecoded = new MemoryStream();
int i = 0;
while (true)
{
bool bit = await bitReader.ReadBitAsync(cancellationToken).ConfigureAwait(false);
i = dnode[i, bit ? 1 : 0];
if (i < 0)
{
i = -(i + 1);
if (i == SPEOF)
{
break;
}
huffmanDecoded.WriteByte((byte)i);
i = 0;
}
}
huffmanDecoded.Position = 0;
return new RunLength90Stream(huffmanDecoded, (int)huffmanDecoded.Length);
}
}
}

View File

@@ -7,24 +7,31 @@ using SharpCompress.Compressors.RLE90;
namespace SharpCompress.Compressors.Squeezed
{
[CLSCompliant(true)]
public class SqueezeStream : Stream
public partial class SqueezeStream : Stream
{
private readonly Stream _stream;
private readonly int _compressedSize;
private const int NUMVALS = 257;
private const int SPEOF = 256;
private Stream _decodedStream;
private Stream _decodedStream = null!;
public SqueezeStream(Stream stream, int compressedSize)
private SqueezeStream(Stream stream, int compressedSize)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
_compressedSize = compressedSize;
_decodedStream = BuildDecodedStream();
}
public static SqueezeStream Create(Stream stream, int compressedSize)
{
var squeezeStream = new SqueezeStream(stream, compressedSize);
squeezeStream._decodedStream = squeezeStream.BuildDecodedStream();
#if DEBUG_STREAMS
this.DebugConstruct(typeof(SqueezeStream));
squeezeStream.DebugConstruct(typeof(SqueezeStream));
#endif
return squeezeStream;
}
protected override void Dispose(bool disposing)

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using SharpCompress.Common.Arc;
namespace SharpCompress.Readers.Arc
{
public partial class ArcReader
{
protected override async IAsyncEnumerable<ArcEntry> GetEntriesAsync(Stream stream)
{
ArcEntryHeader headerReader = new ArcEntryHeader(Options.ArchiveEncoding);
ArcEntryHeader? header;
while (
(header = await headerReader.ReadHeaderAsync(stream, CancellationToken.None))
!= null
)
{
yield return new ArcEntry(new ArcFilePart(header, stream));
}
}
}
}