diff --git a/src/SharpCompress/Archives/AutoArchiveFactory.cs b/src/SharpCompress/Archives/AutoArchiveFactory.cs index 472dc4bb..0c36d1c0 100644 --- a/src/SharpCompress/Archives/AutoArchiveFactory.cs +++ b/src/SharpCompress/Archives/AutoArchiveFactory.cs @@ -8,7 +8,7 @@ using SharpCompress.Readers; namespace SharpCompress.Archives; -class AutoArchiveFactory : IArchiveFactory +internal class AutoArchiveFactory : IArchiveFactory { public string Name => nameof(AutoArchiveFactory); @@ -22,7 +22,7 @@ class AutoArchiveFactory : IArchiveFactory int bufferSize = ReaderOptions.DefaultBufferSize ) => throw new NotSupportedException(); - public Task IsArchiveAsync( + public ValueTask IsArchiveAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, @@ -34,18 +34,18 @@ class AutoArchiveFactory : IArchiveFactory public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => ArchiveFactory.Open(stream, readerOptions); - public Task OpenAsync( + public async ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => ArchiveFactory.OpenAsync(stream, readerOptions, cancellationToken); + ) => await ArchiveFactory.OpenAsync(stream, readerOptions, cancellationToken); public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => ArchiveFactory.Open(fileInfo, readerOptions); - public Task OpenAsync( + public async ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => ArchiveFactory.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) => await ArchiveFactory.OpenAsync(fileInfo, readerOptions, cancellationToken); } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index f67f68ac..6895cfac 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -108,14 +108,14 @@ public class GZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, readerOptions)).ConfigureAwait(false); + return new(Open(stream, readerOptions)); } /// @@ -124,14 +124,14 @@ public class GZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfo, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfo, readerOptions)); } /// @@ -140,14 +140,14 @@ public class GZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(streams, readerOptions)).ConfigureAwait(false); + return new(Open(streams, readerOptions)); } /// @@ -156,14 +156,14 @@ public class GZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfos, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfos, readerOptions)); } public static GZipArchive Create() => new(); @@ -231,7 +231,7 @@ public class GZipArchive : AbstractWritableArchive return true; } - public static async Task IsGZipFileAsync( + public static async ValueTask IsGZipFileAsync( Stream stream, CancellationToken cancellationToken = default ) diff --git a/src/SharpCompress/Archives/IArchiveAsyncExtensions.cs b/src/SharpCompress/Archives/IArchiveAsyncExtensions.cs new file mode 100644 index 00000000..ca3db1cf --- /dev/null +++ b/src/SharpCompress/Archives/IArchiveAsyncExtensions.cs @@ -0,0 +1,100 @@ +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 IArchiveAsyncExtensions +{ + /// The archive to extract. + extension(IArchiveAsync archive) + { + /// + /// 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 (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip) + { + 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 async Task WriteToDirectoryAsyncInternal( + 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/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs index 0d39c6e2..c1d2ac98 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 @@ public static class IArchiveExtensions ); } } - - /// - /// 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 c456cd60..40e25d00 100644 --- a/src/SharpCompress/Archives/IArchiveFactory.cs +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -34,7 +34,7 @@ public interface IArchiveFactory : IFactory /// An open, readable and seekable stream. /// reading options. /// Cancellation token. - Task OpenAsync( + ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -53,7 +53,7 @@ public interface IArchiveFactory : IFactory /// the file to open. /// reading options. /// Cancellation token. - Task OpenAsync( + ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Archives/IMultiArchiveFactory.cs b/src/SharpCompress/Archives/IMultiArchiveFactory.cs index 313dc8af..2c96ef53 100644 --- a/src/SharpCompress/Archives/IMultiArchiveFactory.cs +++ b/src/SharpCompress/Archives/IMultiArchiveFactory.cs @@ -35,7 +35,7 @@ public interface IMultiArchiveFactory : IFactory /// /// reading options. /// Cancellation token. - Task OpenAsync( + ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -54,7 +54,7 @@ public interface IMultiArchiveFactory : IFactory /// /// reading options. /// Cancellation token. - Task OpenAsync( + ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index dccbf8d2..45e97f93 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -189,14 +189,14 @@ public class RarArchive : AbstractArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, readerOptions)).ConfigureAwait(false); + return new(Open(stream, readerOptions)); } /// @@ -205,14 +205,14 @@ public class RarArchive : AbstractArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfo, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfo, readerOptions)); } /// @@ -221,14 +221,14 @@ public class RarArchive : AbstractArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(streams, readerOptions)).ConfigureAwait(false); + return new(Open(streams, readerOptions)); } /// @@ -237,14 +237,14 @@ public class RarArchive : AbstractArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfos, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfos, readerOptions)); } public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 24c567f6..f73621e6 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -111,14 +111,14 @@ public class SevenZipArchive : AbstractArchive /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, readerOptions)).ConfigureAwait(false); + return new(Open(stream, readerOptions)); } /// @@ -127,14 +127,14 @@ public class SevenZipArchive : AbstractArchive /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfo, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfo, readerOptions)); } /// @@ -143,14 +143,14 @@ public class SevenZipArchive : AbstractArchive /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(streams, readerOptions)).ConfigureAwait(false); + return new(Open(streams, readerOptions)); } /// @@ -159,14 +159,14 @@ public class SevenZipArchive : AbstractArchive /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfos, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfos, readerOptions)); } /// diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index ac5ad110..9423c915 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -109,14 +109,14 @@ public class TarArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, readerOptions)).ConfigureAwait(false); + return new(Open(stream, readerOptions)); } /// @@ -125,14 +125,14 @@ public class TarArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfo, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfo, readerOptions)); } /// @@ -141,14 +141,14 @@ public class TarArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(streams, readerOptions)).ConfigureAwait(false); + return new(Open(streams, readerOptions)); } /// @@ -157,14 +157,14 @@ public class TarArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfos, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfos, readerOptions)); } public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index e4c3f273..328aa7e0 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -130,14 +130,14 @@ public class ZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, readerOptions)).ConfigureAwait(false); + return new(Open(stream, readerOptions)); } /// @@ -146,14 +146,14 @@ public class ZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfo, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfo, readerOptions)); } /// @@ -162,14 +162,14 @@ public class ZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(streams, readerOptions)).ConfigureAwait(false); + return new(Open(streams, readerOptions)); } /// @@ -178,14 +178,14 @@ public class ZipArchive : AbstractWritableArchive /// /// /// - public static async Task OpenAsync( + public static ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(fileInfos, readerOptions)).ConfigureAwait(false); + return new(Open(fileInfos, readerOptions)); } public static bool IsZipFile( @@ -283,7 +283,7 @@ public class ZipArchive : AbstractWritableArchive } } - public static async Task IsZipFileAsync( + public static async ValueTask IsZipFileAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, @@ -319,7 +319,7 @@ public class ZipArchive : AbstractWritableArchive } } - public static async Task IsZipMultiAsync( + public static async ValueTask IsZipMultiAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, @@ -345,7 +345,8 @@ public class ZipArchive : AbstractWritableArchive var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); ZipHeader? x = null; await foreach ( - var h in z.ReadSeekableHeader(stream).WithCancellation(cancellationToken) + var h in z.ReadSeekableHeaderAsync(stream) + .WithCancellation(cancellationToken) ) { x = h; @@ -451,6 +452,59 @@ public class ZipArchive : AbstractWritableArchive } } + protected override async IAsyncEnumerable 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( diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs index a6baf34b..81d419f2 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs @@ -13,9 +13,17 @@ public class ZipArchiveEntry : ZipEntry, IArchiveEntry public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull(); - public virtual Task OpenEntryStreamAsync( + public virtual async Task 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/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs index e7572711..f2a7d9de 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 class SeekableZipFilePart : ZipFilePart return base.GetCompressedStream(); } + internal override async Task 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 170950b2..38441c27 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs @@ -19,11 +19,11 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory internal SeekableZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding) : base(StreamingMode.Seekable, password, archiveEncoding) { } - internal async IAsyncEnumerable ReadSeekableHeader(Stream stream) + internal async IAsyncEnumerable ReadSeekableHeaderAsync(Stream stream) { var reader = new AsyncBinaryReader(stream); - await SeekBackToHeader(stream, reader); + await SeekBackToHeaderAsync(stream, reader); var eocd_location = stream.Position; var entry = new DirectoryEndHeader(); @@ -153,6 +153,73 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory } } + 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++) @@ -166,7 +233,7 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory return true; } - private static async ValueTask SeekBackToHeader(Stream stream, AsyncBinaryReader reader) + private static async ValueTask SeekBackToHeaderAsync(Stream stream, AsyncBinaryReader reader) { // Minimum EOCD length if (stream.Length < MINIMUM_EOCD_LENGTH) @@ -270,4 +337,31 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory } 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/Factories/AceFactory.cs b/src/SharpCompress/Factories/AceFactory.cs index fe8d8f9c..02a6e489 100644 --- a/src/SharpCompress/Factories/AceFactory.cs +++ b/src/SharpCompress/Factories/AceFactory.cs @@ -35,10 +35,10 @@ namespace SharpCompress.Factories public IReader OpenReader(Stream stream, ReaderOptions? options) => AceReader.Open(stream, options); - public Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default - ) => Task.FromResult(OpenReader(stream, options)); + ) => new(OpenReader(stream, options)); } } diff --git a/src/SharpCompress/Factories/ArcFactory.cs b/src/SharpCompress/Factories/ArcFactory.cs index 18065afd..f497509a 100644 --- a/src/SharpCompress/Factories/ArcFactory.cs +++ b/src/SharpCompress/Factories/ArcFactory.cs @@ -44,14 +44,14 @@ namespace SharpCompress.Factories public IReader OpenReader(Stream stream, ReaderOptions? options) => ArcReader.Open(stream, options); - public async Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(OpenReader(stream, options)).ConfigureAwait(false); + return new(OpenReader(stream, options)); } } } diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs index 420e6ae6..0f69bcab 100644 --- a/src/SharpCompress/Factories/ArjFactory.cs +++ b/src/SharpCompress/Factories/ArjFactory.cs @@ -35,14 +35,14 @@ namespace SharpCompress.Factories public IReader OpenReader(Stream stream, ReaderOptions? options) => ArjReader.Open(stream, options); - public async Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(OpenReader(stream, options)).ConfigureAwait(false); + return new(OpenReader(stream, options)); } } } diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs index 45e801b8..7662314c 100644 --- a/src/SharpCompress/Factories/Factory.cs +++ b/src/SharpCompress/Factories/Factory.cs @@ -60,7 +60,7 @@ public abstract class Factory : IFactory ); /// - public virtual Task IsArchiveAsync( + public virtual ValueTask IsArchiveAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, @@ -68,7 +68,7 @@ public abstract class Factory : IFactory ) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(IsArchive(stream, password, bufferSize)); + return new(IsArchive(stream, password, bufferSize)); } /// diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index 34085df3..7fc14d75 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -49,7 +49,7 @@ public class GZipFactory ) => GZipArchive.IsGZipFile(stream); /// - public override Task IsArchiveAsync( + public override ValueTask IsArchiveAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, @@ -65,7 +65,7 @@ public class GZipFactory GZipArchive.Open(stream, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -76,7 +76,7 @@ public class GZipFactory GZipArchive.Open(fileInfo, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -91,7 +91,7 @@ public class GZipFactory GZipArchive.Open(streams, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -102,7 +102,7 @@ public class GZipFactory GZipArchive.Open(fileInfos, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -147,14 +147,14 @@ public class GZipFactory GZipReader.Open(stream, options); /// - public async Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(OpenReader(stream, options)).ConfigureAwait(false); + return new(OpenReader(stream, options)); } #endregion @@ -172,14 +172,14 @@ public class GZipFactory } /// - public async Task OpenAsync( + public ValueTask OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, writerOptions)).ConfigureAwait(false); + return new(Open(stream, writerOptions)); } #endregion diff --git a/src/SharpCompress/Factories/IFactory.cs b/src/SharpCompress/Factories/IFactory.cs index 47200cb7..a9dd4f5a 100644 --- a/src/SharpCompress/Factories/IFactory.cs +++ b/src/SharpCompress/Factories/IFactory.cs @@ -51,7 +51,7 @@ public interface IFactory /// optional password /// buffer size for reading /// cancellation token - Task IsArchiveAsync( + ValueTask IsArchiveAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs index db1b725d..0180fefb 100644 --- a/src/SharpCompress/Factories/RarFactory.cs +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -50,7 +50,7 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade RarArchive.Open(stream, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -61,7 +61,7 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade RarArchive.Open(fileInfo, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -76,7 +76,7 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade RarArchive.Open(streams, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -87,7 +87,7 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade RarArchive.Open(fileInfos, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -102,14 +102,14 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade RarReader.Open(stream, options); /// - public async Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(OpenReader(stream, options)).ConfigureAwait(false); + return new(OpenReader(stream, options)); } #endregion diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index 73c08fa0..5a4be49e 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -45,7 +45,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory SevenZipArchive.Open(stream, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -56,7 +56,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory SevenZipArchive.Open(fileInfo, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -71,7 +71,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory SevenZipArchive.Open(streams, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -82,7 +82,7 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory SevenZipArchive.Open(fileInfos, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 57295699..4adccf8b 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -70,7 +70,7 @@ public class TarFactory TarArchive.Open(stream, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -81,7 +81,7 @@ public class TarFactory TarArchive.Open(fileInfo, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -96,7 +96,7 @@ public class TarFactory TarArchive.Open(streams, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -107,7 +107,7 @@ public class TarFactory TarArchive.Open(fileInfos, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -265,14 +265,14 @@ public class TarFactory TarReader.Open(stream, options); /// - public async Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(OpenReader(stream, options)).ConfigureAwait(false); + return new(OpenReader(stream, options)); } #endregion @@ -284,14 +284,14 @@ public class TarFactory new TarWriter(stream, new TarWriterOptions(writerOptions)); /// - public async Task OpenAsync( + public ValueTask OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, writerOptions)).ConfigureAwait(false); + return new(Open(stream, writerOptions)); } #endregion diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index 756bf31f..f8950b44 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -82,7 +82,7 @@ public class ZipFactory } /// - public override async Task IsArchiveAsync( + public override async ValueTask IsArchiveAsync( Stream stream, string? password = null, int bufferSize = ReaderOptions.DefaultBufferSize, @@ -137,7 +137,7 @@ public class ZipFactory ZipArchive.Open(stream, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -148,7 +148,7 @@ public class ZipFactory ZipArchive.Open(fileInfo, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -163,7 +163,7 @@ public class ZipFactory ZipArchive.Open(streams, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -174,7 +174,7 @@ public class ZipFactory ZipArchive.Open(fileInfos, readerOptions); /// - public Task OpenAsync( + public ValueTask OpenAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -189,14 +189,14 @@ public class ZipFactory ZipReader.Open(stream, options); /// - public async Task OpenReaderAsync( + public ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(OpenReader(stream, options)).ConfigureAwait(false); + return new(OpenReader(stream, options)); } #endregion @@ -208,14 +208,14 @@ public class ZipFactory new ZipWriter(stream, new ZipWriterOptions(writerOptions)); /// - public async Task OpenAsync( + public ValueTask OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return await Task.FromResult(Open(stream, writerOptions)).ConfigureAwait(false); + return new(Open(stream, writerOptions)); } #endregion diff --git a/src/SharpCompress/Readers/IReaderFactory.cs b/src/SharpCompress/Readers/IReaderFactory.cs index dd95f187..4b311fde 100644 --- a/src/SharpCompress/Readers/IReaderFactory.cs +++ b/src/SharpCompress/Readers/IReaderFactory.cs @@ -21,7 +21,7 @@ public interface IReaderFactory : Factories.IFactory /// /// /// - Task OpenReaderAsync( + ValueTask OpenReaderAsync( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Writers/IWriterFactory.cs b/src/SharpCompress/Writers/IWriterFactory.cs index 059dbe59..f933e819 100644 --- a/src/SharpCompress/Writers/IWriterFactory.cs +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -9,7 +9,7 @@ public interface IWriterFactory : IFactory { IWriter Open(Stream stream, WriterOptions writerOptions); - Task OpenAsync( + ValueTask OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default diff --git a/tests/SharpCompress.Test/ExtractAll.cs b/tests/SharpCompress.Test/ExtractAll.cs index 3e8b7d7a..f45a7486 100644 --- a/tests/SharpCompress.Test/ExtractAll.cs +++ b/tests/SharpCompress.Test/ExtractAll.cs @@ -23,7 +23,7 @@ public class ExtractAllTests : TestBase var testArchive = Path.Combine(TEST_ARCHIVES_PATH, archivePath); var options = new ExtractionOptions() { ExtractFullPath = true, Overwrite = true }; - using var archive = ArchiveFactory.Open(testArchive); + await using var archive = await ArchiveFactory.OpenAsync(testArchive); await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH, options); } diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs index cd93a3c1..7ee07db4 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs @@ -7,6 +7,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.Compressors.Deflate; +using SharpCompress.Test.Mocks; using SharpCompress.Writers; using SharpCompress.Writers.Zip; using Xunit; @@ -118,7 +119,7 @@ public class ZipArchiveAsyncTests : ArchiveTests } [Fact] - public async Task Zip_Random_Write_Remove_Async() + public async Task Zip_Random_Write_Remove_Sync() { var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.mod.zip"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); @@ -140,7 +141,7 @@ public class ZipArchiveAsyncTests : ArchiveTests } [Fact] - public async Task Zip_Random_Write_Add_Async() + public async Task Zip_Random_Write_Add_Sync() { var jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.mod.zip"); @@ -182,9 +183,9 @@ public class ZipArchiveAsyncTests : ArchiveTests public async Task Zip_Deflate_Entry_Stream_Async() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) - using (var archive = ZipArchive.Open(stream)) + await using (var archive = await ZipArchive.OpenAsync(new AsyncOnlyStream(stream))) { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync( SCRATCH_FILES_PATH, @@ -199,7 +200,7 @@ public class ZipArchiveAsyncTests : ArchiveTests public async Task Zip_Deflate_Archive_WriteToDirectoryAsync() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) - using (var archive = ZipArchive.Open(stream)) + await using (var archive = await ZipArchive.OpenAsync(new AsyncOnlyStream(stream))) { await archive.WriteToDirectoryAsync( SCRATCH_FILES_PATH, @@ -216,7 +217,7 @@ public class ZipArchiveAsyncTests : ArchiveTests var progress = new Progress(report => progressReports.Add(report)); using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) - using (var archive = ZipArchive.Open(stream)) + await using (var archive = await ZipArchive.OpenAsync(new AsyncOnlyStream(stream))) { await archive.WriteToDirectoryAsync( SCRATCH_FILES_PATH,