Merge remote-tracking branch 'origin/release' into copilot/fix-async-only-stream-writes

This commit is contained in:
Adam Hathcock
2026-04-28 12:21:10 +01:00
17 changed files with 562 additions and 75 deletions

View File

@@ -45,7 +45,21 @@ internal class AsyncMarkingBinaryReader : IDisposable
{
CurrentReadByteCount += count;
var bytes = new byte[count];
await _reader.ReadBytesAsync(bytes, 0, count, cancellationToken).ConfigureAwait(false);
try
{
await _reader.ReadBytesAsync(bytes, 0, count, cancellationToken).ConfigureAwait(false);
}
catch (IncompleteArchiveException ex)
{
throw new InvalidFormatException(
string.Format(
Constants.DefaultCultureInfo,
"Could not read the requested amount of bytes. End of stream reached. Requested: {0}",
count
),
ex
);
}
return bytes;
}

View File

@@ -281,13 +281,13 @@ internal class ModelPpm
internal bool DecodeInit(IRarUnpack unpackRead, int escChar)
{
var maxOrder = unpackRead.Char & 0xff;
var maxOrder = unpackRead.ReadChar() & 0xff;
var reset = ((maxOrder & 0x20) != 0);
var maxMb = 0;
if (reset)
{
maxMb = unpackRead.Char;
maxMb = unpackRead.ReadChar();
}
else
{
@@ -298,7 +298,7 @@ internal class ModelPpm
}
if ((maxOrder & 0x40) != 0)
{
escChar = unpackRead.Char;
escChar = unpackRead.ReadChar();
unpackRead.PpmEscChar = escChar;
}
Coder = new RangeCoder(unpackRead);
@@ -333,6 +333,65 @@ internal class ModelPpm
return (_minContext.Address != 0);
}
internal async ValueTask<bool> DecodeInitAsync(
IRarUnpack unpackRead,
int escChar,
CancellationToken cancellationToken = default
)
{
var maxOrder =
await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false) & 0xff;
var reset = ((maxOrder & 0x20) != 0);
var maxMb = 0;
if (reset)
{
maxMb = await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false);
}
else
{
if (SubAlloc.GetAllocatedMemory() == 0)
{
return false;
}
}
if ((maxOrder & 0x40) != 0)
{
escChar = await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false);
unpackRead.PpmEscChar = escChar;
}
Coder = new RangeCoder();
await Coder.InitAsync(unpackRead, cancellationToken).ConfigureAwait(false);
if (reset)
{
maxOrder = (maxOrder & 0x1f) + 1;
if (maxOrder > 16)
{
maxOrder = 16 + ((maxOrder - 16) * 3);
}
if (maxOrder == 1)
{
SubAlloc.StopSubAllocator();
return false;
}
SubAlloc.StartSubAllocator((maxMb + 1) << 20);
_minContext = new PpmContext(Heap);
_maxContext = new PpmContext(Heap);
FoundState = new State(Heap);
_dummySee2Cont = new See2Context();
for (var i = 0; i < 25; i++)
{
for (var j = 0; j < 16; j++)
{
_see2Cont[i][j] = new See2Context();
}
}
StartModelRare(maxOrder);
}
return _minContext.Address != 0;
}
public virtual int DecodeChar()
{
// Debug

View File

@@ -19,7 +19,7 @@ internal class RangeCoder
private long _low,
_code,
_range;
private readonly IRarUnpack _unpackRead;
private IRarUnpack _unpackRead;
private readonly Stream _stream;
internal RangeCoder(IRarUnpack unpackRead)
@@ -36,6 +36,24 @@ internal class RangeCoder
internal RangeCoder() { }
internal async ValueTask InitAsync(
IRarUnpack unpackRead,
CancellationToken cancellationToken = default
)
{
_unpackRead = unpackRead;
SubRange = new SubRange();
_low = _code = 0L;
_range = 0xFFFFffffL;
for (var i = 0; i < 4; i++)
{
_code =
((_code << 8) | await ReadCharAsync(cancellationToken).ConfigureAwait(false))
& UINT_MASK;
}
}
private void Init()
{
SubRange = new SubRange();
@@ -44,7 +62,7 @@ internal class RangeCoder
_range = 0xFFFFffffL;
for (var i = 0; i < 4; i++)
{
_code = ((_code << 8) | Char) & UINT_MASK;
_code = ((_code << 8) | ReadChar()) & UINT_MASK;
}
}
@@ -73,20 +91,17 @@ internal class RangeCoder
}
}
private long Char
private long ReadChar()
{
get
if (_unpackRead != null)
{
if (_unpackRead != null)
{
return (_unpackRead.Char);
}
if (_stream != null)
{
return _stream.ReadByte();
}
return -1;
return (_unpackRead.ReadChar());
}
if (_stream != null)
{
return _stream.ReadByte();
}
return -1;
}
internal SubRange SubRange { get; private set; }
@@ -121,7 +136,7 @@ internal class RangeCoder
_range = (-_low & (BOT - 1)) & UINT_MASK;
c2 = false;
}
_code = ((_code << 8) | Char) & UINT_MASK;
_code = ((_code << 8) | ReadChar()) & UINT_MASK;
_range = (_range << 8) & UINT_MASK;
_low = (_low << 8) & UINT_MASK;
}
@@ -131,7 +146,7 @@ internal class RangeCoder
{
if (_unpackRead != null)
{
return _unpackRead.Char;
return await _unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false);
}
if (_stream != null)
{

View File

@@ -22,6 +22,7 @@ internal interface IRarUnpack
bool Suspended { get; set; }
long DestSize { get; }
int Char { get; }
int ReadChar();
ValueTask<int> ReadCharAsync(CancellationToken cancellationToken);
int PpmEscChar { get; set; }
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -112,7 +113,10 @@ internal sealed partial class Unpack
{
return;
}
if ((!solid || !tablesRead) && !ReadTables())
if (
(!solid || !tablesRead)
&& !await ReadTablesAsync(cancellationToken).ConfigureAwait(false)
)
{
return;
}
@@ -137,7 +141,7 @@ internal sealed partial class Unpack
if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr)
{
UnpWriteBuf();
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
if (destUnpSize < 0)
{
return;
@@ -150,7 +154,7 @@ internal sealed partial class Unpack
}
if (unpBlockType == BlockTypes.BLOCK_PPM)
{
var Ch = ppm.DecodeChar();
var Ch = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (Ch == -1)
{
ppmError = true;
@@ -158,10 +162,10 @@ internal sealed partial class Unpack
}
if (Ch == PpmEscChar)
{
var NextCh = ppm.DecodeChar();
var NextCh = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (NextCh == 0)
{
if (!ReadTables())
if (!await ReadTablesAsync(cancellationToken).ConfigureAwait(false))
{
break;
}
@@ -173,7 +177,7 @@ internal sealed partial class Unpack
}
if (NextCh == 3)
{
if (!ReadVMCodePPM())
if (!await ReadVMCodePPMAsync(cancellationToken).ConfigureAwait(false))
{
break;
}
@@ -186,7 +190,8 @@ internal sealed partial class Unpack
var failed = false;
for (var I = 0; I < 4 && !failed; I++)
{
var ch = ppm.DecodeChar();
var ch = await ppm.DecodeCharAsync(cancellationToken)
.ConfigureAwait(false);
if (ch == -1)
{
failed = true;
@@ -212,7 +217,8 @@ internal sealed partial class Unpack
}
if (NextCh == 5)
{
var Length = ppm.DecodeChar();
var Length = await ppm.DecodeCharAsync(cancellationToken)
.ConfigureAwait(false);
if (Length == -1)
{
break;
@@ -294,7 +300,7 @@ internal sealed partial class Unpack
}
if (Number == 256)
{
if (!ReadEndOfBlock())
if (!await ReadEndOfBlockAsync(cancellationToken).ConfigureAwait(false))
{
break;
}
@@ -302,7 +308,7 @@ internal sealed partial class Unpack
}
if (Number == 257)
{
if (!ReadVMCode())
if (!await ReadVMCodeAsync(cancellationToken).ConfigureAwait(false))
{
break;
}
@@ -350,7 +356,7 @@ internal sealed partial class Unpack
CopyString(2, Distance);
}
}
UnpWriteBuf();
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
}
private async Task UnpWriteBufAsync(CancellationToken cancellationToken = default)
@@ -600,4 +606,267 @@ internal sealed partial class Unpack
writtenFileSize += size;
destUnpSize -= size;
}
private async Task<bool> ReadTablesAsync(CancellationToken cancellationToken = default)
{
var bitLengthArray = ArrayPool<byte>.Shared.Rent(PackDef.BC);
var bitLength = new Memory<byte>(bitLengthArray, 0, PackDef.BC);
var tableArray = ArrayPool<byte>.Shared.Rent(PackDef.HUFF_TABLE_SIZE);
var table = new Memory<byte>(tableArray, 0, PackDef.HUFF_TABLE_SIZE);
try
{
if (inAddr > readTop - 25)
{
if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false))
{
return false;
}
}
AddBits((8 - inBit) & 7);
long bitField = GetBits() & unchecked((int)0xffFFffFF);
if ((bitField & 0x8000) != 0)
{
unpBlockType = BlockTypes.BLOCK_PPM;
return await ppm.DecodeInitAsync(this, PpmEscChar, cancellationToken)
.ConfigureAwait(false);
}
unpBlockType = BlockTypes.BLOCK_LZ;
prevLowDist = 0;
lowDistRepCount = 0;
if ((bitField & 0x4000) == 0)
{
new Span<byte>(unpOldTable).Clear();
}
AddBits(2);
for (var i = 0; i < PackDef.BC; i++)
{
var length = (Utility.URShift(GetBits(), 12)) & 0xFF;
AddBits(4);
if (length == 15)
{
var zeroCount = (Utility.URShift(GetBits(), 12)) & 0xFF;
AddBits(4);
if (zeroCount == 0)
{
bitLength.Span[i] = 15;
}
else
{
zeroCount += 2;
while (zeroCount-- > 0 && i < bitLength.Length)
{
bitLength.Span[i++] = 0;
}
i--;
}
}
else
{
bitLength.Span[i] = (byte)length;
}
}
UnpackUtility.makeDecodeTables(bitLength.Span, 0, BD, PackDef.BC);
var TableSize = PackDef.HUFF_TABLE_SIZE;
for (var i = 0; i < TableSize; )
{
if (inAddr > readTop - 5)
{
if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false))
{
return false;
}
}
var Number = this.decodeNumber(BD);
if (Number < 16)
{
table.Span[i] = (byte)((Number + unpOldTable[i]) & 0xf);
i++;
}
else if (Number < 18)
{
int N;
if (Number == 16)
{
N = (Utility.URShift(GetBits(), 13)) + 3;
AddBits(3);
}
else
{
N = (Utility.URShift(GetBits(), 9)) + 11;
AddBits(7);
}
while (N-- > 0 && i < TableSize)
{
table.Span[i] = table.Span[i - 1];
i++;
}
}
else
{
int N;
if (Number == 18)
{
N = (Utility.URShift(GetBits(), 13)) + 3;
AddBits(3);
}
else
{
N = (Utility.URShift(GetBits(), 9)) + 11;
AddBits(7);
}
while (N-- > 0 && i < TableSize)
{
table.Span[i++] = 0;
}
}
}
tablesRead = true;
if (inAddr > readTop)
{
return false;
}
UnpackUtility.makeDecodeTables(table.Span, 0, LD, PackDef.NC);
UnpackUtility.makeDecodeTables(table.Span, PackDef.NC, DD, PackDef.DC);
UnpackUtility.makeDecodeTables(table.Span, PackDef.NC + PackDef.DC, LDD, PackDef.LDC);
UnpackUtility.makeDecodeTables(
table.Span,
PackDef.NC + PackDef.DC + PackDef.LDC,
RD,
PackDef.RC
);
table.Span.CopyTo(unpOldTable);
return true;
}
finally
{
ArrayPool<byte>.Shared.Return(bitLengthArray);
ArrayPool<byte>.Shared.Return(tableArray);
}
}
private async Task<bool> ReadEndOfBlockAsync(CancellationToken cancellationToken = default)
{
var BitField = GetBits();
bool NewTable,
NewFile = false;
if ((BitField & 0x8000) != 0)
{
NewTable = true;
AddBits(1);
}
else
{
NewFile = true;
NewTable = (BitField & 0x4000) != 0;
AddBits(2);
}
tablesRead = !NewTable;
return !(
NewFile || NewTable && !await ReadTablesAsync(cancellationToken).ConfigureAwait(false)
);
}
private async Task<bool> ReadVMCodeAsync(CancellationToken cancellationToken = default)
{
var FirstByte = GetBits() >> 8;
AddBits(8);
var Length = (FirstByte & 7) + 1;
if (Length == 7)
{
Length = (GetBits() >> 8) + 7;
AddBits(8);
}
else if (Length == 8)
{
Length = GetBits();
AddBits(16);
}
var vmCode = new List<byte>();
for (var I = 0; I < Length; I++)
{
if (
inAddr >= readTop - 1
&& !await unpReadBufAsync(cancellationToken).ConfigureAwait(false)
&& I < Length - 1
)
{
return false;
}
vmCode.Add((byte)(GetBits() >> 8));
AddBits(8);
}
return AddVMCode(FirstByte, vmCode);
}
public async ValueTask<int> ReadCharAsync(CancellationToken cancellationToken = default)
{
if (inAddr > MAX_SIZE - 30)
{
await unpReadBufAsync(cancellationToken).ConfigureAwait(false);
}
return InBuf[inAddr++] & 0xff;
}
private async Task<bool> ReadVMCodePPMAsync(CancellationToken cancellationToken = default)
{
var FirstByte = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (FirstByte == -1)
{
return false;
}
var Length = (FirstByte & 7) + 1;
if (Length == 7)
{
var B1 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (B1 == -1)
{
return false;
}
Length = B1 + 7;
}
else if (Length == 8)
{
var B1 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (B1 == -1)
{
return false;
}
var B2 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (B2 == -1)
{
return false;
}
Length = (B1 * 256) + B2;
}
var vmCode = new List<byte>();
for (var I = 0; I < Length; I++)
{
var Ch = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false);
if (Ch == -1)
{
return false;
}
vmCode.Add((byte)Ch);
}
return AddVMCode(FirstByte, vmCode);
}
}

