diff --git a/.editorconfig b/.editorconfig
index eab3d4286..96f2a953b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -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
##########################################
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 703b2803c..9f2e0c864 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -13,8 +13,11 @@
-
-
-
+
+
+
diff --git a/build/packages.lock.json b/build/packages.lock.json
index 9a72d50dc..ddbe3e974 100644
--- a/build/packages.lock.json
+++ b/build/packages.lock.json
@@ -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=="
}
}
}
diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs
index 672382ffc..12ab15a6b 100644
--- a/src/SharpCompress/Archives/AbstractArchive.cs
+++ b/src/SharpCompress/Archives/AbstractArchive.cs
@@ -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 : IArchive
+public abstract class AbstractArchive : IArchive, IAsyncArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
{
@@ -26,6 +25,12 @@ internal AbstractArchive(ArchiveType type, SourceStream sourceStream)
_sourceStream = sourceStream;
_lazyVolumes = new LazyReadOnlyCollection(LoadVolumes(_sourceStream));
_lazyEntries = new LazyReadOnlyCollection(LoadEntries(Volumes));
+ _lazyVolumesAsync = new LazyAsyncReadOnlyCollection(
+ LoadVolumesAsync(_sourceStream)
+ );
+ _lazyEntriesAsync = new LazyAsyncReadOnlyCollection(
+ LoadEntriesAsync(_lazyVolumesAsync)
+ );
}
internal AbstractArchive(ArchiveType type)
@@ -34,19 +39,16 @@ internal AbstractArchive(ArchiveType type)
ReaderOptions = new();
_lazyVolumes = new LazyReadOnlyCollection(Enumerable.Empty());
_lazyEntries = new LazyReadOnlyCollection(Enumerable.Empty());
+ _lazyVolumesAsync = new LazyAsyncReadOnlyCollection(
+ AsyncEnumerableEx.Empty()
+ );
+ _lazyEntriesAsync = new LazyAsyncReadOnlyCollection(
+ AsyncEnumerableEx.Empty()
+ );
}
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;
- }
-
///
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
///
@@ -72,6 +74,19 @@ private static Stream CheckStreams(Stream stream)
protected abstract IEnumerable LoadVolumes(SourceStream sourceStream);
protected abstract IEnumerable LoadEntries(IEnumerable volumes);
+ protected virtual IAsyncEnumerable LoadVolumesAsync(SourceStream sourceStream) =>
+ LoadVolumes(sourceStream).ToAsyncEnumerable();
+
+ protected virtual async IAsyncEnumerable LoadEntriesAsync(
+ IAsyncEnumerable volumes
+ )
+ {
+ foreach (var item in LoadEntries(await volumes.ToListAsync()))
+ {
+ yield return item;
+ }
+ }
+
IEnumerable IArchive.Entries => Entries.Cast();
IEnumerable IArchive.Volumes => _lazyVolumes.Cast();
@@ -118,6 +133,7 @@ public IReader ExtractAllEntries()
}
protected abstract IReader CreateReaderForSolidExtraction();
+ protected abstract ValueTask CreateReaderForSolidExtractionAsync();
///
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
@@ -140,4 +156,67 @@ public bool IsComplete
return Entries.All(x => x.IsComplete);
}
}
+
+ #region Async Support
+
+ private readonly LazyAsyncReadOnlyCollection _lazyVolumesAsync;
+ private readonly LazyAsyncReadOnlyCollection _lazyEntriesAsync;
+
+ public virtual async ValueTask DisposeAsync()
+ {
+ if (!_disposed)
+ {
+ await foreach (var v in _lazyVolumesAsync)
+ {
+ v.Dispose();
+ }
+ foreach (var v in _lazyEntriesAsync.GetLoaded().Cast())
+ {
+ v.Close();
+ }
+ _sourceStream?.Dispose();
+
+ _disposed = true;
+ }
+ }
+
+ private async ValueTask EnsureEntriesLoadedAsync()
+ {
+ await _lazyEntriesAsync.EnsureFullyLoaded();
+ await _lazyVolumesAsync.EnsureFullyLoaded();
+ }
+
+ public virtual IAsyncEnumerable EntriesAsync => _lazyEntriesAsync;
+ IAsyncEnumerable IAsyncArchive.EntriesAsync =>
+ EntriesAsync.Cast();
+
+ public IAsyncEnumerable VolumesAsync => _lazyVolumesAsync.Cast();
+
+ public async ValueTask 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 IsSolidAsync() => new(false);
+
+ public async ValueTask IsCompleteAsync()
+ {
+ await EnsureEntriesLoadedAsync();
+ return await EntriesAsync.All(x => x.IsComplete);
+ }
+
+ public async ValueTask TotalSizeAsync() =>
+ await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.CompressedSize);
+
+ public async ValueTask TotalUncompressSizeAsync() =>
+ await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.Size);
+
+ #endregion
}
diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.cs b/src/SharpCompress/Archives/AbstractWritableArchive.cs
index 744d4ee2a..13fb66f9a 100644
--- a/src/SharpCompress/Archives/AbstractWritableArchive.cs
+++ b/src/SharpCompress/Archives/AbstractWritableArchive.cs
@@ -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
@@ -208,7 +208,7 @@ protected abstract void SaveTo(
IEnumerable newEntries
);
- protected abstract Task SaveToAsync(
+ protected abstract ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
IEnumerable oldEntries,
diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs
index 94368ece5..e052c3e80 100644
--- a/src/SharpCompress/Archives/ArchiveFactory.cs
+++ b/src/SharpCompress/Archives/ArchiveFactory.cs
@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.IO;
@@ -24,6 +26,28 @@ public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
return FindFactory(stream).Open(stream, readerOptions);
}
+ ///
+ /// Opens an Archive for random access asynchronously
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ readerOptions ??= new ReaderOptions();
+ stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize);
+ var factory = await FindFactoryAsync(stream, cancellationToken)
+ .ConfigureAwait(false);
+ return await factory
+ .OpenAsync(stream, readerOptions, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
public static IWritableArchive Create(ArchiveType type)
{
var factory = Factory
@@ -49,6 +73,22 @@ public static IArchive Open(string filePath, ReaderOptions? options = null)
return Open(new FileInfo(filePath), options);
}
+ ///
+ /// Opens an Archive from a filepath asynchronously.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ string filePath,
+ ReaderOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ filePath.NotNullOrEmpty(nameof(filePath));
+ return OpenAsync(new FileInfo(filePath), options, cancellationToken);
+ }
+
///
/// Constructor with a FileInfo object to an existing file.
///
@@ -61,6 +101,25 @@ public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
return FindFactory(fileInfo).Open(fileInfo, options);
}
+ ///
+ /// Opens an Archive from a FileInfo object asynchronously.
+ ///
+ ///
+ ///
+ ///
+ public static async ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ options ??= new ReaderOptions { LeaveStreamOpen = false };
+
+ var factory = await FindFactoryAsync(fileInfo, cancellationToken)
+ .ConfigureAwait(false);
+ return await factory.OpenAsync(fileInfo, options, cancellationToken).ConfigureAwait(false);
+ }
+
///
/// Constructor with IEnumerable FileInfo objects, multi and split support.
///
@@ -87,6 +146,40 @@ public static IArchive Open(IEnumerable fileInfos, ReaderOptions? opti
return FindFactory(fileInfo).Open(filesArray, options);
}
+ ///
+ /// Opens a multi-part archive from files asynchronously.
+ ///
+ ///
+ ///
+ ///
+ public static async ValueTask OpenAsync(
+ IEnumerable fileInfos,
+ ReaderOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ fileInfos.NotNull(nameof(fileInfos));
+ var filesArray = fileInfos.ToArray();
+ if (filesArray.Length == 0)
+ {
+ throw new InvalidOperationException("No files to open");
+ }
+
+ var fileInfo = filesArray[0];
+ if (filesArray.Length == 1)
+ {
+ return await OpenAsync(fileInfo, options, cancellationToken).ConfigureAwait(false);
+ }
+
+ fileInfo.NotNull(nameof(fileInfo));
+ options ??= new ReaderOptions { LeaveStreamOpen = false };
+
+ var factory = FindFactory(fileInfo);
+ return await factory
+ .OpenAsync(filesArray, options, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
///
/// Constructor with IEnumerable FileInfo objects, multi and split support.
///
@@ -113,6 +206,41 @@ public static IArchive Open(IEnumerable streams, ReaderOptions? options
return FindFactory(firstStream).Open(streamsArray, options);
}
+ ///
+ /// Opens a multi-part archive from streams asynchronously.
+ ///
+ ///
+ ///
+ ///
+ public static async ValueTask OpenAsync(
+ IEnumerable streams,
+ ReaderOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ streams.NotNull(nameof(streams));
+ var streamsArray = streams.ToArray();
+ if (streamsArray.Length == 0)
+ {
+ throw new InvalidOperationException("No streams");
+ }
+
+ var firstStream = streamsArray[0];
+ if (streamsArray.Length == 1)
+ {
+ return await OpenAsync(firstStream, options, cancellationToken).ConfigureAwait(false);
+ }
+
+ firstStream.NotNull(nameof(firstStream));
+ options ??= new ReaderOptions();
+
+ var factory = FindFactory(firstStream);
+ return await factory
+ .OpenAsync(streamsArray, options, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
///
/// Extract to specific directory, retaining filename
///
@@ -166,6 +294,52 @@ private static T FindFactory(Stream stream)
);
}
+ private static async ValueTask FindFactoryAsync(
+ FileInfo finfo,
+ CancellationToken cancellationToken
+ )
+ where T : IFactory
+ {
+ finfo.NotNull(nameof(finfo));
+ using Stream stream = finfo.OpenRead();
+ return await FindFactoryAsync(stream, cancellationToken);
+ }
+
+ private static async ValueTask FindFactoryAsync(
+ Stream stream,
+ CancellationToken cancellationToken
+ )
+ where T : IFactory
+ {
+ stream.NotNull(nameof(stream));
+ if (!stream.CanRead || !stream.CanSeek)
+ {
+ throw new ArgumentException("Stream should be readable and seekable");
+ }
+
+ var factories = Factory.Factories.OfType();
+
+ var startPosition = stream.Position;
+
+ foreach (var factory in factories)
+ {
+ stream.Seek(startPosition, SeekOrigin.Begin);
+
+ if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken))
+ {
+ stream.Seek(startPosition, SeekOrigin.Begin);
+
+ return factory;
+ }
+ }
+
+ var extensions = string.Join(", ", factories.Select(item => item.Name));
+
+ throw new InvalidOperationException(
+ $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
+ );
+ }
+
public static bool IsArchive(
string filePath,
out ArchiveType? type,
diff --git a/src/SharpCompress/Archives/AutoArchiveFactory.cs b/src/SharpCompress/Archives/AutoArchiveFactory.cs
index 78313df54..7751c7e01 100644
--- a/src/SharpCompress/Archives/AutoArchiveFactory.cs
+++ b/src/SharpCompress/Archives/AutoArchiveFactory.cs
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
-class AutoArchiveFactory : IArchiveFactory
+internal class AutoArchiveFactory : IArchiveFactory
{
public string Name => nameof(AutoArchiveFactory);
@@ -20,11 +22,30 @@ public bool IsArchive(
int bufferSize = ReaderOptions.DefaultBufferSize
) => throw new NotSupportedException();
+ public ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize,
+ CancellationToken cancellationToken = default
+ ) => throw new NotSupportedException();
+
public FileInfo? GetFilePart(int index, FileInfo part1) => throw new NotSupportedException();
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
ArchiveFactory.Open(stream, readerOptions);
+ public async ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => await ArchiveFactory.OpenAsync(stream, readerOptions, cancellationToken);
+
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
ArchiveFactory.Open(fileInfo, readerOptions);
+
+ public async ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => await ArchiveFactory.OpenAsync(fileInfo, readerOptions, cancellationToken);
}
diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs
index 34e4c6484..7c8c08f7c 100644
--- a/src/SharpCompress/Archives/GZip/GZipArchive.cs
+++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs
@@ -102,6 +102,70 @@ public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = nul
);
}
+ ///
+ /// Opens a GZipArchive asynchronously from a stream.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(stream, readerOptions));
+ }
+
+ ///
+ /// Opens a GZipArchive asynchronously from a FileInfo.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfo, readerOptions));
+ }
+
+ ///
+ /// Opens a GZipArchive asynchronously from multiple streams.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(streams, readerOptions));
+ }
+
+ ///
+ /// Opens a GZipArchive asynchronously from multiple FileInfo objects.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfos, readerOptions));
+ }
+
public static GZipArchive Create() => new();
///
@@ -138,10 +202,13 @@ public void SaveTo(FileInfo fileInfo)
SaveTo(stream, new WriterOptions(CompressionType.GZip));
}
- public Task SaveToAsync(string filePath, CancellationToken cancellationToken = default) =>
+ public ValueTask SaveToAsync(string filePath, CancellationToken cancellationToken = default) =>
SaveToAsync(new FileInfo(filePath), cancellationToken);
- public async Task SaveToAsync(FileInfo fileInfo, CancellationToken cancellationToken = default)
+ public async ValueTask SaveToAsync(
+ FileInfo fileInfo,
+ CancellationToken cancellationToken = default
+ )
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await SaveToAsync(stream, new WriterOptions(CompressionType.GZip), cancellationToken)
@@ -167,6 +234,28 @@ public static bool IsGZipFile(Stream stream)
return true;
}
+ public static async ValueTask IsGZipFileAsync(
+ Stream stream,
+ CancellationToken cancellationToken = default
+ )
+ {
+ // read the header on the first read
+ byte[] header = new byte[10];
+
+ // workitem 8501: handle edge case (decompress empty stream)
+ if (!await stream.ReadFullyAsync(header, cancellationToken).ConfigureAwait(false))
+ {
+ return false;
+ }
+
+ if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
internal GZipArchive()
: base(ArchiveType.GZip) { }
@@ -213,7 +302,7 @@ IEnumerable newEntries
}
}
- protected override async Task SaveToAsync(
+ protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
IEnumerable oldEntries,
@@ -250,4 +339,11 @@ protected override IReader CreateReaderForSolidExtraction()
stream.Position = 0;
return GZipReader.Open(stream);
}
+
+ protected override ValueTask CreateReaderForSolidExtractionAsync()
+ {
+ var stream = Volumes.Single().Stream;
+ stream.Position = 0;
+ return new(GZipReader.Open(stream));
+ }
}
diff --git a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs
index 62e4760b3..049c7262a 100644
--- a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs
+++ b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs
@@ -23,10 +23,12 @@ public virtual Stream OpenEntryStream()
return Parts.Single().GetCompressedStream().NotNull();
}
- public virtual Task OpenEntryStreamAsync(CancellationToken cancellationToken = default)
+ public async ValueTask OpenEntryStreamAsync(
+ CancellationToken cancellationToken = default
+ )
{
// GZip synchronous implementation is fast enough, just wrap it
- return Task.FromResult(OpenEntryStream());
+ return OpenEntryStream();
}
#region IArchiveEntry Members
diff --git a/src/SharpCompress/Archives/IArchiveEntry.cs b/src/SharpCompress/Archives/IArchiveEntry.cs
index 69b3a674e..a38e65a0c 100644
--- a/src/SharpCompress/Archives/IArchiveEntry.cs
+++ b/src/SharpCompress/Archives/IArchiveEntry.cs
@@ -17,7 +17,7 @@ public interface IArchiveEntry : IEntry
/// Opens the current entry as a stream that will decompress as it is read asynchronously.
/// Read the entire stream or use SkipEntry on EntryStream.
///
- Task OpenEntryStreamAsync(CancellationToken cancellationToken = default);
+ ValueTask OpenEntryStreamAsync(CancellationToken cancellationToken = default);
///
/// The archive can find all the parts of the archive needed to extract this entry.
diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
index af2c9be45..3bf940351 100644
--- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
+++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs
@@ -37,7 +37,7 @@ public void WriteTo(Stream streamToWriteTo, IProgress? progress
/// The stream to write the entry content to.
/// Cancellation token.
/// Optional progress reporter for tracking extraction progress.
- public async Task WriteToAsync(
+ public async ValueTask WriteToAsync(
Stream streamToWriteTo,
IProgress? progress = null,
CancellationToken cancellationToken = default
@@ -110,18 +110,20 @@ public void WriteToDirectory(
///
/// Extract to specific directory asynchronously, retaining filename
///
- public Task WriteToDirectoryAsync(
+ public async ValueTask WriteToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
- ExtractionMethods.WriteEntryToDirectoryAsync(
- entry,
- destinationDirectory,
- options,
- entry.WriteToFileAsync,
- cancellationToken
- );
+ await ExtractionMethods
+ .WriteEntryToDirectoryAsync(
+ entry,
+ destinationDirectory,
+ options,
+ entry.WriteToFileAsync,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
///
/// Extract to specific file
@@ -141,21 +143,23 @@ public void WriteToFile(string destinationFileName, ExtractionOptions? options =
///
/// Extract to specific file asynchronously
///
- public Task WriteToFileAsync(
+ public async ValueTask WriteToFileAsync(
string destinationFileName,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
- ExtractionMethods.WriteEntryToFileAsync(
- entry,
- destinationFileName,
- options,
- async (x, fm, ct) =>
- {
- using var fs = File.Open(destinationFileName, fm);
- await entry.WriteToAsync(fs, null, ct).ConfigureAwait(false);
- },
- cancellationToken
- );
+ await ExtractionMethods
+ .WriteEntryToFileAsync(
+ entry,
+ destinationFileName,
+ options,
+ async (x, fm, ct) =>
+ {
+ using var fs = File.Open(destinationFileName, fm);
+ await entry.WriteToAsync(fs, null, ct).ConfigureAwait(false);
+ },
+ cancellationToken
+ )
+ .ConfigureAwait(false);
}
}
diff --git a/src/SharpCompress/Archives/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs
index 0d39c6e2c..c1d2ac987 100644
--- a/src/SharpCompress/Archives/IArchiveExtensions.cs
+++ b/src/SharpCompress/Archives/IArchiveExtensions.cs
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Readers;
@@ -80,89 +78,5 @@ private void WriteToDirectoryInternal(
);
}
}
-
- ///
- /// Extract to specific directory asynchronously with progress reporting and cancellation support
- ///
- /// The folder to extract into.
- /// Extraction options.
- /// Optional progress reporter for tracking extraction progress.
- /// Optional cancellation token.
- public async Task WriteToDirectoryAsync(
- string destinationDirectory,
- ExtractionOptions? options = null,
- IProgress? progress = null,
- CancellationToken cancellationToken = default
- )
- {
- // For solid archives (Rar, 7Zip), use the optimized reader-based approach
- if (archive.IsSolid || archive.Type == ArchiveType.SevenZip)
- {
- using var reader = archive.ExtractAllEntries();
- await reader.WriteAllToDirectoryAsync(
- destinationDirectory,
- options,
- cancellationToken
- );
- }
- else
- {
- // For non-solid archives, extract entries directly
- await archive.WriteToDirectoryAsyncInternal(
- destinationDirectory,
- options,
- progress,
- cancellationToken
- );
- }
- }
-
- private async Task WriteToDirectoryAsyncInternal(
- string destinationDirectory,
- ExtractionOptions? options,
- IProgress? progress,
- CancellationToken cancellationToken
- )
- {
- // Prepare for progress reporting
- var totalBytes = archive.TotalUncompressSize;
- var bytesRead = 0L;
-
- // Tracking for created directories.
- var seenDirectories = new HashSet();
-
- // Extract
- foreach (var entry in archive.Entries)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (entry.IsDirectory)
- {
- var dirPath = Path.Combine(
- destinationDirectory,
- entry.Key.NotNull("Entry Key is null")
- );
- if (
- Path.GetDirectoryName(dirPath + "/") is { } parentDirectory
- && seenDirectories.Add(dirPath)
- )
- {
- Directory.CreateDirectory(parentDirectory);
- }
- continue;
- }
-
- // Use the entry's WriteToDirectoryAsync method which respects ExtractionOptions
- await entry
- .WriteToDirectoryAsync(destinationDirectory, options, cancellationToken)
- .ConfigureAwait(false);
-
- // Update progress
- bytesRead += entry.Size;
- progress?.Report(
- new ProgressReport(entry.Key ?? string.Empty, bytesRead, totalBytes)
- );
- }
- }
}
}
diff --git a/src/SharpCompress/Archives/IArchiveFactory.cs b/src/SharpCompress/Archives/IArchiveFactory.cs
index 370e5c9fe..1c1253f6a 100644
--- a/src/SharpCompress/Archives/IArchiveFactory.cs
+++ b/src/SharpCompress/Archives/IArchiveFactory.cs
@@ -1,4 +1,6 @@
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Factories;
using SharpCompress.Readers;
@@ -26,10 +28,34 @@ public interface IArchiveFactory : IFactory
/// reading options.
IArchive Open(Stream stream, ReaderOptions? readerOptions = null);
+ ///
+ /// Opens an Archive for random access asynchronously.
+ ///
+ /// An open, readable and seekable stream.
+ /// reading options.
+ /// Cancellation token.
+ ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ );
+
///
/// Constructor with a FileInfo object to an existing file.
///
/// the file to open.
/// reading options.
IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null);
+
+ ///
+ /// Opens an Archive from a FileInfo object asynchronously.
+ ///
+ /// the file to open.
+ /// reading options.
+ /// Cancellation token.
+ ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ );
}
diff --git a/src/SharpCompress/Archives/IAsyncArchive.cs b/src/SharpCompress/Archives/IAsyncArchive.cs
new file mode 100644
index 000000000..bd3f290ea
--- /dev/null
+++ b/src/SharpCompress/Archives/IAsyncArchive.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using SharpCompress.Common;
+using SharpCompress.Readers;
+
+namespace SharpCompress.Archives;
+
+public interface IAsyncArchive : IAsyncDisposable
+{
+ IAsyncEnumerable EntriesAsync { get; }
+ IAsyncEnumerable VolumesAsync { get; }
+
+ ArchiveType Type { get; }
+
+ ///
+ /// Use this method to extract all entries in an archive in order.
+ /// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
+ /// extracted sequentially for the best performance.
+ ///
+ ValueTask ExtractAllEntriesAsync();
+
+ ///
+ /// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
+ /// Rar Archives can be SOLID while all 7Zip archives are considered SOLID.
+ ///
+ ValueTask IsSolidAsync();
+
+ ///
+ /// This checks to see if all the known entries have IsComplete = true
+ ///
+ ValueTask IsCompleteAsync();
+
+ ///
+ /// The total size of the files compressed in the archive.
+ ///
+ ValueTask TotalSizeAsync();
+
+ ///
+ /// The total size of the files as uncompressed in the archive.
+ ///
+ ValueTask TotalUncompressSizeAsync();
+}
diff --git a/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs b/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs
new file mode 100644
index 000000000..b6b0cad1e
--- /dev/null
+++ b/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using SharpCompress.Common;
+using SharpCompress.Readers;
+
+namespace SharpCompress.Archives;
+
+public static class IAsyncArchiveExtensions
+{
+ ///
+ /// Extract to specific directory asynchronously with progress reporting and cancellation support
+ ///
+ /// The archive to extract.
+ /// The folder to extract into.
+ /// Extraction options.
+ /// Optional progress reporter for tracking extraction progress.
+ /// Optional cancellation token.
+ public static async Task WriteToDirectoryAsync(
+ this IAsyncArchive archive,
+ string destinationDirectory,
+ ExtractionOptions? options = null,
+ IProgress? progress = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ // For solid archives (Rar, 7Zip), use the optimized reader-based approach
+ if (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip)
+ {
+ await using var reader = await archive.ExtractAllEntriesAsync();
+ await reader.WriteAllToDirectoryAsync(destinationDirectory, options, cancellationToken);
+ }
+ else
+ {
+ // For non-solid archives, extract entries directly
+ await archive.WriteToDirectoryAsyncInternal(
+ destinationDirectory,
+ options,
+ progress,
+ cancellationToken
+ );
+ }
+ }
+
+ private static async Task WriteToDirectoryAsyncInternal(
+ this IAsyncArchive archive,
+ string destinationDirectory,
+ ExtractionOptions? options,
+ IProgress? progress,
+ CancellationToken cancellationToken
+ )
+ {
+ // Prepare for progress reporting
+ var totalBytes = await archive.TotalUncompressSizeAsync();
+ var bytesRead = 0L;
+
+ // Tracking for created directories.
+ var seenDirectories = new HashSet();
+
+ // Extract
+ await foreach (var entry in archive.EntriesAsync.WithCancellation(cancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (entry.IsDirectory)
+ {
+ var dirPath = Path.Combine(
+ destinationDirectory,
+ entry.Key.NotNull("Entry Key is null")
+ );
+ if (
+ Path.GetDirectoryName(dirPath + "/") is { } parentDirectory
+ && seenDirectories.Add(dirPath)
+ )
+ {
+ Directory.CreateDirectory(parentDirectory);
+ }
+ continue;
+ }
+
+ // Use the entry's WriteToDirectoryAsync method which respects ExtractionOptions
+ await entry
+ .WriteToDirectoryAsync(destinationDirectory, options, cancellationToken)
+ .ConfigureAwait(false);
+
+ // Update progress
+ bytesRead += entry.Size;
+ progress?.Report(new ProgressReport(entry.Key ?? string.Empty, bytesRead, totalBytes));
+ }
+ }
+}
diff --git a/src/SharpCompress/Archives/IMultiArchiveFactory.cs b/src/SharpCompress/Archives/IMultiArchiveFactory.cs
index c26b649f1..4fa94d7fe 100644
--- a/src/SharpCompress/Archives/IMultiArchiveFactory.cs
+++ b/src/SharpCompress/Archives/IMultiArchiveFactory.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Factories;
using SharpCompress.Readers;
@@ -27,10 +29,34 @@ public interface IMultiArchiveFactory : IFactory
/// reading options.
IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null);
+ ///
+ /// Opens a multi-part archive from streams asynchronously.
+ ///
+ ///
+ /// reading options.
+ /// Cancellation token.
+ ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ );
+
///
/// Constructor with IEnumerable Stream objects, multi and split support.
///
///
/// reading options.
IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null);
+
+ ///
+ /// Opens a multi-part archive from files asynchronously.
+ ///
+ ///
+ /// reading options.
+ /// Cancellation token.
+ ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ );
}
diff --git a/src/SharpCompress/Archives/IWritableArchive.cs b/src/SharpCompress/Archives/IWritableArchive.cs
index dde22a032..74d8da763 100644
--- a/src/SharpCompress/Archives/IWritableArchive.cs
+++ b/src/SharpCompress/Archives/IWritableArchive.cs
@@ -22,7 +22,7 @@ IArchiveEntry AddEntry(
void SaveTo(Stream stream, WriterOptions options);
- Task SaveToAsync(
+ ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
CancellationToken cancellationToken = default
diff --git a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs
index 4defe6049..60ec83d85 100644
--- a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs
+++ b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs
@@ -44,14 +44,14 @@ WriterOptions options
writableArchive.SaveTo(stream, options);
}
- public static Task SaveToAsync(
+ public static ValueTask SaveToAsync(
this IWritableArchive writableArchive,
string filePath,
WriterOptions options,
CancellationToken cancellationToken = default
) => writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken);
- public static async Task SaveToAsync(
+ public static async ValueTask SaveToAsync(
this IWritableArchive writableArchive,
FileInfo fileInfo,
WriterOptions options,
diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs
index 9acfdccc5..03b8d4d90 100644
--- a/src/SharpCompress/Archives/Rar/RarArchive.cs
+++ b/src/SharpCompress/Archives/Rar/RarArchive.cs
@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Rar;
using SharpCompress.Common.Rar.Headers;
@@ -65,7 +67,13 @@ protected override IEnumerable LoadVolumes(SourceStream sourceStream)
return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable();
}
- protected override IReader CreateReaderForSolidExtraction()
+ protected override IReader CreateReaderForSolidExtraction() =>
+ CreateReaderForSolidExtractionInternal();
+
+ protected override ValueTask CreateReaderForSolidExtractionAsync() =>
+ new(CreateReaderForSolidExtractionInternal());
+
+ private RarReader CreateReaderForSolidExtractionInternal()
{
if (this.IsMultipartVolume())
{
@@ -181,6 +189,70 @@ public static RarArchive Open(IEnumerable streams, ReaderOptions? reader
);
}
+ ///
+ /// Opens a RarArchive asynchronously from a stream.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(stream, readerOptions));
+ }
+
+ ///
+ /// Opens a RarArchive asynchronously from a FileInfo.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfo, readerOptions));
+ }
+
+ ///
+ /// Opens a RarArchive asynchronously from multiple streams.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(streams, readerOptions));
+ }
+
+ ///
+ /// Opens a RarArchive asynchronously from multiple FileInfo objects.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfos, readerOptions));
+ }
+
public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath));
public static bool IsRarFile(FileInfo fileInfo)
diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs
index 69c54f310..0fe259cca 100644
--- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs
+++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs
@@ -92,7 +92,9 @@ public Stream OpenEntryStream()
return stream;
}
- public async Task OpenEntryStreamAsync(CancellationToken cancellationToken = default)
+ public async ValueTask OpenEntryStreamAsync(
+ CancellationToken cancellationToken = default
+ )
{
RarStream stream;
if (IsRarV3)
diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs
index d9b5ba1aa..43f49abed 100644
--- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs
+++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs
@@ -105,6 +105,70 @@ public static SevenZipArchive Open(Stream stream, ReaderOptions? readerOptions =
);
}
+ ///
+ /// Opens a SevenZipArchive asynchronously from a stream.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(stream, readerOptions));
+ }
+
+ ///
+ /// Opens a SevenZipArchive asynchronously from a FileInfo.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfo, readerOptions));
+ }
+
+ ///
+ /// Opens a SevenZipArchive asynchronously from multiple streams.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(streams, readerOptions));
+ }
+
+ ///
+ /// Opens a SevenZipArchive asynchronously from multiple FileInfo objects.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfos, readerOptions));
+ }
+
///
/// Constructor with a SourceStream able to handle FileInfo and Streams.
///
@@ -201,6 +265,9 @@ private static bool SignatureMatch(Stream stream)
protected override IReader CreateReaderForSolidExtraction() =>
new SevenZipReader(ReaderOptions, this);
+ protected override ValueTask CreateReaderForSolidExtractionAsync() =>
+ new(new SevenZipReader(ReaderOptions, this));
+
public override bool IsSolid =>
Entries
.Where(x => !x.IsDirectory)
diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs
index 754c8c637..a0d4a50d8 100644
--- a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs
+++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs
@@ -12,8 +12,9 @@ internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part)
public Stream OpenEntryStream() => FilePart.GetCompressedStream();
- public Task OpenEntryStreamAsync(CancellationToken cancellationToken = default) =>
- Task.FromResult(OpenEntryStream());
+ public async ValueTask OpenEntryStreamAsync(
+ CancellationToken cancellationToken = default
+ ) => OpenEntryStream();
public IArchive Archive { get; }
diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs
index 2754fd9bb..1aeaf9a7a 100644
--- a/src/SharpCompress/Archives/Tar/TarArchive.cs
+++ b/src/SharpCompress/Archives/Tar/TarArchive.cs
@@ -103,6 +103,70 @@ public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null
);
}
+ ///
+ /// Opens a TarArchive asynchronously from a stream.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(stream, readerOptions));
+ }
+
+ ///
+ /// Opens a TarArchive asynchronously from a FileInfo.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfo, readerOptions));
+ }
+
+ ///
+ /// Opens a TarArchive asynchronously from multiple streams.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(streams, readerOptions));
+ }
+
+ ///
+ /// Opens a TarArchive asynchronously from multiple FileInfo objects.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfos, readerOptions));
+ }
+
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
public static bool IsTarFile(FileInfo fileInfo)
@@ -259,7 +323,7 @@ IEnumerable newEntries
}
}
- protected override async Task SaveToAsync(
+ protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
IEnumerable oldEntries,
@@ -302,4 +366,11 @@ protected override IReader CreateReaderForSolidExtraction()
stream.Position = 0;
return TarReader.Open(stream);
}
+
+ protected override ValueTask CreateReaderForSolidExtractionAsync()
+ {
+ var stream = Volumes.Single().Stream;
+ stream.Position = 0;
+ return new(TarReader.Open(stream));
+ }
}
diff --git a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
index 8c0827917..cbea2c717 100644
--- a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
+++ b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs
@@ -14,9 +14,9 @@ internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
- public virtual Task OpenEntryStreamAsync(
+ public async ValueTask OpenEntryStreamAsync(
CancellationToken cancellationToken = default
- ) => Task.FromResult(OpenEntryStream());
+ ) => OpenEntryStream();
#region IArchiveEntry Members
diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs
index 57db85c2a..756bc8863 100644
--- a/src/SharpCompress/Archives/Zip/ZipArchive.cs
+++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs
@@ -124,6 +124,70 @@ public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null
);
}
+ ///
+ /// Opens a ZipArchive asynchronously from a stream.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(stream, readerOptions));
+ }
+
+ ///
+ /// Opens a ZipArchive asynchronously from a FileInfo.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfo, readerOptions));
+ }
+
+ ///
+ /// Opens a ZipArchive asynchronously from multiple streams.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(streams, readerOptions));
+ }
+
+ ///
+ /// Opens a ZipArchive asynchronously from multiple FileInfo objects.
+ ///
+ ///
+ ///
+ ///
+ public static ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(fileInfos, readerOptions));
+ }
+
public static bool IsZipFile(
string filePath,
string? password = null,
@@ -199,7 +263,95 @@ public static bool IsZipMulti(
if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe
{
var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding());
- var x = z.ReadSeekableHeader(stream).FirstOrDefault();
+ var x = z.ReadSeekableHeader(stream, useSync: true).FirstOrDefault();
+ return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
+ }
+ catch (CryptographicException)
+ {
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public static async ValueTask IsZipFileAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
+ try
+ {
+ if (stream is not SharpCompressStream)
+ {
+ stream = new SharpCompressStream(stream, bufferSize: bufferSize);
+ }
+
+ var header = await headerFactory
+ .ReadStreamHeaderAsync(stream)
+ .Where(x => x.ZipHeaderType != ZipHeaderType.Split)
+ .FirstOrDefaultAsync();
+ if (header is null)
+ {
+ return false;
+ }
+ return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
+ }
+ catch (CryptographicException)
+ {
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public static async ValueTask IsZipMultiAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
+ try
+ {
+ if (stream is not SharpCompressStream)
+ {
+ stream = new SharpCompressStream(stream, bufferSize: bufferSize);
+ }
+
+ var header = headerFactory
+ .ReadStreamHeader(stream)
+ .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
+ if (header is null)
+ {
+ if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe
+ {
+ var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding());
+ ZipHeader? x = null;
+ await foreach (
+ var h in z.ReadSeekableHeaderAsync(stream)
+ .WithCancellation(cancellationToken)
+ )
+ {
+ x = h;
+ break;
+ }
return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry;
}
else
@@ -254,7 +406,9 @@ internal ZipArchive()
protected override IEnumerable LoadEntries(IEnumerable volumes)
{
var vols = volumes.ToArray();
- foreach (var h in headerFactory.NotNull().ReadSeekableHeader(vols.Last().Stream))
+ foreach (
+ var h in headerFactory.NotNull().ReadSeekableHeader(vols.Last().Stream, useSync: true)
+ )
{
if (h != null)
{
@@ -298,6 +452,59 @@ protected override IEnumerable LoadEntries(IEnumerable LoadEntriesAsync(
+ IAsyncEnumerable volumes
+ )
+ {
+ var vols = await volumes.ToListAsync();
+ var volsArray = vols.ToArray();
+
+ await foreach (
+ var h in headerFactory.NotNull().ReadSeekableHeaderAsync(volsArray.Last().Stream)
+ )
+ {
+ if (h != null)
+ {
+ switch (h.ZipHeaderType)
+ {
+ case ZipHeaderType.DirectoryEntry:
+ {
+ var deh = (DirectoryEntryHeader)h;
+ Stream s;
+ if (
+ deh.RelativeOffsetOfEntryHeader + deh.CompressedSize
+ > volsArray[deh.DiskNumberStart].Stream.Length
+ )
+ {
+ var v = volsArray.Skip(deh.DiskNumberStart).ToArray();
+ s = new SourceStream(
+ v[0].Stream,
+ i => i < v.Length ? v[i].Stream : null,
+ new ReaderOptions() { LeaveStreamOpen = true }
+ );
+ }
+ else
+ {
+ s = volsArray[deh.DiskNumberStart].Stream;
+ }
+
+ yield return new ZipArchiveEntry(
+ this,
+ new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
+ );
+ }
+ break;
+ case ZipHeaderType.DirectoryEnd:
+ {
+ var bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty();
+ volsArray.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
+ yield break;
+ }
+ }
+ }
+ }
+ }
+
public void SaveTo(Stream stream) => SaveTo(stream, new WriterOptions(CompressionType.Deflate));
protected override void SaveTo(
@@ -329,7 +536,7 @@ IEnumerable newEntries
}
}
- protected override async Task SaveToAsync(
+ protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
IEnumerable oldEntries,
@@ -385,4 +592,11 @@ protected override IReader CreateReaderForSolidExtraction()
((IStreamStack)stream).StackSeek(0);
return ZipReader.Open(stream, ReaderOptions, Entries);
}
+
+ protected override ValueTask CreateReaderForSolidExtractionAsync()
+ {
+ var stream = Volumes.Single().Stream;
+ stream.Position = 0;
+ return new(ZipReader.Open(stream));
+ }
}
diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs
index a6baf34b3..f59da4f66 100644
--- a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs
+++ b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs
@@ -13,9 +13,17 @@ internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part)
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
- public virtual Task OpenEntryStreamAsync(
+ public async ValueTask OpenEntryStreamAsync(
CancellationToken cancellationToken = default
- ) => Task.FromResult(OpenEntryStream());
+ )
+ {
+ var part = Parts.Single();
+ if (part is SeekableZipFilePart seekablePart)
+ {
+ return (await seekablePart.GetCompressedStreamAsync(cancellationToken)).NotNull();
+ }
+ return OpenEntryStream();
+ }
#region IArchiveEntry Members
diff --git a/src/SharpCompress/Common/AsyncBinaryReader.cs b/src/SharpCompress/Common/AsyncBinaryReader.cs
new file mode 100644
index 000000000..51da5d5cd
--- /dev/null
+++ b/src/SharpCompress/Common/AsyncBinaryReader.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SharpCompress.Common
+{
+ public sealed class AsyncBinaryReader : IDisposable
+ {
+ private readonly Stream _stream;
+ private readonly Stream _originalStream;
+ private readonly bool _leaveOpen;
+ private readonly byte[] _buffer = new byte[8];
+ private bool _disposed;
+
+ public AsyncBinaryReader(Stream stream, bool leaveOpen = false, int bufferSize = 4096)
+ {
+ _originalStream = stream ?? throw new ArgumentNullException(nameof(stream));
+ _leaveOpen = leaveOpen;
+
+ // Use the stream directly without wrapping in BufferedStream
+ // BufferedStream uses synchronous Read internally which doesn't work with async-only streams
+ // SharpCompress uses SharpCompressStream for buffering which supports true async reads
+ _stream = stream;
+ }
+
+ public Stream BaseStream => _stream;
+
+ public async ValueTask ReadByteAsync(CancellationToken ct = default)
+ {
+ await _stream.ReadExactAsync(_buffer, 0, 1, ct).ConfigureAwait(false);
+ return _buffer[0];
+ }
+
+ public async ValueTask ReadUInt16Async(CancellationToken ct = default)
+ {
+ await _stream.ReadExactAsync(_buffer, 0, 2, ct).ConfigureAwait(false);
+ return BinaryPrimitives.ReadUInt16LittleEndian(_buffer);
+ }
+
+ public async ValueTask ReadUInt32Async(CancellationToken ct = default)
+ {
+ await _stream.ReadExactAsync(_buffer, 0, 4, ct).ConfigureAwait(false);
+ return BinaryPrimitives.ReadUInt32LittleEndian(_buffer);
+ }
+
+ public async ValueTask ReadUInt64Async(CancellationToken ct = default)
+ {
+ await _stream.ReadExactAsync(_buffer, 0, 8, ct).ConfigureAwait(false);
+ return BinaryPrimitives.ReadUInt64LittleEndian(_buffer);
+ }
+
+ public async ValueTask ReadBytesAsync(int count, CancellationToken ct = default)
+ {
+ var result = new byte[count];
+ await _stream.ReadExactAsync(result, 0, count, ct).ConfigureAwait(false);
+ return result;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ // Dispose the original stream if we own it
+ if (!_leaveOpen)
+ {
+ _originalStream.Dispose();
+ }
+ }
+
+#if NET6_0_OR_GREATER
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+
+ // Dispose the original stream if we own it
+ if (!_leaveOpen)
+ {
+ await _originalStream.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+#endif
+ }
+}
diff --git a/src/SharpCompress/Common/EntryStream.cs b/src/SharpCompress/Common/EntryStream.cs
index 9e87e25e0..11d0e898f 100644
--- a/src/SharpCompress/Common/EntryStream.cs
+++ b/src/SharpCompress/Common/EntryStream.cs
@@ -56,7 +56,7 @@ public void SkipEntry()
///
/// Asynchronously skip the rest of the entry stream.
///
- public async Task SkipEntryAsync(CancellationToken cancellationToken = default)
+ public async ValueTask SkipEntryAsync(CancellationToken cancellationToken = default)
{
await this.SkipAsync(cancellationToken).ConfigureAwait(false);
_completed = true;
diff --git a/src/SharpCompress/Common/ExtractionMethods.cs b/src/SharpCompress/Common/ExtractionMethods.cs
index 509524b15..787771de9 100644
--- a/src/SharpCompress/Common/ExtractionMethods.cs
+++ b/src/SharpCompress/Common/ExtractionMethods.cs
@@ -124,11 +124,11 @@ Action openAndWrite
}
}
- public static async Task WriteEntryToDirectoryAsync(
+ public static async ValueTask WriteEntryToDirectoryAsync(
IEntry entry,
string destinationDirectory,
ExtractionOptions? options,
- Func writeAsync,
+ Func writeAsync,
CancellationToken cancellationToken = default
)
{
@@ -197,11 +197,11 @@ public static async Task WriteEntryToDirectoryAsync(
}
}
- public static async Task WriteEntryToFileAsync(
+ public static async ValueTask WriteEntryToFileAsync(
IEntry entry,
string destinationFileName,
ExtractionOptions? options,
- Func openAndWriteAsync,
+ Func openAndWriteAsync,
CancellationToken cancellationToken = default
)
{
diff --git a/src/SharpCompress/Common/FilePart.cs b/src/SharpCompress/Common/FilePart.cs
index 3548b6c1c..583dbf4b4 100644
--- a/src/SharpCompress/Common/FilePart.cs
+++ b/src/SharpCompress/Common/FilePart.cs
@@ -1,4 +1,6 @@
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace SharpCompress.Common;
@@ -14,4 +16,8 @@ public abstract class FilePart
internal abstract Stream? GetCompressedStream();
internal abstract Stream? GetRawStream();
internal bool Skipped { get; set; }
+
+ internal virtual ValueTask GetCompressedStreamAsync(
+ CancellationToken cancellationToken = default
+ ) => new(GetCompressedStream());
}
diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs
index 2e54a6ddd..7d35f3ea6 100644
--- a/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEndHeader.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
@@ -19,6 +20,18 @@ internal override void Read(BinaryReader reader)
Comment = reader.ReadBytes(CommentLength);
}
+ internal override async ValueTask Read(AsyncBinaryReader reader)
+ {
+ VolumeNumber = await reader.ReadUInt16Async();
+ FirstVolumeWithDirectory = await reader.ReadUInt16Async();
+ TotalNumberOfEntriesInDisk = await reader.ReadUInt16Async();
+ TotalNumberOfEntries = await reader.ReadUInt16Async();
+ DirectorySize = await reader.ReadUInt32Async();
+ DirectoryStartOffsetRelativeToDisk = await reader.ReadUInt32Async();
+ CommentLength = await reader.ReadUInt16Async();
+ Comment = await reader.ReadBytesAsync(CommentLength);
+ }
+
public ushort VolumeNumber { get; private set; }
public ushort FirstVolumeWithDirectory { get; private set; }
diff --git a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
index 6a95a5bfb..a9ed564f6 100644
--- a/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/DirectoryEntryHeader.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
@@ -31,7 +32,37 @@ internal override void Read(BinaryReader reader)
var extra = reader.ReadBytes(extraLength);
var comment = reader.ReadBytes(commentLength);
- // According to .ZIP File Format Specification
+ ProcessReadData(name, extra, comment);
+ }
+
+ internal override async ValueTask Read(AsyncBinaryReader reader)
+ {
+ Version = await reader.ReadUInt16Async();
+ VersionNeededToExtract = await reader.ReadUInt16Async();
+ Flags = (HeaderFlags)await reader.ReadUInt16Async();
+ CompressionMethod = (ZipCompressionMethod)await reader.ReadUInt16Async();
+ OriginalLastModifiedTime = LastModifiedTime = await reader.ReadUInt16Async();
+ OriginalLastModifiedDate = LastModifiedDate = await reader.ReadUInt16Async();
+ Crc = await reader.ReadUInt32Async();
+ CompressedSize = await reader.ReadUInt32Async();
+ UncompressedSize = await reader.ReadUInt32Async();
+ var nameLength = await reader.ReadUInt16Async();
+ var extraLength = await reader.ReadUInt16Async();
+ var commentLength = await reader.ReadUInt16Async();
+ DiskNumberStart = await reader.ReadUInt16Async();
+ InternalFileAttributes = await reader.ReadUInt16Async();
+ ExternalFileAttributes = await reader.ReadUInt32Async();
+ RelativeOffsetOfEntryHeader = await reader.ReadUInt32Async();
+
+ var name = await reader.ReadBytesAsync(nameLength);
+ var extra = await reader.ReadBytesAsync(extraLength);
+ var comment = await reader.ReadBytesAsync(commentLength);
+
+ ProcessReadData(name, extra, comment);
+ }
+
+ private void ProcessReadData(byte[] name, byte[] extra, byte[] comment)
+ {
//
// For example: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
//
diff --git a/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs b/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs
index 5a587a7bc..9c648baf3 100644
--- a/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/IgnoreHeader.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
@@ -8,4 +9,6 @@ public IgnoreHeader(ZipHeaderType type)
: base(type) { }
internal override void Read(BinaryReader reader) { }
+
+ internal override ValueTask Read(AsyncBinaryReader reader) => default;
}
diff --git a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
index 6fd3a9e2c..9091d454e 100644
--- a/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/LocalEntryHeader.cs
@@ -1,13 +1,12 @@
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
-internal class LocalEntryHeader : ZipFileEntry
+internal class LocalEntryHeader(IArchiveEncoding archiveEncoding)
+ : ZipFileEntry(ZipHeaderType.LocalEntry, archiveEncoding)
{
- public LocalEntryHeader(IArchiveEncoding archiveEncoding)
- : base(ZipHeaderType.LocalEntry, archiveEncoding) { }
-
internal override void Read(BinaryReader reader)
{
Version = reader.ReadUInt16();
@@ -23,7 +22,29 @@ internal override void Read(BinaryReader reader)
var name = reader.ReadBytes(nameLength);
var extra = reader.ReadBytes(extraLength);
- // According to .ZIP File Format Specification
+ ProcessReadData(name, extra);
+ }
+
+ internal override async ValueTask Read(AsyncBinaryReader reader)
+ {
+ Version = await reader.ReadUInt16Async();
+ Flags = (HeaderFlags)await reader.ReadUInt16Async();
+ CompressionMethod = (ZipCompressionMethod)await reader.ReadUInt16Async();
+ OriginalLastModifiedTime = LastModifiedTime = await reader.ReadUInt16Async();
+ OriginalLastModifiedDate = LastModifiedDate = await reader.ReadUInt16Async();
+ Crc = await reader.ReadUInt32Async();
+ CompressedSize = await reader.ReadUInt32Async();
+ UncompressedSize = await reader.ReadUInt32Async();
+ var nameLength = await reader.ReadUInt16Async();
+ var extraLength = await reader.ReadUInt16Async();
+ var name = await reader.ReadBytesAsync(nameLength);
+ var extra = await reader.ReadBytesAsync(extraLength);
+
+ ProcessReadData(name, extra);
+ }
+
+ private void ProcessReadData(byte[] name, byte[] extra)
+ {
//
// For example: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
//
diff --git a/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs b/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs
index 4151a6cbf..29aaabaae 100644
--- a/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/SplitHeader.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
@@ -9,4 +10,7 @@ public SplitHeader()
: base(ZipHeaderType.Split) { }
internal override void Read(BinaryReader reader) => throw new NotImplementedException();
+
+ internal override ValueTask Read(AsyncBinaryReader reader) =>
+ throw new NotImplementedException();
}
diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs
index a74b4d1f0..b15b6f162 100644
--- a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndHeader.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
@@ -26,6 +27,25 @@ internal override void Read(BinaryReader reader)
);
}
+ internal override async ValueTask Read(AsyncBinaryReader reader)
+ {
+ SizeOfDirectoryEndRecord = (long)await reader.ReadUInt64Async();
+ VersionMadeBy = await reader.ReadUInt16Async();
+ VersionNeededToExtract = await reader.ReadUInt16Async();
+ VolumeNumber = await reader.ReadUInt32Async();
+ FirstVolumeWithDirectory = await reader.ReadUInt32Async();
+ TotalNumberOfEntriesInDisk = (long)await reader.ReadUInt64Async();
+ TotalNumberOfEntries = (long)await reader.ReadUInt64Async();
+ DirectorySize = (long)await reader.ReadUInt64Async();
+ DirectoryStartOffsetRelativeToDisk = (long)await reader.ReadUInt64Async();
+ DataSector = await reader.ReadBytesAsync(
+ (int)(
+ SizeOfDirectoryEndRecord
+ - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS
+ )
+ );
+ }
+
private const int SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS = 44;
public long SizeOfDirectoryEndRecord { get; private set; }
diff --git a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs
index 3020d377e..8326be99a 100644
--- a/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/Zip64DirectoryEndLocatorHeader.cs
@@ -1,12 +1,10 @@
using System.IO;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
-internal class Zip64DirectoryEndLocatorHeader : ZipHeader
+internal class Zip64DirectoryEndLocatorHeader() : ZipHeader(ZipHeaderType.Zip64DirectoryEndLocator)
{
- public Zip64DirectoryEndLocatorHeader()
- : base(ZipHeaderType.Zip64DirectoryEndLocator) { }
-
internal override void Read(BinaryReader reader)
{
FirstVolumeWithDirectory = reader.ReadUInt32();
@@ -14,6 +12,13 @@ internal override void Read(BinaryReader reader)
TotalNumberOfVolumes = reader.ReadUInt32();
}
+ internal override async ValueTask Read(AsyncBinaryReader reader)
+ {
+ FirstVolumeWithDirectory = await reader.ReadUInt32Async();
+ RelativeOffsetOfTheEndOfDirectoryRecord = (long)await reader.ReadUInt64Async();
+ TotalNumberOfVolumes = await reader.ReadUInt32Async();
+ }
+
public uint FirstVolumeWithDirectory { get; private set; }
public long RelativeOffsetOfTheEndOfDirectoryRecord { get; private set; }
diff --git a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs
index 374f54370..edcb29767 100644
--- a/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs
+++ b/src/SharpCompress/Common/Zip/Headers/ZipFileEntry.cs
@@ -2,18 +2,14 @@
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
-internal abstract class ZipFileEntry : ZipHeader
+internal abstract class ZipFileEntry(ZipHeaderType type, IArchiveEncoding archiveEncoding)
+ : ZipHeader(type)
{
- protected ZipFileEntry(ZipHeaderType type, IArchiveEncoding archiveEncoding)
- : base(type)
- {
- Extra = new List();
- ArchiveEncoding = archiveEncoding;
- }
-
internal bool IsDirectory
{
get
@@ -30,7 +26,7 @@ internal bool IsDirectory
internal Stream? PackedStream { get; set; }
- internal IArchiveEncoding ArchiveEncoding { get; }
+ internal IArchiveEncoding ArchiveEncoding { get; } = archiveEncoding;
internal string? Name { get; set; }
@@ -44,7 +40,7 @@ internal bool IsDirectory
internal long UncompressedSize { get; set; }
- internal List Extra { get; set; }
+ internal List Extra { get; set; } = new();
public string? Password { get; set; }
@@ -63,6 +59,24 @@ internal PkwareTraditionalEncryptionData ComposeEncryptionData(Stream archiveStr
return encryptionData;
}
+ internal async ValueTask ComposeEncryptionDataAsync(
+ Stream archiveStream,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (archiveStream is null)
+ {
+ throw new ArgumentNullException(nameof(archiveStream));
+ }
+
+ var buffer = new byte[12];
+ await archiveStream.ReadFullyAsync(buffer, 0, 12, cancellationToken).ConfigureAwait(false);
+
+ var encryptionData = PkwareTraditionalEncryptionData.ForRead(Password!, this, buffer);
+
+ return encryptionData;
+ }
+
internal WinzipAesEncryptionData? WinzipAesEncryptionData { get; set; }
///
diff --git a/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs b/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs
index 36d40a821..9ce1caa3a 100644
--- a/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs
+++ b/src/SharpCompress/Common/Zip/Headers/ZipHeader.cs
@@ -1,18 +1,14 @@
using System.IO;
+using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
-internal abstract class ZipHeader
+internal abstract class ZipHeader(ZipHeaderType type)
{
- protected ZipHeader(ZipHeaderType type)
- {
- ZipHeaderType = type;
- HasData = true;
- }
-
- internal ZipHeaderType ZipHeaderType { get; }
+ internal ZipHeaderType ZipHeaderType { get; } = type;
internal abstract void Read(BinaryReader reader);
+ internal abstract ValueTask Read(AsyncBinaryReader reader);
- internal bool HasData { get; set; }
+ internal bool HasData { get; set; } = true;
}
diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs
index e75727112..7dbf93baa 100644
--- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs
+++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs
@@ -1,4 +1,6 @@
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
namespace SharpCompress.Common.Zip;
@@ -25,9 +27,24 @@ internal override Stream GetCompressedStream()
return base.GetCompressedStream();
}
+ internal override async ValueTask GetCompressedStreamAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (!_isLocalHeaderLoaded)
+ {
+ await LoadLocalHeaderAsync(cancellationToken);
+ _isLocalHeaderLoaded = true;
+ }
+ return await base.GetCompressedStreamAsync(cancellationToken);
+ }
+
private void LoadLocalHeader() =>
Header = _headerFactory.GetLocalHeader(BaseStream, (DirectoryEntryHeader)Header);
+ private async ValueTask LoadLocalHeaderAsync(CancellationToken cancellationToken = default) =>
+ Header = await _headerFactory.GetLocalHeaderAsync(BaseStream, (DirectoryEntryHeader)Header);
+
protected override Stream CreateBaseStream()
{
BaseStream.Position = Header.DataStartPosition.NotNull();
diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs
index 8d6349586..908566229 100644
--- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs
+++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -18,7 +19,74 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
internal SeekableZipHeaderFactory(string? password, IArchiveEncoding archiveEncoding)
: base(StreamingMode.Seekable, password, archiveEncoding) { }
- internal IEnumerable ReadSeekableHeader(Stream stream)
+ internal async IAsyncEnumerable ReadSeekableHeaderAsync(Stream stream)
+ {
+ var reader = new AsyncBinaryReader(stream);
+
+ await SeekBackToHeaderAsync(stream, reader);
+
+ var eocd_location = stream.Position;
+ var entry = new DirectoryEndHeader();
+ await entry.Read(reader);
+
+ if (entry.IsZip64)
+ {
+ _zip64 = true;
+
+ // ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD
+ stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin);
+ uint zip64_locator = await reader.ReadUInt32Async();
+ if (zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR)
+ {
+ throw new ArchiveException("Failed to locate the Zip64 Directory Locator");
+ }
+
+ var zip64Locator = new Zip64DirectoryEndLocatorHeader();
+ await zip64Locator.Read(reader);
+
+ stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin);
+ var zip64Signature = await reader.ReadUInt32Async();
+ if (zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY)
+ {
+ throw new ArchiveException("Failed to locate the Zip64 Header");
+ }
+
+ var zip64Entry = new Zip64DirectoryEndHeader();
+ await zip64Entry.Read(reader);
+ stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
+ }
+ else
+ {
+ stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
+ }
+
+ var position = stream.Position;
+ while (true)
+ {
+ stream.Position = position;
+ var signature = await reader.ReadUInt32Async();
+ var nextHeader = await ReadHeader(signature, reader, _zip64);
+ position = stream.Position;
+
+ if (nextHeader is null)
+ {
+ yield break;
+ }
+
+ if (nextHeader is DirectoryEntryHeader entryHeader)
+ {
+ //entry could be zero bytes so we need to know that.
+ entryHeader.HasData = entryHeader.CompressedSize != 0;
+ yield return entryHeader;
+ }
+ else if (nextHeader is DirectoryEndHeader endHeader)
+ {
+ yield return endHeader;
+ }
+ }
+ }
+
+ internal IEnumerable ReadSeekableHeader(Stream stream, bool useSync)
{
var reader = new BinaryReader(stream);
@@ -85,6 +153,73 @@ internal IEnumerable ReadSeekableHeader(Stream stream)
}
}
+ internal async IAsyncEnumerable ReadSeekableHeaderAsync(Stream stream, bool useSync)
+ {
+ var reader = new AsyncBinaryReader(stream);
+
+ await SeekBackToHeaderAsync(stream, reader);
+
+ var eocd_location = stream.Position;
+ var entry = new DirectoryEndHeader();
+ await entry.Read(reader);
+
+ if (entry.IsZip64)
+ {
+ _zip64 = true;
+
+ // ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD
+ stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin);
+ var zip64_locator = await reader.ReadUInt32Async();
+ if (zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR)
+ {
+ throw new ArchiveException("Failed to locate the Zip64 Directory Locator");
+ }
+
+ var zip64Locator = new Zip64DirectoryEndLocatorHeader();
+ await zip64Locator.Read(reader);
+
+ stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin);
+ var zip64Signature = await reader.ReadUInt32Async();
+ if (zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY)
+ {
+ throw new ArchiveException("Failed to locate the Zip64 Header");
+ }
+
+ var zip64Entry = new Zip64DirectoryEndHeader();
+ await zip64Entry.Read(reader);
+ stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
+ }
+ else
+ {
+ stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
+ }
+
+ var position = stream.Position;
+ while (true)
+ {
+ stream.Position = position;
+ var signature = await reader.ReadUInt32Async();
+ var nextHeader = await ReadHeader(signature, reader, _zip64);
+ position = stream.Position;
+
+ if (nextHeader is null)
+ {
+ yield break;
+ }
+
+ if (nextHeader is DirectoryEntryHeader entryHeader)
+ {
+ //entry could be zero bytes so we need to know that.
+ entryHeader.HasData = entryHeader.CompressedSize != 0;
+ yield return entryHeader;
+ }
+ else if (nextHeader is DirectoryEndHeader endHeader)
+ {
+ yield return endHeader;
+ }
+ }
+ }
+
private static bool IsMatch(byte[] haystack, int position, byte[] needle)
{
for (var i = 0; i < needle.Length; i++)
@@ -98,6 +233,45 @@ private static bool IsMatch(byte[] haystack, int position, byte[] needle)
return true;
}
+ private static async ValueTask SeekBackToHeaderAsync(Stream stream, AsyncBinaryReader reader)
+ {
+ // Minimum EOCD length
+ if (stream.Length < MINIMUM_EOCD_LENGTH)
+ {
+ throw new ArchiveException(
+ "Could not find Zip file Directory at the end of the file. File may be corrupted."
+ );
+ }
+
+ var len =
+ stream.Length < MAX_SEARCH_LENGTH_FOR_EOCD
+ ? (int)stream.Length
+ : MAX_SEARCH_LENGTH_FOR_EOCD;
+ // We search for marker in reverse to find the first occurance
+ byte[] needle = { 0x06, 0x05, 0x4b, 0x50 };
+
+ stream.Seek(-len, SeekOrigin.End);
+
+ var seek = await reader.ReadBytesAsync(len);
+
+ // Search in reverse
+ Array.Reverse(seek);
+
+ // don't exclude the minimum eocd region, otherwise you fail to locate the header in empty zip files
+ var max_search_area = len; // - MINIMUM_EOCD_LENGTH;
+
+ for (var pos_from_end = 0; pos_from_end < max_search_area; ++pos_from_end)
+ {
+ if (IsMatch(seek, pos_from_end, needle))
+ {
+ stream.Seek(-pos_from_end, SeekOrigin.End);
+ return;
+ }
+ }
+
+ throw new ArchiveException("Failed to locate the Zip Header");
+ }
+
private static void SeekBackToHeader(Stream stream, BinaryReader reader)
{
// Minimum EOCD length
@@ -163,4 +337,31 @@ DirectoryEntryHeader directoryEntryHeader
}
return localEntryHeader;
}
+
+ internal async ValueTask GetLocalHeaderAsync(
+ Stream stream,
+ DirectoryEntryHeader directoryEntryHeader
+ )
+ {
+ stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin);
+ var reader = new AsyncBinaryReader(stream);
+ var signature = await reader.ReadUInt32Async();
+ if (await ReadHeader(signature, reader, _zip64) is not LocalEntryHeader localEntryHeader)
+ {
+ throw new InvalidOperationException();
+ }
+
+ // populate fields only known from the DirectoryEntryHeader
+ localEntryHeader.HasData = directoryEntryHeader.HasData;
+ localEntryHeader.ExternalFileAttributes = directoryEntryHeader.ExternalFileAttributes;
+ localEntryHeader.Comment = directoryEntryHeader.Comment;
+
+ if (FlagUtility.HasFlag(localEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor))
+ {
+ localEntryHeader.Crc = directoryEntryHeader.Crc;
+ localEntryHeader.CompressedSize = directoryEntryHeader.CompressedSize;
+ localEntryHeader.UncompressedSize = directoryEntryHeader.UncompressedSize;
+ }
+ return localEntryHeader;
+ }
}
diff --git a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs
index 5464a9cc8..312ea1263 100644
--- a/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs
+++ b/src/SharpCompress/Common/Zip/StreamingZipFilePart.cs
@@ -1,4 +1,6 @@
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors.Deflate;
using SharpCompress.IO;
@@ -31,6 +33,28 @@ internal override Stream GetCompressedStream()
return _decompressionStream;
}
+ internal override async ValueTask GetCompressedStreamAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (!Header.HasData)
+ {
+ return Stream.Null;
+ }
+ _decompressionStream = await CreateDecompressionStreamAsync(
+ await GetCryptoStreamAsync(CreateBaseStream(), cancellationToken)
+ .ConfigureAwait(false),
+ Header.CompressionMethod,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ if (LeaveStreamOpen)
+ {
+ return SharpCompressStream.Create(_decompressionStream, leaveOpen: true);
+ }
+ return _decompressionStream;
+ }
+
internal BinaryReader FixStreamedFileLocation(ref SharpCompressStream rewindableStream)
{
if (Header.IsDirectory)
diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs
index ff52244a4..479a5c2a1 100644
--- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs
+++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs
@@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using SharpCompress.Common;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -200,4 +203,331 @@ internal IEnumerable ReadStreamHeader(Stream stream)
yield return header;
}
}
+
+ ///
+ /// Reads ZIP headers asynchronously for streams that do not support synchronous reads.
+ ///
+ internal IAsyncEnumerable ReadStreamHeaderAsync(Stream stream) =>
+ new StreamHeaderAsyncEnumerable(this, stream);
+
+ ///
+ /// Invokes the shared async header parsing logic on the base factory.
+ ///
+ private ValueTask ReadHeaderAsyncInternal(
+ uint headerBytes,
+ AsyncBinaryReader reader
+ ) => ReadHeader(headerBytes, reader);
+
+ ///
+ /// Exposes the last parsed local entry header to the async enumerator so it can handle streaming data descriptors.
+ ///
+ private LocalEntryHeader? LastEntryHeader
+ {
+ get => _lastEntryHeader;
+ set => _lastEntryHeader = value;
+ }
+
+ ///
+ /// Produces an async enumerator for streaming ZIP headers.
+ ///
+ private sealed class StreamHeaderAsyncEnumerable : IAsyncEnumerable
+ {
+ private readonly StreamingZipHeaderFactory _headerFactory;
+ private readonly Stream _stream;
+
+ public StreamHeaderAsyncEnumerable(StreamingZipHeaderFactory headerFactory, Stream stream)
+ {
+ _headerFactory = headerFactory;
+ _stream = stream;
+ }
+
+ public IAsyncEnumerator GetAsyncEnumerator(
+ CancellationToken cancellationToken = default
+ ) => new StreamHeaderAsyncEnumerator(_headerFactory, _stream, cancellationToken);
+ }
+
+ ///
+ /// Async implementation of using to avoid sync reads.
+ ///
+ private sealed class StreamHeaderAsyncEnumerator : IAsyncEnumerator, IDisposable
+ {
+ private readonly StreamingZipHeaderFactory _headerFactory;
+ private readonly SharpCompressStream _rewindableStream;
+ private readonly AsyncBinaryReader _reader;
+ private readonly CancellationToken _cancellationToken;
+ private bool _completed;
+
+ public StreamHeaderAsyncEnumerator(
+ StreamingZipHeaderFactory headerFactory,
+ Stream stream,
+ CancellationToken cancellationToken
+ )
+ {
+ _headerFactory = headerFactory;
+ _rewindableStream = EnsureSharpCompressStream(stream);
+ _reader = new AsyncBinaryReader(_rewindableStream, leaveOpen: true);
+ _cancellationToken = cancellationToken;
+ }
+
+ private ZipHeader? _current;
+
+ public ZipHeader Current =>
+ _current ?? throw new InvalidOperationException("No current header is available.");
+
+ ///
+ /// Advances to the next ZIP header in the stream, honoring streaming data descriptors where applicable.
+ ///
+ public async ValueTask MoveNextAsync()
+ {
+ if (_completed)
+ {
+ return false;
+ }
+
+ while (true)
+ {
+ _cancellationToken.ThrowIfCancellationRequested();
+
+ uint headerBytes;
+ var lastEntryHeader = _headerFactory.LastEntryHeader;
+ if (
+ lastEntryHeader != null
+ && FlagUtility.HasFlag(lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor)
+ )
+ {
+ if (lastEntryHeader.Part is null)
+ {
+ continue;
+ }
+
+ var pos = _rewindableStream.CanSeek ? (long?)_rewindableStream.Position : null;
+
+ var crc = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ if (crc == POST_DATA_DESCRIPTOR)
+ {
+ crc = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ }
+ lastEntryHeader.Crc = crc;
+
+ //attempt 32bit read
+ ulong compressedSize = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ ulong uncompressedSize = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ headerBytes = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+
+ //check for zip64 sentinel or unexpected header
+ bool isSentinel =
+ compressedSize == 0xFFFFFFFF || uncompressedSize == 0xFFFFFFFF;
+ bool isHeader = headerBytes == 0x04034b50 || headerBytes == 0x02014b50;
+
+ if (!isHeader && !isSentinel)
+ {
+ //reshuffle into 64-bit values
+ compressedSize = (uncompressedSize << 32) | compressedSize;
+ uncompressedSize =
+ ((ulong)headerBytes << 32)
+ | await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ headerBytes = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ }
+ else if (isSentinel)
+ {
+ //standards-compliant zip64 descriptor
+ compressedSize = await _reader
+ .ReadUInt64Async(_cancellationToken)
+ .ConfigureAwait(false);
+ uncompressedSize = await _reader
+ .ReadUInt64Async(_cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ lastEntryHeader.CompressedSize = (long)compressedSize;
+ lastEntryHeader.UncompressedSize = (long)uncompressedSize;
+
+ if (pos.HasValue)
+ {
+ lastEntryHeader.DataStartPosition = pos - lastEntryHeader.CompressedSize;
+ }
+ }
+ else if (lastEntryHeader != null && lastEntryHeader.IsZip64)
+ {
+ if (lastEntryHeader.Part is null)
+ {
+ continue;
+ }
+
+ var pos = _rewindableStream.CanSeek ? (long?)_rewindableStream.Position : null;
+
+ headerBytes = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+
+ _ = await _reader.ReadUInt16Async(_cancellationToken).ConfigureAwait(false); // version
+ _ = await _reader.ReadUInt16Async(_cancellationToken).ConfigureAwait(false); // flags
+ _ = await _reader.ReadUInt16Async(_cancellationToken).ConfigureAwait(false); // compressionMethod
+ _ = await _reader.ReadUInt16Async(_cancellationToken).ConfigureAwait(false); // lastModifiedDate
+ _ = await _reader.ReadUInt16Async(_cancellationToken).ConfigureAwait(false); // lastModifiedTime
+
+ var crc = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+
+ if (crc == POST_DATA_DESCRIPTOR)
+ {
+ crc = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ }
+ lastEntryHeader.Crc = crc;
+
+ // The DataDescriptor can be either 64bit or 32bit
+ var compressedSize = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ var uncompressedSize = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+
+ // Check if we have header or 64bit DataDescriptor
+ var testHeader = !(headerBytes == 0x04034b50 || headerBytes == 0x02014b50);
+
+ var test64Bit = ((long)uncompressedSize << 32) | compressedSize;
+ if (test64Bit == lastEntryHeader.CompressedSize && testHeader)
+ {
+ lastEntryHeader.UncompressedSize =
+ (
+ (long)
+ await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false) << 32
+ ) | headerBytes;
+ headerBytes = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ lastEntryHeader.UncompressedSize = uncompressedSize;
+ }
+
+ if (pos.HasValue)
+ {
+ lastEntryHeader.DataStartPosition = pos - lastEntryHeader.CompressedSize;
+
+ // 4 = First 4 bytes of the entry header (i.e. 50 4B 03 04)
+ _rewindableStream.Position = pos.Value + 4;
+ }
+ }
+ else
+ {
+ headerBytes = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ _headerFactory.LastEntryHeader = null;
+ var header = await _headerFactory
+ .ReadHeaderAsyncInternal(headerBytes, _reader)
+ .ConfigureAwait(false);
+ if (header is null)
+ {
+ _completed = true;
+ return false;
+ }
+
+ //entry could be zero bytes so we need to know that.
+ if (header.ZipHeaderType == ZipHeaderType.LocalEntry)
+ {
+ var localHeader = (LocalEntryHeader)header;
+ var directoryHeader = _headerFactory._entries?.FirstOrDefault(entry =>
+ entry.Key == localHeader.Name
+ && localHeader.CompressedSize == 0
+ && localHeader.UncompressedSize == 0
+ && localHeader.Crc == 0
+ && localHeader.IsDirectory == false
+ );
+
+ if (directoryHeader != null)
+ {
+ localHeader.UncompressedSize = directoryHeader.Size;
+ localHeader.CompressedSize = directoryHeader.CompressedSize;
+ localHeader.Crc = (uint)directoryHeader.Crc;
+ }
+
+ // If we have CompressedSize, there is data to be read
+ if (localHeader.CompressedSize > 0)
+ {
+ header.HasData = true;
+ } // Check if zip is streaming ( Length is 0 and is declared in PostDataDescriptor )
+ else if (localHeader.Flags.HasFlag(HeaderFlags.UsePostDataDescriptor))
+ {
+ var nextHeaderBytes = await _reader
+ .ReadUInt32Async(_cancellationToken)
+ .ConfigureAwait(false);
+ ((IStreamStack)_rewindableStream).Rewind(sizeof(uint));
+
+ // Check if next data is PostDataDescriptor, streamed file with 0 length
+ header.HasData = !IsHeader(nextHeaderBytes);
+ }
+ else // We are not streaming and compressed size is 0, we have no data
+ {
+ header.HasData = false;
+ }
+ }
+
+ _current = header;
+ return true;
+ }
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ Dispose();
+ return default;
+ }
+
+ ///
+ /// Disposes the underlying reader (without closing the archive stream).
+ ///
+ public void Dispose()
+ {
+ _reader.Dispose();
+ }
+
+ ///
+ /// Ensures the stream is a so header parsing can use rewind/buffer helpers.
+ ///
+ private static SharpCompressStream EnsureSharpCompressStream(Stream stream)
+ {
+ if (stream is SharpCompressStream sharpCompressStream)
+ {
+ return sharpCompressStream;
+ }
+
+ // Ensure the stream is already a SharpCompressStream so the buffer/size is set.
+ // The original code wrapped this with RewindableStream; use SharpCompressStream so we can get the buffer size.
+ if (stream is SourceStream src)
+ {
+ return new SharpCompressStream(
+ stream,
+ src.ReaderOptions.LeaveStreamOpen,
+ bufferSize: src.ReaderOptions.BufferSize
+ );
+ }
+
+ throw new ArgumentException("Stream must be a SharpCompressStream", nameof(stream));
+ }
+ }
}
diff --git a/src/SharpCompress/Common/Zip/ZipFilePart.cs b/src/SharpCompress/Common/Zip/ZipFilePart.cs
index 16eb8e1a9..219f24fae 100644
--- a/src/SharpCompress/Common/Zip/ZipFilePart.cs
+++ b/src/SharpCompress/Common/Zip/ZipFilePart.cs
@@ -2,6 +2,8 @@
using System.Buffers.Binary;
using System.IO;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors;
using SharpCompress.Compressors.BZip2;
@@ -264,4 +266,244 @@ protected Stream GetCryptoStream(Stream plainStream)
}
return plainStream;
}
+
+ internal override async ValueTask GetCompressedStreamAsync(
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (!Header.HasData)
+ {
+ return Stream.Null;
+ }
+ var decompressionStream = await CreateDecompressionStreamAsync(
+ await GetCryptoStreamAsync(CreateBaseStream(), cancellationToken)
+ .ConfigureAwait(false),
+ Header.CompressionMethod,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ if (LeaveStreamOpen)
+ {
+ return SharpCompressStream.Create(decompressionStream, leaveOpen: true);
+ }
+ return decompressionStream;
+ }
+
+ protected async Task GetCryptoStreamAsync(
+ Stream plainStream,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var isFileEncrypted = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted);
+
+ if (Header.CompressedSize == 0 && isFileEncrypted)
+ {
+ throw new NotSupportedException("Cannot encrypt file with unknown size at start.");
+ }
+
+ if (
+ (
+ Header.CompressedSize == 0
+ && FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor)
+ ) || Header.IsZip64
+ )
+ {
+ plainStream = SharpCompressStream.Create(plainStream, leaveOpen: true); //make sure AES doesn't close
+ }
+ else
+ {
+ plainStream = new ReadOnlySubStream(plainStream, Header.CompressedSize); //make sure AES doesn't close
+ }
+
+ if (isFileEncrypted)
+ {
+ switch (Header.CompressionMethod)
+ {
+ case ZipCompressionMethod.None:
+ case ZipCompressionMethod.Shrink:
+ case ZipCompressionMethod.Reduce1:
+ case ZipCompressionMethod.Reduce2:
+ case ZipCompressionMethod.Reduce3:
+ case ZipCompressionMethod.Reduce4:
+ case ZipCompressionMethod.Deflate:
+ case ZipCompressionMethod.Deflate64:
+ case ZipCompressionMethod.BZip2:
+ case ZipCompressionMethod.LZMA:
+ case ZipCompressionMethod.PPMd:
+ {
+ return new PkwareTraditionalCryptoStream(
+ plainStream,
+ await Header
+ .ComposeEncryptionDataAsync(plainStream, cancellationToken)
+ .ConfigureAwait(false),
+ CryptoMode.Decrypt
+ );
+ }
+
+ case ZipCompressionMethod.WinzipAes:
+ {
+ if (Header.WinzipAesEncryptionData != null)
+ {
+ return new WinzipAesCryptoStream(
+ plainStream,
+ Header.WinzipAesEncryptionData,
+ Header.CompressedSize - 10
+ );
+ }
+ return plainStream;
+ }
+
+ default:
+ {
+ throw new InvalidOperationException("Header.CompressionMethod is invalid");
+ }
+ }
+ }
+ return plainStream;
+ }
+
+ protected async Task CreateDecompressionStreamAsync(
+ Stream stream,
+ ZipCompressionMethod method,
+ CancellationToken cancellationToken = default
+ )
+ {
+ switch (method)
+ {
+ case ZipCompressionMethod.None:
+ {
+ if (Header.CompressedSize is 0)
+ {
+ return new DataDescriptorStream(stream);
+ }
+
+ return stream;
+ }
+ case ZipCompressionMethod.Shrink:
+ {
+ return new ShrinkStream(
+ stream,
+ CompressionMode.Decompress,
+ Header.CompressedSize,
+ Header.UncompressedSize
+ );
+ }
+ case ZipCompressionMethod.Reduce1:
+ {
+ return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 1);
+ }
+ case ZipCompressionMethod.Reduce2:
+ {
+ return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 2);
+ }
+ case ZipCompressionMethod.Reduce3:
+ {
+ return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 3);
+ }
+ case ZipCompressionMethod.Reduce4:
+ {
+ return new ReduceStream(stream, Header.CompressedSize, Header.UncompressedSize, 4);
+ }
+ case ZipCompressionMethod.Explode:
+ {
+ return new ExplodeStream(
+ stream,
+ Header.CompressedSize,
+ Header.UncompressedSize,
+ Header.Flags
+ );
+ }
+
+ case ZipCompressionMethod.Deflate:
+ {
+ return new DeflateStream(stream, CompressionMode.Decompress);
+ }
+ case ZipCompressionMethod.Deflate64:
+ {
+ return new Deflate64Stream(stream, CompressionMode.Decompress);
+ }
+ case ZipCompressionMethod.BZip2:
+ {
+ return new BZip2Stream(stream, CompressionMode.Decompress, false);
+ }
+ case ZipCompressionMethod.LZMA:
+ {
+ if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted))
+ {
+ throw new NotSupportedException("LZMA with pkware encryption.");
+ }
+ var buffer = new byte[4];
+ await stream.ReadFullyAsync(buffer, 0, 4, cancellationToken).ConfigureAwait(false);
+ var version = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(0, 2));
+ var propsSize = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(2, 2));
+ var props = new byte[propsSize];
+ await stream
+ .ReadFullyAsync(props, 0, propsSize, cancellationToken)
+ .ConfigureAwait(false);
+ return new LzmaStream(
+ props,
+ stream,
+ Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1,
+ FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1)
+ ? -1
+ : Header.UncompressedSize
+ );
+ }
+ case ZipCompressionMethod.Xz:
+ {
+ return new XZStream(stream);
+ }
+ case ZipCompressionMethod.ZStandard:
+ {
+ return new DecompressionStream(stream);
+ }
+ case ZipCompressionMethod.PPMd:
+ {
+ var props = new byte[2];
+ await stream.ReadFullyAsync(props, 0, 2, cancellationToken).ConfigureAwait(false);
+ return new PpmdStream(new PpmdProperties(props), stream, false);
+ }
+ case ZipCompressionMethod.WinzipAes:
+ {
+ var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
+ if (data is null)
+ {
+ throw new InvalidFormatException("No Winzip AES extra data found.");
+ }
+
+ if (data.Length != 7)
+ {
+ throw new InvalidFormatException("Winzip data length is not 7.");
+ }
+
+ var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes);
+
+ if (compressedMethod != 0x01 && compressedMethod != 0x02)
+ {
+ throw new InvalidFormatException(
+ "Unexpected vendor version number for WinZip AES metadata"
+ );
+ }
+
+ var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2));
+ if (vendorId != 0x4541)
+ {
+ throw new InvalidFormatException(
+ "Unexpected vendor ID for WinZip AES metadata"
+ );
+ }
+
+ return await CreateDecompressionStreamAsync(
+ stream,
+ (ZipCompressionMethod)
+ BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)),
+ cancellationToken
+ );
+ }
+ default:
+ {
+ throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod);
+ }
+ }
+ }
}
diff --git a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs
index 865aba44a..7238dda5c 100644
--- a/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs
+++ b/src/SharpCompress/Common/Zip/ZipHeaderFactory.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
+using SharpCompress;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -34,6 +36,82 @@ IArchiveEncoding archiveEncoding
_archiveEncoding = archiveEncoding;
}
+ protected async ValueTask ReadHeader(
+ uint headerBytes,
+ AsyncBinaryReader reader,
+ bool zip64 = false
+ )
+ {
+ switch (headerBytes)
+ {
+ case ENTRY_HEADER_BYTES:
+ {
+ var entryHeader = new LocalEntryHeader(_archiveEncoding);
+ await entryHeader.Read(reader);
+ await LoadHeaderAsync(entryHeader, reader.BaseStream).ConfigureAwait(false);
+
+ _lastEntryHeader = entryHeader;
+ return entryHeader;
+ }
+ case DIRECTORY_START_HEADER_BYTES:
+ {
+ var entry = new DirectoryEntryHeader(_archiveEncoding);
+ await entry.Read(reader);
+ return entry;
+ }
+ case POST_DATA_DESCRIPTOR:
+ {
+ if (
+ _lastEntryHeader != null
+ && FlagUtility.HasFlag(
+ _lastEntryHeader.NotNull().Flags,
+ HeaderFlags.UsePostDataDescriptor
+ )
+ )
+ {
+ _lastEntryHeader.Crc = await reader.ReadUInt32Async();
+ _lastEntryHeader.CompressedSize = zip64
+ ? (long)await reader.ReadUInt64Async()
+ : await reader.ReadUInt32Async();
+ _lastEntryHeader.UncompressedSize = zip64
+ ? (long)await reader.ReadUInt64Async()
+ : await reader.ReadUInt32Async();
+ }
+ else
+ {
+ await reader.ReadBytesAsync(zip64 ? 20 : 12);
+ }
+ return null;
+ }
+ case DIGITAL_SIGNATURE:
+ return null;
+ case DIRECTORY_END_HEADER_BYTES:
+ {
+ var entry = new DirectoryEndHeader();
+ await entry.Read(reader);
+ return entry;
+ }
+ case SPLIT_ARCHIVE_HEADER_BYTES:
+ {
+ return new SplitHeader();
+ }
+ case ZIP64_END_OF_CENTRAL_DIRECTORY:
+ {
+ var entry = new Zip64DirectoryEndHeader();
+ await entry.Read(reader);
+ return entry;
+ }
+ case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR:
+ {
+ var entry = new Zip64DirectoryEndLocatorHeader();
+ await entry.Read(reader);
+ return entry;
+ }
+ default:
+ return null;
+ }
+ }
+
protected ZipHeader? ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false)
{
switch (headerBytes)
@@ -205,4 +283,82 @@ private void LoadHeader(ZipFileEntry entryHeader, Stream stream)
//}
}
+
+ ///
+ /// Loads encryption metadata and stream positioning for a header using async reads where needed.
+ ///
+ private async ValueTask LoadHeaderAsync(ZipFileEntry entryHeader, Stream stream)
+ {
+ if (FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.Encrypted))
+ {
+ if (
+ !entryHeader.IsDirectory
+ && entryHeader.CompressedSize == 0
+ && FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.UsePostDataDescriptor)
+ )
+ {
+ throw new NotSupportedException(
+ "SharpCompress cannot currently read non-seekable Zip Streams with encrypted data that has been written in a non-seekable manner."
+ );
+ }
+
+ if (_password is null)
+ {
+ throw new CryptographicException("No password supplied for encrypted zip.");
+ }
+
+ entryHeader.Password = _password;
+
+ if (entryHeader.CompressionMethod == ZipCompressionMethod.WinzipAes)
+ {
+ var data = entryHeader.Extra.SingleOrDefault(x =>
+ x.Type == ExtraDataType.WinZipAes
+ );
+ if (data != null)
+ {
+ var keySize = (WinzipAesKeySize)data.DataBytes[4];
+
+ var salt = new byte[WinzipAesEncryptionData.KeyLengthInBytes(keySize) / 2];
+ var passwordVerifyValue = new byte[2];
+ await stream.ReadExactAsync(salt, 0, salt.Length).ConfigureAwait(false);
+ await stream.ReadExactAsync(passwordVerifyValue, 0, 2).ConfigureAwait(false);
+
+ entryHeader.WinzipAesEncryptionData = new WinzipAesEncryptionData(
+ keySize,
+ salt,
+ passwordVerifyValue,
+ _password
+ );
+
+ entryHeader.CompressedSize -= (uint)(salt.Length + 2);
+ }
+ }
+ }
+
+ if (entryHeader.IsDirectory)
+ {
+ return;
+ }
+
+ switch (_mode)
+ {
+ case StreamingMode.Seekable:
+ {
+ entryHeader.DataStartPosition = stream.Position;
+ stream.Position += entryHeader.CompressedSize;
+ break;
+ }
+
+ case StreamingMode.Streaming:
+ {
+ entryHeader.PackedStream = stream;
+ break;
+ }
+
+ default:
+ {
+ throw new InvalidFormatException("Invalid StreamingMode");
+ }
+ }
+ }
}
diff --git a/src/SharpCompress/Compressors/ADC/ADCBase.cs b/src/SharpCompress/Compressors/ADC/ADCBase.cs
index 35301b526..ad5218986 100644
--- a/src/SharpCompress/Compressors/ADC/ADCBase.cs
+++ b/src/SharpCompress/Compressors/ADC/ADCBase.cs
@@ -104,7 +104,7 @@ public static int Decompress(byte[] input, out byte[]? output, int bufferSize =
/// Max size for decompressed data
/// Cancellation token
/// Result containing bytes read and decompressed data
- public static async Task DecompressAsync(
+ public static async ValueTask DecompressAsync(
byte[] input,
int bufferSize = 262144,
CancellationToken cancellationToken = default
@@ -117,7 +117,7 @@ public static async Task DecompressAsync(
/// Max size for decompressed data
/// Cancellation token
/// Result containing bytes read and decompressed data
- public static async Task DecompressAsync(
+ public static async ValueTask DecompressAsync(
Stream input,
int bufferSize = 262144,
CancellationToken cancellationToken = default
diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
index e2a757c62..d3c10f9b7 100644
--- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
+++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
@@ -400,7 +400,7 @@ private void finish()
}
}
- private async Task finishAsync(CancellationToken cancellationToken = default)
+ private async ValueTask finishAsync(CancellationToken cancellationToken = default)
{
if (_z is null)
{
@@ -646,7 +646,9 @@ private string ReadZeroTerminatedString()
return _encoding.GetString(buffer, 0, buffer.Length);
}
- private async Task ReadZeroTerminatedStringAsync(CancellationToken cancellationToken)
+ private async ValueTask ReadZeroTerminatedStringAsync(
+ CancellationToken cancellationToken
+ )
{
var list = new List();
var done = false;
@@ -729,7 +731,9 @@ private int _ReadAndValidateGzipHeader()
return totalBytesRead;
}
- private async Task _ReadAndValidateGzipHeaderAsync(CancellationToken cancellationToken)
+ private async ValueTask _ReadAndValidateGzipHeaderAsync(
+ CancellationToken cancellationToken
+ )
{
var totalBytesRead = 0;
diff --git a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs
index 276456fba..0866f718f 100644
--- a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs
+++ b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs
@@ -87,7 +87,7 @@ public void ReleaseStream()
_stream = null;
}
- public async Task ReleaseStreamAsync(CancellationToken cancellationToken = default)
+ public async ValueTask ReleaseStreamAsync(CancellationToken cancellationToken = default)
{
await FlushAsync(cancellationToken).ConfigureAwait(false);
_stream = null;
@@ -112,7 +112,7 @@ private void Flush()
_streamPos = _pos;
}
- private async Task FlushAsync(CancellationToken cancellationToken = default)
+ private async ValueTask FlushAsync(CancellationToken cancellationToken = default)
{
if (_stream is null)
{
@@ -303,7 +303,7 @@ public int CopyStream(Stream stream, int len)
return len - size;
}
- public async Task CopyStreamAsync(
+ public async ValueTask CopyStreamAsync(
Stream stream,
int len,
CancellationToken cancellationToken = default
diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs
index 77e4c4947..26079966c 100644
--- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs
+++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs
@@ -429,7 +429,7 @@ private async ValueTask DecodeChunkHeaderAsync(CancellationToken cancellationTok
{
var controlBuffer = new byte[1];
await _inputStream
- .ReadExactlyAsync(controlBuffer, 0, 1, cancellationToken)
+ .ReadExactAsync(controlBuffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
var control = controlBuffer[0];
_inputPosition++;
@@ -458,13 +458,13 @@ await _inputStream
_availableBytes = (control & 0x1F) << 16;
var buffer = new byte[2];
await _inputStream
- .ReadExactlyAsync(buffer, 0, 2, cancellationToken)
+ .ReadExactAsync(buffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
_availableBytes += (buffer[0] << 8) + buffer[1] + 1;
_inputPosition += 2;
await _inputStream
- .ReadExactlyAsync(buffer, 0, 2, cancellationToken)
+ .ReadExactAsync(buffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
_rangeDecoderLimit = (buffer[0] << 8) + buffer[1] + 1;
_inputPosition += 2;
@@ -473,7 +473,7 @@ await _inputStream
{
_needProps = false;
await _inputStream
- .ReadExactlyAsync(controlBuffer, 0, 1, cancellationToken)
+ .ReadExactAsync(controlBuffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
Properties[0] = controlBuffer[0];
_inputPosition++;
@@ -502,7 +502,7 @@ await _inputStream
_uncompressedChunk = true;
var buffer = new byte[2];
await _inputStream
- .ReadExactlyAsync(buffer, 0, 2, cancellationToken)
+ .ReadExactAsync(buffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
_availableBytes = (buffer[0] << 8) + buffer[1] + 1;
_inputPosition += 2;
diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs b/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs
index 19b0f3748..b57cd53f5 100644
--- a/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs
+++ b/src/SharpCompress/Compressors/LZMA/Utilites/Utils.cs
@@ -53,39 +53,4 @@ public static void Assert(bool expression)
throw new InvalidOperationException("Assertion failed.");
}
}
-
- public static void ReadExact(this Stream stream, byte[] buffer, int offset, int length)
- {
- if (stream is null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
-
- if (buffer is null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
-
- if (offset < 0 || offset > buffer.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(offset));
- }
-
- if (length < 0 || length > buffer.Length - offset)
- {
- throw new ArgumentOutOfRangeException(nameof(length));
- }
-
- while (length > 0)
- {
- var fetched = stream.Read(buffer, offset, length);
- if (fetched <= 0)
- {
- throw new EndOfStreamException();
- }
-
- offset += fetched;
- length -= fetched;
- }
- }
}
diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs
index a4869075a..7f258bc5b 100644
--- a/src/SharpCompress/Compressors/Rar/RarStream.cs
+++ b/src/SharpCompress/Compressors/Rar/RarStream.cs
@@ -68,7 +68,7 @@ public void Initialize()
_position = 0;
}
- public async Task InitializeAsync(CancellationToken cancellationToken = default)
+ public async ValueTask InitializeAsync(CancellationToken cancellationToken = default)
{
fetch = true;
await unpack.DoUnpackAsync(fileHeader, readStream, this, cancellationToken);
diff --git a/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs b/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs
index f5613d661..6f7a863ba 100644
--- a/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs
+++ b/src/SharpCompress/Compressors/Xz/MultiByteIntegers.cs
@@ -58,7 +58,7 @@ public static async Task ReadXZIntegerAsync(
MaxBytes = 9;
}
- var LastByte = await ReadByteAsync(reader, cancellationToken).ConfigureAwait(false);
+ var LastByte = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
var Output = (ulong)LastByte & 0x7F;
var i = 0;
@@ -69,7 +69,7 @@ public static async Task ReadXZIntegerAsync(
throw new InvalidFormatException();
}
- LastByte = await ReadByteAsync(reader, cancellationToken).ConfigureAwait(false);
+ LastByte = await reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
if (LastByte == 0)
{
throw new InvalidFormatException();
@@ -79,37 +79,4 @@ public static async Task ReadXZIntegerAsync(
}
return Output;
}
-
- public static async Task ReadByteAsync(
- this BinaryReader reader,
- CancellationToken cancellationToken = default
- )
- {
- var buffer = new byte[1];
- var bytesRead = await reader
- .BaseStream.ReadAsync(buffer, 0, 1, cancellationToken)
- .ConfigureAwait(false);
- if (bytesRead != 1)
- {
- throw new EndOfStreamException();
- }
- return buffer[0];
- }
-
- public static async Task ReadBytesAsync(
- this BinaryReader reader,
- int count,
- CancellationToken cancellationToken = default
- )
- {
- var buffer = new byte[count];
- var bytesRead = await reader
- .BaseStream.ReadAsync(buffer, 0, count, cancellationToken)
- .ConfigureAwait(false);
- if (bytesRead != count)
- {
- throw new EndOfStreamException();
- }
- return buffer;
- }
}
diff --git a/src/SharpCompress/Compressors/Xz/XZBlock.cs b/src/SharpCompress/Compressors/Xz/XZBlock.cs
index 45e11745a..7c18e3b41 100644
--- a/src/SharpCompress/Compressors/Xz/XZBlock.cs
+++ b/src/SharpCompress/Compressors/Xz/XZBlock.cs
@@ -132,7 +132,7 @@ private void SkipPadding()
_paddingSkipped = true;
}
- private async Task SkipPaddingAsync(CancellationToken cancellationToken = default)
+ private async ValueTask SkipPaddingAsync(CancellationToken cancellationToken = default)
{
var bytes = (BaseStream.Position - _startPosition) % 4;
if (bytes > 0)
@@ -158,7 +158,7 @@ private void CheckCrc()
_crcChecked = true;
}
- private async Task CheckCrcAsync(CancellationToken cancellationToken = default)
+ private async ValueTask CheckCrcAsync(CancellationToken cancellationToken = default)
{
var crc = new byte[_checkSize];
await BaseStream.ReadAsync(crc, 0, _checkSize, cancellationToken).ConfigureAwait(false);
@@ -194,7 +194,7 @@ private void LoadHeader()
HeaderIsLoaded = true;
}
- private async Task LoadHeaderAsync(CancellationToken cancellationToken = default)
+ private async ValueTask LoadHeaderAsync(CancellationToken cancellationToken = default)
{
await ReadHeaderSizeAsync(cancellationToken).ConfigureAwait(false);
var headerCache = await CacheHeaderAsync(cancellationToken).ConfigureAwait(false);
@@ -218,7 +218,7 @@ private void ReadHeaderSize()
}
}
- private async Task ReadHeaderSizeAsync(CancellationToken cancellationToken = default)
+ private async ValueTask ReadHeaderSizeAsync(CancellationToken cancellationToken = default)
{
var buffer = new byte[1];
await BaseStream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
@@ -249,7 +249,7 @@ private byte[] CacheHeader()
return blockHeaderWithoutCrc;
}
- private async Task CacheHeaderAsync(CancellationToken cancellationToken = default)
+ private async ValueTask CacheHeaderAsync(CancellationToken cancellationToken = default)
{
var blockHeaderWithoutCrc = new byte[BlockHeaderSize - 4];
blockHeaderWithoutCrc[0] = _blockHeaderSizeByte;
diff --git a/src/SharpCompress/Compressors/Xz/XZFooter.cs b/src/SharpCompress/Compressors/Xz/XZFooter.cs
index 09c95b1a0..67b68be7c 100644
--- a/src/SharpCompress/Compressors/Xz/XZFooter.cs
+++ b/src/SharpCompress/Compressors/Xz/XZFooter.cs
@@ -62,7 +62,7 @@ public void Process()
}
}
- public async Task ProcessAsync(CancellationToken cancellationToken = default)
+ public async ValueTask ProcessAsync(CancellationToken cancellationToken = default)
{
var crc = await _reader
.BaseStream.ReadLittleEndianUInt32Async(cancellationToken)
diff --git a/src/SharpCompress/Compressors/Xz/XZHeader.cs b/src/SharpCompress/Compressors/Xz/XZHeader.cs
index 1aee619bb..39f706b1d 100644
--- a/src/SharpCompress/Compressors/Xz/XZHeader.cs
+++ b/src/SharpCompress/Compressors/Xz/XZHeader.cs
@@ -41,7 +41,7 @@ public void Process()
ProcessStreamFlags();
}
- public async Task ProcessAsync(CancellationToken cancellationToken = default)
+ public async ValueTask ProcessAsync(CancellationToken cancellationToken = default)
{
CheckMagicBytes(await _reader.ReadBytesAsync(6, cancellationToken).ConfigureAwait(false));
await ProcessStreamFlagsAsync(cancellationToken).ConfigureAwait(false);
@@ -65,7 +65,7 @@ private void ProcessStreamFlags()
}
}
- private async Task ProcessStreamFlagsAsync(CancellationToken cancellationToken = default)
+ private async ValueTask ProcessStreamFlagsAsync(CancellationToken cancellationToken = default)
{
var streamFlags = await _reader.ReadBytesAsync(2, cancellationToken).ConfigureAwait(false);
var crc = await _reader
diff --git a/src/SharpCompress/Compressors/Xz/XZIndex.cs b/src/SharpCompress/Compressors/Xz/XZIndex.cs
index 9c5230911..3bc8ea422 100644
--- a/src/SharpCompress/Compressors/Xz/XZIndex.cs
+++ b/src/SharpCompress/Compressors/Xz/XZIndex.cs
@@ -41,7 +41,7 @@ public static XZIndex FromStream(Stream stream, bool indexMarkerAlreadyVerified)
return index;
}
- public static async Task FromStreamAsync(
+ public static async ValueTask FromStreamAsync(
Stream stream,
bool indexMarkerAlreadyVerified,
CancellationToken cancellationToken = default
@@ -71,7 +71,7 @@ public void Process()
VerifyCrc32();
}
- public async Task ProcessAsync(CancellationToken cancellationToken = default)
+ public async ValueTask ProcessAsync(CancellationToken cancellationToken = default)
{
if (!_indexMarkerAlreadyVerified)
{
@@ -100,7 +100,7 @@ private void VerifyIndexMarker()
}
}
- private async Task VerifyIndexMarkerAsync(CancellationToken cancellationToken = default)
+ private async ValueTask VerifyIndexMarkerAsync(CancellationToken cancellationToken = default)
{
var marker = await _reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
if (marker != 0)
@@ -122,7 +122,7 @@ private void SkipPadding()
}
}
- private async Task SkipPaddingAsync(CancellationToken cancellationToken = default)
+ private async ValueTask SkipPaddingAsync(CancellationToken cancellationToken = default)
{
var bytes = (int)(_reader.BaseStream.Position - StreamStartPosition) % 4;
if (bytes > 0)
@@ -143,7 +143,7 @@ private void VerifyCrc32()
// TODO verify this matches
}
- private async Task VerifyCrc32Async(CancellationToken cancellationToken = default)
+ private async ValueTask VerifyCrc32Async(CancellationToken cancellationToken = default)
{
var crc = await _reader
.BaseStream.ReadLittleEndianUInt32Async(cancellationToken)
diff --git a/src/SharpCompress/Compressors/Xz/XZStream.cs b/src/SharpCompress/Compressors/Xz/XZStream.cs
index ebd0924ed..1e3051d6b 100644
--- a/src/SharpCompress/Compressors/Xz/XZStream.cs
+++ b/src/SharpCompress/Compressors/Xz/XZStream.cs
@@ -142,7 +142,7 @@ private void ReadHeader()
HeaderIsRead = true;
}
- private async Task ReadHeaderAsync(CancellationToken cancellationToken = default)
+ private async ValueTask ReadHeaderAsync(CancellationToken cancellationToken = default)
{
Header = await XZHeader
.FromStreamAsync(BaseStream, cancellationToken)
@@ -153,7 +153,7 @@ private async Task ReadHeaderAsync(CancellationToken cancellationToken = default
private void ReadIndex() => Index = XZIndex.FromStream(BaseStream, true);
- private async Task ReadIndexAsync(CancellationToken cancellationToken = default) =>
+ private async ValueTask ReadIndexAsync(CancellationToken cancellationToken = default) =>
Index = await XZIndex
.FromStreamAsync(BaseStream, true, cancellationToken)
.ConfigureAwait(false);
@@ -162,7 +162,7 @@ private async Task ReadIndexAsync(CancellationToken cancellationToken = default)
private void ReadFooter() => Footer = XZFooter.FromStream(BaseStream);
// TODO verify footer
- private async Task ReadFooterAsync(CancellationToken cancellationToken = default) =>
+ private async ValueTask ReadFooterAsync(CancellationToken cancellationToken = default) =>
Footer = await XZFooter
.FromStreamAsync(BaseStream, cancellationToken)
.ConfigureAwait(false);
@@ -202,7 +202,7 @@ private int ReadBlocks(byte[] buffer, int offset, int count)
return bytesRead;
}
- private async Task ReadBlocksAsync(
+ private async ValueTask ReadBlocksAsync(
byte[] buffer,
int offset,
int count,
diff --git a/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs b/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs
index 92de03b34..af8865b41 100644
--- a/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs
+++ b/src/SharpCompress/Compressors/ZStandard/CompressionStream.cs
@@ -77,7 +77,7 @@ public void LoadDictionary(byte[] dict)
#if !NETSTANDARD2_0 && !NETFRAMEWORK
public override async ValueTask DisposeAsync()
#else
- public async Task DisposeAsync()
+ public async ValueTask DisposeAsync()
#endif
{
if (compressor == null)
@@ -137,7 +137,7 @@ await FlushInternalAsync(ZSTD_EndDirective.ZSTD_e_flush, cancellationToken)
private void FlushInternal(ZSTD_EndDirective directive) => WriteInternal(null, directive);
- private async Task FlushInternalAsync(
+ private async ValueTask FlushInternalAsync(
ZSTD_EndDirective directive,
CancellationToken cancellationToken = default
) => await WriteInternalAsync(null, directive, cancellationToken).ConfigureAwait(false);
@@ -183,7 +183,7 @@ private async ValueTask WriteInternalAsync(
CancellationToken cancellationToken = default
)
#else
- private async Task WriteInternalAsync(
+ private async ValueTask WriteInternalAsync(
ReadOnlyMemory? buffer,
ZSTD_EndDirective directive,
CancellationToken cancellationToken = default
@@ -235,14 +235,16 @@ await WriteInternalAsync(buffer, ZSTD_EndDirective.ZSTD_e_continue, cancellation
.ConfigureAwait(false);
#else
- public override Task WriteAsync(
+ public override async Task WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
- ) => WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken);
+ ) =>
+ await WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken)
+ .ConfigureAwait(false);
- public async Task WriteAsync(
+ public async ValueTask WriteAsync(
ReadOnlyMemory buffer,
CancellationToken cancellationToken = default
) =>
diff --git a/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs b/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
index 9864a8055..78af43513 100644
--- a/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
+++ b/src/SharpCompress/Compressors/ZStandard/DecompressionStream.cs
@@ -177,9 +177,9 @@ public override Task ReadAsync(
int offset,
int count,
CancellationToken cancellationToken
- ) => ReadAsync(new Memory(buffer, offset, count), cancellationToken);
+ ) => ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask();
- public async Task ReadAsync(
+ public async ValueTask ReadAsync(
Memory buffer,
CancellationToken cancellationToken = default
)
diff --git a/src/SharpCompress/Factories/AceFactory.cs b/src/SharpCompress/Factories/AceFactory.cs
index 5b80ae24f..95f647ddb 100644
--- a/src/SharpCompress/Factories/AceFactory.cs
+++ b/src/SharpCompress/Factories/AceFactory.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Ace.Headers;
@@ -26,12 +27,21 @@ public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
- )
- {
- return AceHeader.IsArchive(stream);
- }
+ ) => AceHeader.IsArchive(stream);
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
AceReader.Open(stream, options);
+
+ public ValueTask OpenReaderAsync(
+ Stream stream,
+ ReaderOptions? options,
+ CancellationToken cancellationToken = default
+ ) => new(AceReader.Open(stream, options));
+
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ ) => new(IsArchive(stream, password, bufferSize));
}
}
diff --git a/src/SharpCompress/Factories/ArcFactory.cs b/src/SharpCompress/Factories/ArcFactory.cs
index b5180afae..37984112a 100644
--- a/src/SharpCompress/Factories/ArcFactory.cs
+++ b/src/SharpCompress/Factories/ArcFactory.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Security.Cryptography;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Readers;
@@ -42,5 +43,17 @@ public override bool IsArchive(
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
ArcReader.Open(stream, options);
+
+ public ValueTask OpenReaderAsync(
+ Stream stream,
+ ReaderOptions? options,
+ CancellationToken cancellationToken = default
+ ) => new(ArcReader.Open(stream, options));
+
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ ) => new(IsArchive(stream, password, bufferSize));
}
}
diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs
index f6f7a3934..6e5f7a309 100644
--- a/src/SharpCompress/Factories/ArjFactory.cs
+++ b/src/SharpCompress/Factories/ArjFactory.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Arj.Headers;
@@ -33,5 +34,17 @@ public override bool IsArchive(
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
ArjReader.Open(stream, options);
+
+ public ValueTask OpenReaderAsync(
+ Stream stream,
+ ReaderOptions? options,
+ CancellationToken cancellationToken = default
+ ) => new(ArjReader.Open(stream, options));
+
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ ) => new(IsArchive(stream, password, bufferSize));
}
}
diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs
index 4651ccb22..f28f3d249 100644
--- a/src/SharpCompress/Factories/Factory.cs
+++ b/src/SharpCompress/Factories/Factory.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;
@@ -57,6 +59,24 @@ public abstract bool IsArchive(
int bufferSize = ReaderOptions.DefaultBufferSize
);
+ public abstract ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ );
+
+ ///
+ public virtual ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(IsArchive(stream, password, bufferSize));
+ }
+
///
public virtual FileInfo? GetFilePart(int index, FileInfo part1) => null;
@@ -92,4 +112,34 @@ out IReader? reader
return false;
}
+
+ internal virtual async ValueTask<(bool, IAsyncReader?)> TryOpenReaderAsync(
+ SharpCompressStream stream,
+ ReaderOptions options,
+ CancellationToken cancellationToken
+ )
+ {
+ if (this is IReaderFactory readerFactory)
+ {
+ long pos = ((IStreamStack)stream).GetPosition();
+
+ if (
+ await IsArchiveAsync(
+ stream,
+ options.Password,
+ options.BufferSize,
+ cancellationToken
+ )
+ )
+ {
+ ((IStreamStack)stream).StackSeek(pos);
+ return (
+ true,
+ await readerFactory.OpenReaderAsync(stream, options, cancellationToken)
+ );
+ }
+ }
+
+ return (false, null);
+ }
}
diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs
index 17f344cf0..48f5c63e5 100644
--- a/src/SharpCompress/Factories/GZipFactory.cs
+++ b/src/SharpCompress/Factories/GZipFactory.cs
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.GZip;
using SharpCompress.Archives.Tar;
@@ -46,6 +48,14 @@ public override bool IsArchive(
int bufferSize = ReaderOptions.DefaultBufferSize
) => GZipArchive.IsGZipFile(stream);
+ ///
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize,
+ CancellationToken cancellationToken = default
+ ) => GZipArchive.IsGZipFileAsync(stream, cancellationToken);
+
#endregion
#region IArchiveFactory
@@ -54,10 +64,30 @@ public override bool IsArchive(
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
GZipArchive.Open(stream, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => GZipArchive.OpenAsync(stream, readerOptions, cancellationToken);
+
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ ) => new(IsArchive(stream, password, bufferSize));
+
///
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
GZipArchive.Open(fileInfo, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => GZipArchive.OpenAsync(fileInfo, readerOptions, cancellationToken);
+
#endregion
#region IMultiArchiveFactory
@@ -66,10 +96,24 @@ public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) =>
GZipArchive.Open(streams, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => GZipArchive.OpenAsync(streams, readerOptions, cancellationToken);
+
///
public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) =>
GZipArchive.Open(fileInfos, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => GZipArchive.OpenAsync(fileInfos, readerOptions, cancellationToken);
+
#endregion
#region IReaderFactory
@@ -108,6 +152,17 @@ out IReader? reader
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
GZipReader.Open(stream, options);
+ ///
+ public ValueTask OpenReaderAsync(
+ Stream stream,
+ ReaderOptions? options,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(GZipReader.Open(stream, options));
+ }
+
#endregion
#region IWriterFactory
@@ -122,6 +177,17 @@ public IWriter Open(Stream stream, WriterOptions writerOptions)
return new GZipWriter(stream, new GZipWriterOptions(writerOptions));
}
+ ///
+ public ValueTask OpenAsync(
+ Stream stream,
+ WriterOptions writerOptions,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(Open(stream, writerOptions));
+ }
+
#endregion
#region IWriteableArchiveFactory
diff --git a/src/SharpCompress/Factories/IFactory.cs b/src/SharpCompress/Factories/IFactory.cs
index 63d5eeec6..a9dd4f5ac 100644
--- a/src/SharpCompress/Factories/IFactory.cs
+++ b/src/SharpCompress/Factories/IFactory.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.Factories;
@@ -42,6 +44,20 @@ bool IsArchive(
int bufferSize = ReaderOptions.DefaultBufferSize
);
+ ///
+ /// Returns true if the stream represents an archive of the format defined by this type asynchronously.
+ ///
+ /// A stream, pointing to the beginning of the archive.
+ /// optional password
+ /// buffer size for reading
+ /// cancellation token
+ ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize,
+ CancellationToken cancellationToken = default
+ );
+
///
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
///
diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs
index 610999057..fb9e03abb 100644
--- a/src/SharpCompress/Factories/RarFactory.cs
+++ b/src/SharpCompress/Factories/RarFactory.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.Rar;
using SharpCompress.Common;
@@ -47,10 +49,30 @@ public override bool IsArchive(
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
RarArchive.Open(stream, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => RarArchive.OpenAsync(stream, readerOptions, cancellationToken);
+
///
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
RarArchive.Open(fileInfo, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => RarArchive.OpenAsync(fileInfo, readerOptions, cancellationToken);
+
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ ) => new(IsArchive(stream, password, bufferSize));
+
#endregion
#region IMultiArchiveFactory
@@ -59,10 +81,24 @@ public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) =>
RarArchive.Open(streams, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ IReadOnlyList streams,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => RarArchive.OpenAsync(streams, readerOptions, cancellationToken);
+
///
public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) =>
RarArchive.Open(fileInfos, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ IReadOnlyList fileInfos,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => RarArchive.OpenAsync(fileInfos, readerOptions, cancellationToken);
+
#endregion
#region IReaderFactory
@@ -71,5 +107,16 @@ public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOpt
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
RarReader.Open(stream, options);
+ ///
+ public ValueTask OpenReaderAsync(
+ Stream stream,
+ ReaderOptions? options,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return new(RarReader.Open(stream, options));
+ }
+
#endregion
}
diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs
index 18dedbfdd..c387e3b30 100644
--- a/src/SharpCompress/Factories/SevenZipFactory.cs
+++ b/src/SharpCompress/Factories/SevenZipFactory.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Common;
@@ -42,10 +44,30 @@ public override bool IsArchive(
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
SevenZipArchive.Open(stream, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ Stream stream,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => SevenZipArchive.OpenAsync(stream, readerOptions, cancellationToken);
+
///
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
SevenZipArchive.Open(fileInfo, readerOptions);
+ ///
+ public ValueTask OpenAsync(
+ FileInfo fileInfo,
+ ReaderOptions? readerOptions = null,
+ CancellationToken cancellationToken = default
+ ) => SevenZipArchive.OpenAsync(fileInfo, readerOptions, cancellationToken);
+
+ public override ValueTask IsArchiveAsync(
+ Stream stream,
+ string? password = null,
+ int bufferSize = ReaderOptions.DefaultBufferSize
+ ) => new(IsArchive(stream, password, bufferSize));
+
#endregion
#region IMultiArchiveFactory
@@ -54,10 +76,24 @@ public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
public IArchive Open(IReadOnlyList