Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7c3c94e
Add ArcReaderAsync tests
adamhathcock Nov 25, 2025
3bdaba4
fmt
adamhathcock Nov 25, 2025
fb76bd8
first commit of async reader
adamhathcock Nov 26, 2025
1607d27
Merge branch 'master' into adam/async-binary-reader
adamhathcock Dec 30, 2025
8415a19
Initial plan
Copilot Dec 30, 2025
af71970
Merge branch 'adam/async-binary-reader' into copilot/add-buffered-str…
adamhathcock Dec 30, 2025
39a0b4c
Use BufferedStream for async reading in AsyncBinaryReader
Copilot Dec 30, 2025
ec31cb9
Fix Zip headers to support both sync and async reading
Copilot Dec 30, 2025
6e0e20b
Fix zip64_locator to use ReadUInt32Async instead of ReadUInt16Async
Copilot Dec 30, 2025
038b9f1
Merge remote-tracking branch 'origin/master' into copilot/add-buffere…
adamhathcock Dec 31, 2025
44b7955
reader tests
adamhathcock Dec 31, 2025
8533b09
start of implementing zip reading async
adamhathcock Dec 31, 2025
d04830b
Add some OpenAsync
adamhathcock Jan 2, 2026
ea02d31
Add IsArchiveAsync overloads for Zip and GZip factories
Copilot Jan 2, 2026
5464054
Consolidate reads
adamhathcock Jan 3, 2026
1a71c01
Consolidate ReadExact and ReadFully methods into Utility.cs
Copilot Jan 3, 2026
05642cb
Use ArrayPool for temporary buffers in BinaryReaderExtensions
Copilot Jan 3, 2026
372ecb7
Use threshold-based ArrayPool strategy for BinaryReaderExtensions
Copilot Jan 3, 2026
7701522
Add input validation for ReadBytesAsync count parameter
Copilot Jan 3, 2026
9bd86f6
Replace manual TransferTo implementation with Stream.CopyTo framework…
Copilot Jan 3, 2026
b825e15
Merge pull request #1100 from adamhathcock/copilot/fix-read-method-im…
adamhathcock Jan 5, 2026
fbce3e7
Merge branch 'master' into adam/async
adamhathcock Jan 7, 2026
39d85ff
conflicts from merge
adamhathcock Jan 7, 2026
c3fd420
Pass more Zip tests
adamhathcock Jan 7, 2026
b9792ca
fix async zip decompression
adamhathcock Jan 7, 2026
ac0716d
write testing
adamhathcock Jan 7, 2026
60d42ca
fmt
adamhathcock Jan 7, 2026
541fd13
IArchiveAsync
adamhathcock Jan 8, 2026
0f37cbf
archive async path uses new async interface
adamhathcock Jan 8, 2026
60e5220
fmt
adamhathcock Jan 8, 2026
8e42296
switch Task to ValueTask
adamhathcock Jan 8, 2026
406b198
can't dispose before returning
adamhathcock Jan 8, 2026
7aec98d
read async interface for reader
adamhathcock Jan 8, 2026
b501bac
better names for new interfaces
adamhathcock Jan 8, 2026
3747a27
Task to ValueTask
adamhathcock Jan 8, 2026
4f0a2e3
disable zip64 tests
adamhathcock Jan 8, 2026
bdcc1d3
fix scratch dir creation
adamhathcock Jan 8, 2026
01e6e04
Merge branch 'master' into adam/async
adamhathcock Jan 8, 2026
ef0b9d5
merge conflicts
adamhathcock Jan 8, 2026
ae614cd
update references
adamhathcock Jan 8, 2026
17cd934
use async methods where we can
adamhathcock Jan 8, 2026
d1fcf31
fmt
adamhathcock Jan 8, 2026
a35e65e
use ifdefs for creating files?
adamhathcock Jan 8, 2026
3fb07d1
Use async dispose always
adamhathcock Jan 12, 2026
0f37049
Initial plan
Copilot Jan 12, 2026
90c8ff8
Initial plan
Copilot Jan 12, 2026
292da90
Initial plan
Copilot Jan 12, 2026
3a63653
Initial plan
Copilot Jan 12, 2026
64a09eb
Fix typo in TestBase.cs comment: 'akways' -> 'always'
Copilot Jan 12, 2026
921cff0
Fix async test method naming: rename Sync to Async
Copilot Jan 12, 2026
f4b1780
Rename async test methods to use _Async suffix instead of _Sync
Copilot Jan 12, 2026
dadf9e7
Merge pull request #1124 from adamhathcock/copilot/sub-pr-1121
adamhathcock Jan 12, 2026
95c409d
Change File.Create to File.OpenWrite in TarReaderAsyncTests for consi…
Copilot Jan 12, 2026
c3ffcf4
Merge pull request #1125 from adamhathcock/copilot/sub-pr-1121-again
adamhathcock Jan 12, 2026
d2f328a
Merge pull request #1126 from adamhathcock/copilot/sub-pr-1121-anothe…
adamhathcock Jan 12, 2026
3807c3c
Merge pull request #1127 from adamhathcock/copilot/sub-pr-1121-yet-again
adamhathcock Jan 12, 2026
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,9 @@ dotnet_diagnostic.NX0001.severity = error
dotnet_diagnostic.NX0002.severity = silent
dotnet_diagnostic.NX0003.severity = silent

dotnet_diagnostic.VSTHRD110.severity = error
dotnet_diagnostic.VSTHRD107.severity = error