View File

@@ -55,16 +55,13 @@ internal sealed partial class Unpack : BitInput, IRarUnpack
set => suspended = value;
}
public int Char
public int ReadChar()
{
get
if (inAddr > MAX_SIZE - 30)
{
if (inAddr > MAX_SIZE - 30)
{
unpReadBuf();
}
return (InBuf[inAddr++] & 0xff);
unpReadBuf();
}
return (InBuf[inAddr++] & 0xff);
}
public int PpmEscChar { get; set; }

View File

@@ -48,7 +48,7 @@ internal partial class Unpack
}
if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr)
{
oldUnpWriteBuf();
await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
if (suspended)
{
return;
@@ -105,7 +105,7 @@ internal partial class Unpack
}
}
}
oldUnpWriteBuf();
await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
}
private async Task<bool> unpReadBufAsync(CancellationToken cancellationToken = default)

View File

@@ -45,7 +45,7 @@ internal partial class Unpack
}
if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr)
{
oldUnpWriteBuf();
await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
if (suspended)
{
return;
@@ -157,8 +157,8 @@ internal partial class Unpack
CopyString20(2, Distance);
}
}
ReadLastTables();
oldUnpWriteBuf();
await ReadLastTablesAsync(cancellationToken).ConfigureAwait(false);
await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
}
private async Task<bool> ReadTables20Async(CancellationToken cancellationToken = default)
@@ -272,4 +272,25 @@ internal partial class Unpack
}
return true;
}
private async Task ReadLastTablesAsync(CancellationToken cancellationToken = default)
{
if (readTop >= inAddr + 5)
{
if (UnpAudioBlock != 0)
{
if (this.decodeNumber(MD[UnpCurChannel]) == 256)
{
await ReadTables20Async(cancellationToken).ConfigureAwait(false);
}
}
else
{
if (this.decodeNumber(LD) == 269)
{
await ReadTables20Async(cancellationToken).ConfigureAwait(false);
}
}
}
}
}

