Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3d29c18
basic async usage
adamhathcock Oct 28, 2025
1ba529a
first pass of async files
adamhathcock Oct 28, 2025
b23f031
add async reads
adamhathcock Oct 28, 2025
8e7d959
add async creations
adamhathcock Oct 28, 2025
aca97c2
add rarcrc tests
adamhathcock Oct 28, 2025
bb53d1e
entrystream fixes and fmt
adamhathcock Oct 29, 2025
b354f7a
fix logic mistake
adamhathcock Oct 29, 2025
df2ed1e
fix RarStream
adamhathcock Oct 29, 2025
a0c5b1c
add archive tests
adamhathcock Oct 29, 2025
351e294
convert unpack15 and unpack20
adamhathcock Oct 29, 2025
aa4cd37
added more async methods
adamhathcock Oct 29, 2025
16543bf
async UnpReadBufAsync
adamhathcock Oct 29, 2025
a09327b
unpack50async
adamhathcock Oct 29, 2025
1af51aa
async unpack1.5
adamhathcock Oct 29, 2025
58bab0d
async unpack2.0
adamhathcock Oct 29, 2025
b83e6ee
fix async usage
adamhathcock Oct 29, 2025
8324114
remove unused code
adamhathcock Oct 29, 2025
665d8cd
ran out of tokens, things work but need one more conversion
adamhathcock Oct 29, 2025
dc31e4c
fmt
adamhathcock Oct 29, 2025
f238be6
add arraypool usage in init
adamhathcock Oct 29, 2025
65e6074
add async for UnpWriteBufAsync and remove comments
adamhathcock Oct 29, 2025
75ada56
add async tests for compress stream
adamhathcock Oct 29, 2025
e786e95
Initial plan
Copilot Oct 29, 2025
88b3a66
Fix Windows test failures in SharpCompressStreamTests
Copilot Oct 29, 2025
ab7196f
some review fixes
adamhathcock Oct 29, 2025
77c8d31
Merge pull request #1000 from adamhathcock/copilot/sub-pr-996
adamhathcock Oct 29, 2025
ca4a193
Merge remote-tracking branch 'origin/adam/async-rar-ai' into adam/asy…
adamhathcock Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions src/SharpCompress/Archives/Rar/RarArchiveEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,51 @@ public override long CompressedSize