##########################################
# Styles
##########################################
Expand Down
9 changes: 6 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="10.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<GlobalPackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
<GlobalPackageReference
Include="Microsoft.VisualStudio.Threading.Analyzers"
Version="17.14.15"
/>
</ItemGroup>
</Project>
40 changes: 40 additions & 0 deletions build/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,51 @@
"resolved": "1.1.9",
"contentHash": "AfK5+ECWYTP7G3AAdnU8IfVj+QpGjrh9GC2mpdcJzCvtQ4pnerAGwHsxJ9D4/RnhDUz2DSzd951O/lQjQby2Sw=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Direct",
"requested": "[17.14.15, )",
"resolved": "17.14.15",
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
},
"SimpleExec": {
"type": "Direct",
"requested": "[13.0.0, )",
"resolved": "13.0.0",
"contentHash": "zcCR1pupa1wI1VqBULRiQKeHKKZOuJhi/K+4V5oO+rHJZlaOD53ViFo1c3PavDoMAfSn/FAXGAWpPoF57rwhYg=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
}
}
}
Expand Down
103 changes: 91 additions & 12 deletions src/SharpCompress/Archives/AbstractArchive.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;

namespace SharpCompress.Archives;

public abstract class AbstractArchive<TEntry, TVolume> : IArchive
public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IAsyncArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
{
Expand All @@ -26,6 +25,12 @@ internal AbstractArchive(ArchiveType type, SourceStream sourceStream)
_sourceStream = sourceStream;
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(_sourceStream));
_lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(
LoadVolumesAsync(_sourceStream)
);
_lazyEntriesAsync = new LazyAsyncReadOnlyCollection<TEntry>(
LoadEntriesAsync(_lazyVolumesAsync)
);
}

internal AbstractArchive(ArchiveType type)
Expand All @@ -34,19 +39,16 @@ internal AbstractArchive(ArchiveType type)
ReaderOptions = new();
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
_lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(
AsyncEnumerableEx.Empty<TVolume>()
);
_lazyEntriesAsync = new LazyAsyncReadOnlyCollection<TEntry>(
AsyncEnumerableEx.Empty<TEntry>()
);
}

public ArchiveType Type { get; }

private static Stream CheckStreams(Stream stream)
{
if (!stream.CanSeek || !stream.CanRead)
{
throw new ArchiveException("Archive streams must be Readable and Seekable");
}
return stream;
}

/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
/// </summary>
Expand All @@ -72,6 +74,19 @@ private static Stream CheckStreams(Stream stream)
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream sourceStream);
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);

protected virtual IAsyncEnumerable<TVolume> LoadVolumesAsync(SourceStream sourceStream) =>
LoadVolumes(sourceStream).ToAsyncEnumerable();

protected virtual async IAsyncEnumerable<TEntry> LoadEntriesAsync(
IAsyncEnumerable<TVolume> volumes
)
{
foreach (var item in LoadEntries(await volumes.ToListAsync()))
{
yield return item;
}
}

IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();

IEnumerable<IVolume> IArchive.Volumes => _lazyVolumes.Cast<IVolume>();
Expand Down Expand Up @@ -118,6 +133,7 @@ public IReader ExtractAllEntries()
}

protected abstract IReader CreateReaderForSolidExtraction();
protected abstract ValueTask<IAsyncReader> CreateReaderForSolidExtractionAsync();

/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
Expand All @@ -140,4 +156,67 @@ public bool IsComplete
return Entries.All(x => x.IsComplete);
}
}

#region Async Support

private readonly LazyAsyncReadOnlyCollection<TVolume> _lazyVolumesAsync;
private readonly LazyAsyncReadOnlyCollection<TEntry> _lazyEntriesAsync;

public virtual async ValueTask DisposeAsync()
{
if (!_disposed)
{
await foreach (var v in _lazyVolumesAsync)
{
v.Dispose();
}
foreach (var v in _lazyEntriesAsync.GetLoaded().Cast<Entry>())
{
v.Close();
}
_sourceStream?.Dispose();

_disposed = true;
}
}

private async ValueTask EnsureEntriesLoadedAsync()
{
await _lazyEntriesAsync.EnsureFullyLoaded();
await _lazyVolumesAsync.EnsureFullyLoaded();
}

public virtual IAsyncEnumerable<TEntry> EntriesAsync => _lazyEntriesAsync;
IAsyncEnumerable<IArchiveEntry> IAsyncArchive.EntriesAsync =>
EntriesAsync.Cast<TEntry, IArchiveEntry>();

public IAsyncEnumerable<IVolume> VolumesAsync => _lazyVolumesAsync.Cast<TVolume, IVolume>();

public async ValueTask<IAsyncReader> ExtractAllEntriesAsync()
{
if (!IsSolid && Type != ArchiveType.SevenZip)
{
throw new SharpCompressException(
"ExtractAllEntries can only be used on solid archives or 7Zip archives (which require random access)."
);
}
await EnsureEntriesLoadedAsync();
return await CreateReaderForSolidExtractionAsync();
}

public virtual ValueTask<bool> IsSolidAsync() => new(false);

public async ValueTask<bool> IsCompleteAsync()
{
await EnsureEntriesLoadedAsync();
return await EntriesAsync.All(x => x.IsComplete);
}

public async ValueTask<long> TotalSizeAsync() =>
await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.CompressedSize);

public async ValueTask<long> TotalUncompressSizeAsync() =>
await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.Size);

#endregion
}
4 changes: 2 additions & 2 deletions src/SharpCompress/Archives/AbstractWritableArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public void SaveTo(Stream stream, WriterOptions options)
SaveTo(stream, options, OldEntries, newEntries);
}

public async Task SaveToAsync(
public async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
CancellationToken cancellationToken = default
Expand Down Expand Up @@ -208,7 +208,7 @@ protected abstract void SaveTo(
IEnumerable<TEntry> newEntries
);

protected abstract Task SaveToAsync(
protected abstract ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
IEnumerable<TEntry> oldEntries,
Expand Down
Loading