View File

@@ -70,7 +70,7 @@ internal partial class Unpack
// So we can safefly use these tables below.
if (
!await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false)
|| !ReadTables()
|| !await ReadTablesAsync(cancellationToken).ConfigureAwait(false)
|| !TablesRead5
)
{
@@ -101,7 +101,7 @@ internal partial class Unpack
}
if (
!await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false)
|| !ReadTables()
|| !await ReadTablesAsync(cancellationToken).ConfigureAwait(false)
)
{
return;
@@ -118,7 +118,7 @@ internal partial class Unpack
&& WriteBorder != UnpPtr
)
{
UnpWriteBuf();
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
if (WrittenFileSize > DestUnpSize)
{
return;
@@ -197,7 +197,7 @@ internal partial class Unpack
var Filter = new UnpackFilter();
if (
!await ReadFilterAsync(Filter, cancellationToken).ConfigureAwait(false)
|| !AddFilter(Filter)
|| !await AddFilterAsync(Filter, cancellationToken).ConfigureAwait(false)
)
{
break;
@@ -232,7 +232,7 @@ internal partial class Unpack
continue;
}
}
UnpWriteBuf();
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
}
private async Task<bool> ReadBlockHeaderAsync(CancellationToken cancellationToken = default)
@@ -318,4 +318,24 @@ internal partial class Unpack
return true;
}
private async Task<bool> AddFilterAsync(
UnpackFilter Filter,
CancellationToken cancellationToken = default
)
{
if (Filters.Count >= MAX_UNPACK_FILTERS)
{
await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false);
if (Filters.Count >= MAX_UNPACK_FILTERS)
{
InitFilters();
}
}
Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart;
Filter.uBlockStart = (uint)((Filter.BlockStart + UnpPtr) & MaxWinMask);
Filters.Add(Filter);
return true;
}
}