public Stream OpenEntryStream()
{
RarStream stream;
if (IsRarV3)
{
return new RarStream(
stream = new RarStream(
archive.UnpackV1.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
else
{
stream = new RarStream(
archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}

return new RarStream(
archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
stream.Initialize();
return stream;
}

public Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
Task.FromResult(OpenEntryStream());
public async Task<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
{
RarStream stream;
if (IsRarV3)
{
stream = new RarStream(
archive.UnpackV1.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
else
{
stream = new RarStream(
archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
Comment on lines +97 to +113
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Suggested change
RarStream stream;
if (IsRarV3)
{
stream = new RarStream(
archive.UnpackV1.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
else
{
stream = new RarStream(
archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
RarStream stream = new RarStream(
IsRarV3 ? archive.UnpackV1.Value : archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);

Copilot uses AI. Check for mistakes.

await stream.InitializeAsync(cancellationToken);
return stream;
}

public bool IsComplete
{
Expand Down
29 changes: 17 additions & 12 deletions src/SharpCompress/Common/EntryStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public async Task SkipEntryAsync(CancellationToken cancellationToken = default)

protected override void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (!(_completed || _reader.Cancelled))
{
SkipEntry();
Expand All @@ -81,12 +86,6 @@ protected override void Dispose(bool disposing)
lzmaStream.Flush(); //Lzma over reads. Knock it back
}
}

if (_isDisposed)
{
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(EntryStream));
#endif
Expand All @@ -97,6 +96,11 @@ protected override void Dispose(bool disposing)
#if !NETFRAMEWORK && !NETSTANDARD2_0
public override async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (!(_completed || _reader.Cancelled))
{
await SkipEntryAsync().ConfigureAwait(false);
Expand All @@ -114,12 +118,6 @@ public override async ValueTask DisposeAsync()
await lzmaStream.FlushAsync().ConfigureAwait(false);
}
}

if (_isDisposed)
{
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(EntryStream));
#endif
Expand Down Expand Up @@ -204,4 +202,11 @@ public override int ReadByte()

public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();

public override Task WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
) => throw new NotSupportedException();
}
10 changes: 10 additions & 0 deletions src/SharpCompress/Compressors/Rar/IRarUnpack.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar.Headers;

namespace SharpCompress.Compressors.Rar;
Expand All @@ -8,6 +10,14 @@ internal interface IRarUnpack
void DoUnpack(FileHeader fileHeader, Stream readStream, Stream writeStream);
void DoUnpack();

Task DoUnpackAsync(
FileHeader fileHeader,
Stream readStream,
Stream writeStream,
CancellationToken cancellationToken
);
Task DoUnpackAsync(CancellationToken cancellationToken);

// eg u/i pause/resume button
bool Suspended { get; set; }

Expand Down
130 changes: 130 additions & 0 deletions src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,136 @@ public override int Read(byte[] buffer, int offset, int count)
return totalRead;
}

public override async System.Threading.Tasks.Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
System.Threading.CancellationToken cancellationToken
)
{
var totalRead = 0;
var currentOffset = offset;
var currentCount = count;
while (currentCount > 0)
{
var readSize = currentCount;
if (currentCount > maxPosition - currentPosition)
{
readSize = (int)(maxPosition - currentPosition);
}

var read = await currentStream
.ReadAsync(buffer, currentOffset, readSize, cancellationToken)
.ConfigureAwait(false);
if (read < 0)
{
throw new EndOfStreamException();
}

currentPosition += read;
currentOffset += read;
currentCount -= read;
totalRead += read;
if (
((maxPosition - currentPosition) == 0)
&& filePartEnumerator.Current.FileHeader.IsSplitAfter
)
{
if (filePartEnumerator.Current.FileHeader.R4Salt != null)
{
throw new InvalidFormatException(
"Sharpcompress currently does not support multi-volume decryption."
);
}
var fileName = filePartEnumerator.Current.FileHeader.FileName;
if (!filePartEnumerator.MoveNext())
{
throw new InvalidFormatException(
"Multi-part rar file is incomplete. Entry expects a new volume: "
+ fileName
);
}
InitializeNextFilePart();
}
else
{
break;
}
}
currentPartTotalReadBytes += totalRead;
currentEntryTotalReadBytes += totalRead;
streamListener.FireCompressedBytesRead(
currentPartTotalReadBytes,
currentEntryTotalReadBytes
);
return totalRead;
}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
Memory<byte> buffer,
System.Threading.CancellationToken cancellationToken = default
)
{
var totalRead = 0;
var currentOffset = 0;
var currentCount = buffer.Length;
while (currentCount > 0)
{
var readSize = currentCount;
if (currentCount > maxPosition - currentPosition)
{
readSize = (int)(maxPosition - currentPosition);
}

var read = await currentStream
.ReadAsync(buffer.Slice(currentOffset, readSize), cancellationToken)
.ConfigureAwait(false);
if (read < 0)
{
throw new EndOfStreamException();
}

currentPosition += read;
currentOffset += read;
currentCount -= read;
totalRead += read;
if (
((maxPosition - currentPosition) == 0)
&& filePartEnumerator.Current.FileHeader.IsSplitAfter
)
{
if (filePartEnumerator.Current.FileHeader.R4Salt != null)
{
throw new InvalidFormatException(
"Sharpcompress currently does not support multi-volume decryption."
);
}
var fileName = filePartEnumerator.Current.FileHeader.FileName;
if (!filePartEnumerator.MoveNext())
{
throw new InvalidFormatException(
"Multi-part rar file is incomplete. Entry expects a new volume: "
+ fileName
);
}
InitializeNextFilePart();
}
else
{
break;
}
}
currentPartTotalReadBytes += totalRead;
currentEntryTotalReadBytes += totalRead;
streamListener.FireCompressedBytesRead(
currentPartTotalReadBytes,
currentEntryTotalReadBytes
);
return totalRead;
}
#endif

public override bool CanRead => true;

public override bool CanSeek => false;
Expand Down
82 changes: 81 additions & 1 deletion src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
Expand Down Expand Up @@ -103,7 +105,7 @@ public BLAKE2SP()

byte[] _hash = { };

public RarBLAKE2spStream(
private RarBLAKE2spStream(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyStream readStream
Expand All @@ -121,6 +123,29 @@ MultiVolumeReadOnlyStream readStream
ResetCrc();
}

public static RarBLAKE2spStream Create(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyStream readStream
)
{
var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream);
stream.Initialize();
return stream;
}

public static async Task<RarBLAKE2spStream> CreateAsync(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyStream readStream,
CancellationToken cancellationToken = default
)
{
var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream);
await stream.InitializeAsync(cancellationToken);
return stream;
}

protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
Expand Down Expand Up @@ -333,4 +358,59 @@ public override int Read(byte[] buffer, int offset, int count)

return result;
}

public override async System.Threading.Tasks.Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
System.Threading.CancellationToken cancellationToken
)
{
var result = await base.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
if (result != 0)
{
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
}
else
{
_hash = Final(_blake2sp);
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
{
// NOTE: we use the last FileHeader in a multipart volume to check CRC
throw new InvalidFormatException("file crc mismatch");
}
}

return result;
}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
public override async System.Threading.Tasks.ValueTask<int> ReadAsync(
Memory<byte> buffer,
System.Threading.CancellationToken cancellationToken = default
)
{
var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (result != 0)
{
Update(_blake2sp, buffer.Span.Slice(0, result), result);
}
else
{
_hash = Final(_blake2sp);
if (
!disableCRCCheck
&& !(GetCrc().SequenceEqual(readStream.CurrentCrc))
&& buffer.Length != 0
)
{
// NOTE: we use the last FileHeader in a multipart volume to check CRC
throw new InvalidFormatException("file crc mismatch");
}
}

return result;
}
#endif
}
Loading
Loading