View File

@@ -154,17 +154,25 @@ internal partial class Unpack : IRarUnpack
public long DestSize => DestUnpSize;
public int Char
public int ReadChar()
{
get
// TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code
if (InAddr > MAX_SIZE - 30)
{
// TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code
if (InAddr > MAX_SIZE - 30)
{
UnpReadBuf();
}
return InBuf[InAddr++];
UnpReadBuf();
}
return InBuf[InAddr++];
}
public async ValueTask<int> ReadCharAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code
if (InAddr > MAX_SIZE - 30)
{
await UnpReadBufAsync(cancellationToken).ConfigureAwait(false);
}
return InBuf[InAddr++];
}
public int PpmEscChar

View File

@@ -41,7 +41,7 @@ internal partial class Unpack
if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr)
{
UnpWriteBuf20();
await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false);
}
if (StMode != 0)
@@ -95,6 +95,6 @@ internal partial class Unpack
}
}
}
UnpWriteBuf20();
await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -49,7 +49,7 @@ internal partial class Unpack
if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr)
{
UnpWriteBuf20();
await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false);
if (Suspended)
{
return;
@@ -165,8 +165,8 @@ internal partial class Unpack
continue;
}
}
ReadLastTables();
UnpWriteBuf20();
await ReadLastTables20Async(cancellationToken).ConfigureAwait(false);
await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false);
}
private async Task UnpWriteBuf20Async(CancellationToken cancellationToken = default)
@@ -316,4 +316,22 @@ internal partial class Unpack
Array.Copy(Table, 0, this.UnpOldTable20, 0, UnpOldTable20.Length);
return true;
}
private async Task ReadLastTables20Async(CancellationToken cancellationToken = default)
{
if (ReadTop >= Inp.InAddr + 5)
{
if (UnpAudioBlock)
{
if (DecodeNumber(Inp, MD[UnpCurChannel]) == 256)
{
await ReadTables20Async(cancellationToken).ConfigureAwait(false);
}
}
else if (DecodeNumber(Inp, BlockTables.LD) == 269)
{
await ReadTables20Async(cancellationToken).ConfigureAwait(false);
}
}
}
}

View File

@@ -60,15 +60,10 @@ public sealed class AsyncBinaryReader : IDisposable
int offset,
int count,
CancellationToken ct = default
)
{
await _stream.ReadExactAsync(bytes, offset, count, ct).ConfigureAwait(false);
}
) => await _stream.ReadExactAsync(bytes, offset, count, ct).ConfigureAwait(false);
public async ValueTask SkipAsync(int count, CancellationToken ct = default)
{
public async ValueTask SkipAsync(int count, CancellationToken ct = default) =>
await _stream.SkipAsync(count, ct).ConfigureAwait(false);
}
public void Dispose()
{

View File

@@ -5,14 +5,9 @@ using System.Threading.Tasks;
namespace SharpCompress.Test.Mocks;
public class AsyncOnlyStream : Stream
public class AsyncOnlyStream(Stream stream) : Stream
{
private readonly Stream _stream;
public AsyncOnlyStream(Stream stream)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
}
private readonly Stream _stream = stream ?? throw new ArgumentNullException(nameof(stream));
public override bool CanRead => _stream.CanRead;
public override bool CanSeek => _stream.CanSeek;

View File

@@ -13,6 +13,31 @@ namespace SharpCompress.Test.Rar;
public class RarArchiveAsyncTests : ArchiveTests
{
[Theory]
[InlineData("Rar15.rar")]
[InlineData("Rar2.rar")]
[InlineData("Rar.rar")]
[InlineData("Rar.Audio_program.rar")]
[InlineData("Rar5.rar")]
[InlineData("Rar5.solid.rar")]
public async ValueTask Rar_Archive_Recently_Changed_Unpackers_Async(string filename)
{
var extractedEntries = 0;
await using var archive = await RarArchive.OpenAsyncArchive(
Path.Combine(TEST_ARCHIVES_PATH, filename),
new ReaderOptions { LookForHeader = true }
);
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
{
using var output = new AsyncOnlyStream(new MemoryStream());
await entry.WriteToAsync(output);
extractedEntries++;
}
Assert.True(extractedEntries > 0);
}
[Fact]
public async ValueTask Rar_EncryptedFileAndHeader_Archive_Async() =>
await ReadRarPasswordAsync("Rar.encrypted_filesAndHeader.rar", "test");

View File

@@ -13,6 +13,32 @@ namespace SharpCompress.Test.Rar;
public class RarArchiveTests : ArchiveTests
{
[Theory]
[InlineData("Rar15.rar")]
[InlineData("Rar2.rar")]
[InlineData("Rar.rar")]
[InlineData("Rar.Audio_program.rar")]
[InlineData("Rar5.rar")]
[InlineData("Rar5.solid.rar")]
public void Rar_Archive_Recently_Changed_Unpackers_Sync(string filename)
{
var extractedEntries = 0;
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
using var archive = RarArchive.OpenArchive(
stream,
new ReaderOptions { LookForHeader = true }
);
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
using var output = new MemoryStream();
entry.WriteTo(output);
extractedEntries++;
}
Assert.True(extractedEntries > 0);
}
[Fact]
public void Rar_EncryptedFileAndHeader_Archive() =>
ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test");

View File

@@ -14,6 +14,30 @@ namespace SharpCompress.Test.Rar;
public class RarReaderAsyncTests : ReaderTests
{
[Theory]
[InlineData("Rar15.rar")]
[InlineData("Rar.rar")]
[InlineData("Rar.Audio_program.rar")]
[InlineData("Rar5.rar")]
[InlineData("Rar5.solid.rar")]
public async ValueTask Rar_Reader_Async_Uses_Only_Async_Stream_Operations(string filename)
{
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
await using var reader = await ReaderFactory.OpenAsyncReader(
new AsyncOnlyStream(stream),
new ReaderOptions { LookForHeader = true }
);
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
using var output = new AsyncOnlyStream(new MemoryStream());
await reader.WriteEntryToAsync(output);
}
}
}
[Fact]
public async ValueTask Rar_Multi_Reader_Async() =>
await DoRar_Multi_Reader_Async([