From cdba5ec4194ead2a238c57628ed3d19bf9c7d5c1 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 7 Feb 2021 09:08:15 +0000 Subject: [PATCH] AsyncEnumerable usage in entries --- src/SharpCompress/Archives/AbstractArchive.cs | 107 ++++++---------- .../Archives/AbstractWritableArchive.cs | 81 ++++++------ src/SharpCompress/Archives/ArchiveFactory.cs | 12 +- .../Archives/GZip/GZipArchive.cs | 64 +++++----- src/SharpCompress/Archives/IArchive.cs | 25 ++-- .../Archives/IArchiveEntryExtensions.cs | 12 +- .../Archives/IArchiveExtensions.cs | 2 +- .../Archives/IArchiveExtractionListener.cs | 11 -- .../Archives/IWritableArchive.cs | 9 +- .../Archives/IWritableArchiveExtensions.cs | 28 +++-- src/SharpCompress/Archives/Zip/ZipArchive.cs | 70 +++++------ src/SharpCompress/AsyncEnumerable.cs | 25 ++++ src/SharpCompress/LazyReadOnlyCollection.cs | 118 +++++------------- src/SharpCompress/Readers/ReaderFactory.cs | 2 +- src/SharpCompress/SharpCompress.csproj | 2 + src/SharpCompress/Utility.cs | 20 +++ tests/SharpCompress.Test/ArchiveTests.cs | 22 ++-- .../GZip/GZipArchiveTests.cs | 34 ++--- .../SharpCompress.Test.csproj | 2 + tests/SharpCompress.Test/Zip/Zip64Tests.cs | 6 +- .../SharpCompress.Test/Zip/ZipArchiveTests.cs | 112 ++++++++--------- 21 files changed, 357 insertions(+), 407 deletions(-) delete mode 100644 src/SharpCompress/Archives/IArchiveExtractionListener.cs create mode 100644 src/SharpCompress/AsyncEnumerable.cs diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 38e47ade..a9adf6f5 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -2,25 +2,20 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Readers; namespace SharpCompress.Archives { - public abstract class AbstractArchive : IArchive, IArchiveExtractionListener + public abstract class AbstractArchive : IArchive where TEntry : IArchiveEntry where TVolume : IVolume { private readonly LazyReadOnlyCollection lazyVolumes; private readonly LazyReadOnlyCollection lazyEntries; - public event EventHandler>? EntryExtractionBegin; - public event EventHandler>? EntryExtractionEnd; - - public event EventHandler? CompressedBytesRead; - public event EventHandler? FilePartExtractionBegin; - - protected ReaderOptions ReaderOptions { get; } + protected ReaderOptions ReaderOptions { get; } = new (); private bool disposed; @@ -38,9 +33,9 @@ namespace SharpCompress.Archives } - protected abstract IEnumerable LoadVolumes(FileInfo file); + protected abstract IAsyncEnumerable LoadVolumes(FileInfo file); - internal AbstractArchive(ArchiveType type, IEnumerable streams, ReaderOptions readerOptions) + internal AbstractArchive(ArchiveType type, IAsyncEnumerable streams, ReaderOptions readerOptions) { Type = type; ReaderOptions = readerOptions; @@ -48,27 +43,15 @@ namespace SharpCompress.Archives lazyEntries = new LazyReadOnlyCollection(LoadEntries(Volumes)); } -#nullable disable internal AbstractArchive(ArchiveType type) { Type = type; - lazyVolumes = new LazyReadOnlyCollection(Enumerable.Empty()); - lazyEntries = new LazyReadOnlyCollection(Enumerable.Empty()); + lazyVolumes = new LazyReadOnlyCollection( AsyncEnumerable.Empty()); + lazyEntries = new LazyReadOnlyCollection(AsyncEnumerable.Empty()); } -#nullable enable public ArchiveType Type { get; } - void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry) - { - EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs(entry)); - } - - void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry) - { - EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs(entry)); - } - private static Stream CheckStreams(Stream stream) { if (!stream.CanSeek || !stream.CanRead) @@ -81,63 +64,48 @@ namespace SharpCompress.Archives /// /// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive. /// - public virtual ICollection Entries => lazyEntries; + public virtual IAsyncEnumerable Entries => lazyEntries; /// /// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive. /// - public ICollection Volumes => lazyVolumes; + public IAsyncEnumerable Volumes => lazyVolumes; /// /// The total size of the files compressed in the archive. /// - public virtual long TotalSize => Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize); + public virtual async ValueTask TotalSizeAsync() + { + await EnsureEntriesLoaded(); + return await Entries.AggregateAsync(0L, (total, cf) => total + cf.CompressedSize); + } /// /// The total size of the files as uncompressed in the archive. /// - public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size); + public virtual async ValueTask TotalUncompressedSizeAsync() + { + await EnsureEntriesLoaded(); + return await Entries.AggregateAsync(0L, (total, cf) => total + cf.Size); + } - protected abstract IEnumerable LoadVolumes(IEnumerable streams); - protected abstract IEnumerable LoadEntries(IEnumerable volumes); + protected abstract IAsyncEnumerable LoadVolumes(IAsyncEnumerable streams); + protected abstract IAsyncEnumerable LoadEntries(IAsyncEnumerable volumes); - IEnumerable IArchive.Entries => Entries.Cast(); + IAsyncEnumerable IArchive.Entries => Entries.Select(x => (IArchiveEntry)x); - IEnumerable IArchive.Volumes => lazyVolumes.Cast(); + IAsyncEnumerable IArchive.Volumes => lazyVolumes.Select(x => (IVolume)x); - public virtual void Dispose() + public virtual async ValueTask DisposeAsync() { if (!disposed) { - lazyVolumes.ForEach(v => v.Dispose()); + await lazyVolumes.ForEachAsync(v => v.Dispose()); lazyEntries.GetLoaded().Cast().ForEach(x => x.Close()); disposed = true; } } - void IArchiveExtractionListener.EnsureEntriesLoaded() - { - lazyEntries.EnsureFullyLoaded(); - lazyVolumes.EnsureFullyLoaded(); - } - - void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes) - { - CompressedBytesRead?.Invoke(this, new CompressedBytesReadEventArgs( - currentFilePartCompressedBytesRead: currentPartCompressedBytes, - compressedBytesRead: compressedReadBytes - )); - } - - void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize) - { - FilePartExtractionBegin?.Invoke(this, new FilePartExtractionBeginEventArgs( - compressedSize: compressedSize, - size: size, - name: name - )); - } - /// /// 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 @@ -149,29 +117,32 @@ namespace SharpCompress.Archives /// occur if this is used at the same time as other extraction methods on this instance. /// /// - public IReader ExtractAllEntries() + public async ValueTask ExtractAllEntries() { - ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); - return CreateReaderForSolidExtraction(); + await EnsureEntriesLoaded(); + return await CreateReaderForSolidExtraction(); + } + + public async ValueTask EnsureEntriesLoaded() + { + await lazyEntries.EnsureFullyLoaded(); + await lazyVolumes.EnsureFullyLoaded(); } - protected abstract IReader CreateReaderForSolidExtraction(); + protected abstract ValueTask CreateReaderForSolidExtraction(); /// /// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files). /// - public virtual bool IsSolid => false; + public virtual ValueTask IsSolidAsync() => new(false); /// /// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive. /// - public bool IsComplete + public async ValueTask IsCompleteAsync() { - get - { - ((IArchiveExtractionListener)this).EnsureEntriesLoaded(); - return Entries.All(x => x.IsComplete); - } + await EnsureEntriesLoaded(); + return await Entries.AllAsync(x => x.IsComplete); } } } diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.cs b/src/SharpCompress/Archives/AbstractWritableArchive.cs index d69dd06b..b4dadeca 100644 --- a/src/SharpCompress/Archives/AbstractWritableArchive.cs +++ b/src/SharpCompress/Archives/AbstractWritableArchive.cs @@ -14,7 +14,7 @@ namespace SharpCompress.Archives where TEntry : IArchiveEntry where TVolume : IVolume { - private class RebuildPauseDisposable : IDisposable + private class RebuildPauseDisposable : IAsyncDisposable { private readonly AbstractWritableArchive archive; @@ -24,16 +24,16 @@ namespace SharpCompress.Archives archive.pauseRebuilding = true; } - public void Dispose() + public async ValueTask DisposeAsync() { archive.pauseRebuilding = false; - archive.RebuildModifiedCollection(); + await archive.RebuildModifiedCollection(); } } - private readonly List newEntries = new List(); - private readonly List removedEntries = new List(); + private readonly List newEntries = new(); + private readonly List removedEntries = new(); - private readonly List modifiedEntries = new List(); + private readonly List modifiedEntries = new(); private bool hasModifications; private bool pauseRebuilding; @@ -43,7 +43,7 @@ namespace SharpCompress.Archives } internal AbstractWritableArchive(ArchiveType type, Stream stream, ReaderOptions readerFactoryOptions) - : base(type, stream.AsEnumerable(), readerFactoryOptions) + : base(type, stream.AsAsyncEnumerable(), readerFactoryOptions) { } @@ -52,24 +52,24 @@ namespace SharpCompress.Archives { } - public override ICollection Entries + public override IAsyncEnumerable Entries { get { if (hasModifications) { - return modifiedEntries; + return modifiedEntries.ToAsyncEnumerable(); } return base.Entries; } } - public IDisposable PauseEntryRebuilding() + public IAsyncDisposable PauseEntryRebuilding() { return new RebuildPauseDisposable(this); } - private void RebuildModifiedCollection() + private async ValueTask RebuildModifiedCollection() { if (pauseRebuilding) { @@ -78,56 +78,57 @@ namespace SharpCompress.Archives hasModifications = true; newEntries.RemoveAll(v => removedEntries.Contains(v)); modifiedEntries.Clear(); - modifiedEntries.AddRange(OldEntries.Concat(newEntries)); + modifiedEntries.AddRange(await OldEntries.Concat(newEntries.ToAsyncEnumerable()).ToListAsync()); } - private IEnumerable OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } } + private IAsyncEnumerable OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } } - public void RemoveEntry(TEntry entry) + public async ValueTask RemoveEntryAsync(TEntry entry) { if (!removedEntries.Contains(entry)) { removedEntries.Add(entry); - RebuildModifiedCollection(); + await RebuildModifiedCollection(); } } - void IWritableArchive.RemoveEntry(IArchiveEntry entry) + ValueTask IWritableArchive.RemoveEntryAsync(IArchiveEntry entry, CancellationToken cancellationToken) { - RemoveEntry((TEntry)entry); + return RemoveEntryAsync((TEntry)entry); } - public TEntry AddEntry(string key, Stream source, - long size = 0, DateTime? modified = null) + public ValueTask AddEntryAsync(string key, Stream source, + long size = 0, DateTime? modified = null, + CancellationToken cancellationToken = default) { - return AddEntry(key, source, false, size, modified); + return AddEntryAsync(key, source, false, size, modified, cancellationToken); } - IArchiveEntry IWritableArchive.AddEntry(string key, Stream source, bool closeStream, long size, DateTime? modified) + async ValueTask IWritableArchive.AddEntryAsync(string key, Stream source, bool closeStream, long size, DateTime? modified, CancellationToken cancellationToken) { - return AddEntry(key, source, closeStream, size, modified); + return await AddEntryAsync(key, source, closeStream, size, modified, cancellationToken); } - public TEntry AddEntry(string key, Stream source, bool closeStream, - long size = 0, DateTime? modified = null) + public async ValueTask AddEntryAsync(string key, Stream source, bool closeStream, + long size = 0, DateTime? modified = null, CancellationToken cancellationToken = default) { if (key.Length > 0 && key[0] is '/' or '\\') { key = key.Substring(1); } - if (DoesKeyMatchExisting(key)) + if (await DoesKeyMatchExisting(key)) { throw new ArchiveException("Cannot add entry with duplicate key: " + key); } - var entry = CreateEntry(key, source, size, modified, closeStream); + var entry = await CreateEntry(key, source, size, modified, closeStream, cancellationToken); newEntries.Add(entry); - RebuildModifiedCollection(); + await RebuildModifiedCollection(); return entry; } - private bool DoesKeyMatchExisting(string key) + private async ValueTask DoesKeyMatchExisting(string key) { - foreach (var path in Entries.Select(x => x.Key)) + await foreach (var path in Entries.Select(x => x.Key)) { var p = path.Replace('/', '\\'); if (p.Length > 0 && p[0] == '\\') @@ -139,32 +140,32 @@ namespace SharpCompress.Archives return false; } - public async Task SaveToAsync(Stream stream, WriterOptions options) + public async ValueTask SaveToAsync(Stream stream, WriterOptions options, CancellationToken cancellationToken = default) { //reset streams of new entries newEntries.Cast().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin)); - await SaveToAsync(stream, options, OldEntries, newEntries); + await SaveToAsync(stream, options, OldEntries, newEntries.ToAsyncEnumerable(), cancellationToken); } - protected TEntry CreateEntry(string key, Stream source, long size, DateTime? modified, - bool closeStream) + protected ValueTask CreateEntry(string key, Stream source, long size, DateTime? modified, + bool closeStream, CancellationToken cancellationToken) { if (!source.CanRead || !source.CanSeek) { throw new ArgumentException("Streams must be readable and seekable to use the Writing Archive API"); } - return CreateEntryInternal(key, source, size, modified, closeStream); + return CreateEntryInternal(key, source, size, modified, closeStream, cancellationToken); } - protected abstract TEntry CreateEntryInternal(string key, Stream source, long size, DateTime? modified, - bool closeStream); + protected abstract ValueTask CreateEntryInternal(string key, Stream source, long size, DateTime? modified, + bool closeStream, CancellationToken cancellationToken); - protected abstract Task SaveToAsync(Stream stream, WriterOptions options, IEnumerable oldEntries, IEnumerable newEntries, - CancellationToken cancellationToken = default); + protected abstract ValueTask SaveToAsync(Stream stream, WriterOptions options, IAsyncEnumerable oldEntries, IAsyncEnumerable newEntries, + CancellationToken cancellationToken = default); - public override void Dispose() + public override async ValueTask DisposeAsync() { - base.Dispose(); + await base.DisposeAsync(); newEntries.Cast().ForEach(x => x.Close()); removedEntries.Cast().ForEach(x => x.Close()); modifiedEntries.Cast().ForEach(x => x.Close()); diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index a48eac8c..ac79145c 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -20,7 +20,7 @@ namespace SharpCompress.Archives /// /// /// - public static async ValueTask OpenAsync(Stream stream, ReaderOptions? readerOptions = null) + public static async ValueTask OpenAsync(Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default) { stream.CheckNotNull(nameof(stream)); if (!stream.CanRead || !stream.CanSeek) @@ -40,7 +40,7 @@ namespace SharpCompress.Archives return SevenZipArchive.Open(stream, readerOptions); } stream.Seek(0, SeekOrigin.Begin); */ - if (GZipArchive.IsGZipFile(stream)) + if (await GZipArchive.IsGZipFileAsync(stream, cancellationToken)) { stream.Seek(0, SeekOrigin.Begin); return GZipArchive.Open(stream, readerOptions); @@ -87,7 +87,7 @@ namespace SharpCompress.Archives /// /// /// - public static async ValueTask OpenAsync(FileInfo fileInfo, ReaderOptions? options = null) + public static async ValueTask OpenAsync(FileInfo fileInfo, ReaderOptions? options = null, CancellationToken cancellationToken = default) { fileInfo.CheckNotNull(nameof(fileInfo)); options ??= new ReaderOptions { LeaveStreamOpen = false }; @@ -103,7 +103,7 @@ namespace SharpCompress.Archives return SevenZipArchive.Open(fileInfo, options); } stream.Seek(0, SeekOrigin.Begin); */ - if (GZipArchive.IsGZipFile(stream)) + if (await GZipArchive.IsGZipFileAsync(stream, cancellationToken)) { return GZipArchive.Open(fileInfo, options); } @@ -128,8 +128,8 @@ namespace SharpCompress.Archives ExtractionOptions? options = null, CancellationToken cancellationToken = default) { - using IArchive archive = await OpenAsync(sourceArchive); - foreach (IArchiveEntry entry in archive.Entries) + await using IArchive archive = await OpenAsync(sourceArchive); + await foreach (IArchiveEntry entry in archive.Entries.WithCancellation(cancellationToken)) { await entry.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken); } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index b179259c..a11ce60a 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -63,50 +64,50 @@ namespace SharpCompress.Archives.GZip { } - protected override IEnumerable LoadVolumes(FileInfo file) + protected override IAsyncEnumerable LoadVolumes(FileInfo file) { - return new GZipVolume(file, ReaderOptions).AsEnumerable(); + return new GZipVolume(file, ReaderOptions).AsAsyncEnumerable(); } - public static bool IsGZipFile(string filePath) + public static ValueTask IsGZipFileAsync(string filePath, CancellationToken cancellationToken = default) { - return IsGZipFile(new FileInfo(filePath)); + return IsGZipFileAsync(new FileInfo(filePath), cancellationToken); } - public static bool IsGZipFile(FileInfo fileInfo) + public static async ValueTask IsGZipFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default) { if (!fileInfo.Exists) { return false; } - using Stream stream = fileInfo.OpenRead(); - return IsGZipFile(stream); + await using Stream stream = fileInfo.OpenRead(); + return await IsGZipFileAsync(stream, cancellationToken); } - public Task SaveToAsync(string filePath) + public Task SaveToAsync(string filePath, CancellationToken cancellationToken = default) { - return SaveToAsync(new FileInfo(filePath)); + return SaveToAsync(new FileInfo(filePath), cancellationToken); } - public async Task SaveToAsync(FileInfo fileInfo) + public async Task SaveToAsync(FileInfo fileInfo, CancellationToken cancellationToken = default) { - using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - await SaveToAsync(stream, new WriterOptions(CompressionType.GZip)); + await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); + await SaveToAsync(stream, new WriterOptions(CompressionType.GZip), cancellationToken); } - public static bool IsGZipFile(Stream stream) + public static async ValueTask IsGZipFileAsync(Stream stream, CancellationToken cancellationToken = default) { // read the header on the first read - byte[] header = new byte[10]; + using var header = MemoryPool.Shared.Rent(10); // workitem 8501: handle edge case (decompress empty stream) - if (!stream.ReadFully(header)) + if (!await stream.ReadFullyAsync(header.Memory, cancellationToken)) { return false; } - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + if (header.Memory.Span[0] != 0x1F || header.Memory.Span[1] != 0x8B || header.Memory.Span[2] != 8) { return false; } @@ -129,49 +130,50 @@ namespace SharpCompress.Archives.GZip { } - protected override GZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified, - bool closeStream) + protected override async ValueTask CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified, + bool closeStream, CancellationToken cancellationToken = default) { - if (Entries.Any()) + if (await Entries.AnyAsync(cancellationToken: cancellationToken)) { throw new InvalidOperationException("Only one entry is allowed in a GZip Archive"); } return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream); } - protected override async Task SaveToAsync(Stream stream, WriterOptions options, - IEnumerable oldEntries, - IEnumerable newEntries, + protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options, + IAsyncEnumerable oldEntries, + IAsyncEnumerable newEntries, CancellationToken cancellationToken = default) { - if (Entries.Count > 1) + if (await Entries.CountAsync(cancellationToken: cancellationToken) > 1) { throw new InvalidOperationException("Only one entry is allowed in a GZip Archive"); } await using var writer = new GZipWriter(stream, new GZipWriterOptions(options)); - foreach (var entry in oldEntries.Concat(newEntries) - .Where(x => !x.IsDirectory)) + await foreach (var entry in oldEntries.Concat(newEntries) + .Where(x => !x.IsDirectory) + .WithCancellation(cancellationToken)) { await using var entryStream = entry.OpenEntryStream(); await writer.WriteAsync(entry.Key, entryStream, entry.LastModifiedTime, cancellationToken); } } - protected override IEnumerable LoadVolumes(IEnumerable streams) + protected override async IAsyncEnumerable LoadVolumes(IAsyncEnumerable streams) { - return new GZipVolume(streams.First(), ReaderOptions).AsEnumerable(); + yield return new GZipVolume(await streams.FirstAsync(), ReaderOptions); } - protected override IEnumerable LoadEntries(IEnumerable volumes) + protected override async IAsyncEnumerable LoadEntries(IAsyncEnumerable volumes) { - Stream stream = volumes.Single().Stream; + Stream stream = (await volumes.SingleAsync()).Stream; yield return new GZipArchiveEntry(this, new GZipFilePart(stream, ReaderOptions.ArchiveEncoding)); } - protected override IReader CreateReaderForSolidExtraction() + protected override async ValueTask CreateReaderForSolidExtraction() { - var stream = Volumes.Single().Stream; + var stream = (await Volumes.SingleAsync()).Stream; stream.Position = 0; return GZipReader.Open(stream); } diff --git a/src/SharpCompress/Archives/IArchive.cs b/src/SharpCompress/Archives/IArchive.cs index 2ba84a39..e4d74355 100644 --- a/src/SharpCompress/Archives/IArchive.cs +++ b/src/SharpCompress/Archives/IArchive.cs @@ -1,49 +1,44 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Readers; namespace SharpCompress.Archives { - public interface IArchive : IDisposable + public interface IArchive : IAsyncDisposable { - event EventHandler> EntryExtractionBegin; - event EventHandler> EntryExtractionEnd; - - event EventHandler CompressedBytesRead; - event EventHandler FilePartExtractionBegin; - - IEnumerable Entries { get; } - IEnumerable Volumes { get; } + IAsyncEnumerable Entries { get; } + IAsyncEnumerable Volumes { get; } ArchiveType Type { get; } - + ValueTask EnsureEntriesLoaded(); /// /// 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. /// - IReader ExtractAllEntries(); + ValueTask ExtractAllEntries(); /// /// 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. /// - bool IsSolid { get; } + ValueTask IsSolidAsync(); /// /// This checks to see if all the known entries have IsComplete = true /// - bool IsComplete { get; } + ValueTask IsCompleteAsync(); /// /// The total size of the files compressed in the archive. /// - long TotalSize { get; } + ValueTask TotalSizeAsync(); /// /// The total size of the files as uncompressed in the archive. /// - long TotalUncompressSize { get; } + ValueTask TotalUncompressedSizeAsync(); } } \ No newline at end of file diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index 0349d451..2f1e980f 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -15,10 +15,8 @@ namespace SharpCompress.Archives throw new ExtractionException("Entry is a file directory and cannot be extracted."); } - var streamListener = (IArchiveExtractionListener)archiveEntry.Archive; - streamListener.EnsureEntriesLoaded(); - streamListener.FireEntryExtractionBegin(archiveEntry); - streamListener.FireFilePartExtractionBegin(archiveEntry.Key, archiveEntry.Size, archiveEntry.CompressedSize); + var archive = archiveEntry.Archive; + await archive.EnsureEntriesLoaded(); var entryStream = archiveEntry.OpenEntryStream(); if (entryStream is null) { @@ -26,12 +24,8 @@ namespace SharpCompress.Archives } await using (entryStream) { - await using (Stream s = new ListeningStream(streamListener, entryStream)) - { - await s.TransferToAsync(streamToWriteTo, cancellationToken); - } + await entryStream.TransferToAsync(streamToWriteTo, cancellationToken); } - streamListener.FireEntryExtractionEnd(archiveEntry); } /// diff --git a/src/SharpCompress/Archives/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs index bab30de9..0edf5258 100644 --- a/src/SharpCompress/Archives/IArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveExtensions.cs @@ -15,7 +15,7 @@ namespace SharpCompress.Archives ExtractionOptions? options = null, CancellationToken cancellationToken = default) { - foreach (IArchiveEntry entry in archive.Entries.Where(x => !x.IsDirectory)) + await foreach (IArchiveEntry entry in archive.Entries.Where(x => !x.IsDirectory).WithCancellation(cancellationToken)) { await entry.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken); } diff --git a/src/SharpCompress/Archives/IArchiveExtractionListener.cs b/src/SharpCompress/Archives/IArchiveExtractionListener.cs deleted file mode 100644 index 9ce07e8a..00000000 --- a/src/SharpCompress/Archives/IArchiveExtractionListener.cs +++ /dev/null @@ -1,11 +0,0 @@ -using SharpCompress.Common; - -namespace SharpCompress.Archives -{ - internal interface IArchiveExtractionListener : IExtractionListener - { - void EnsureEntriesLoaded(); - void FireEntryExtractionBegin(IArchiveEntry entry); - void FireEntryExtractionEnd(IArchiveEntry entry); - } -} \ No newline at end of file diff --git a/src/SharpCompress/Archives/IWritableArchive.cs b/src/SharpCompress/Archives/IWritableArchive.cs index 385ff2dc..e78bddf5 100644 --- a/src/SharpCompress/Archives/IWritableArchive.cs +++ b/src/SharpCompress/Archives/IWritableArchive.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using SharpCompress.Writers; @@ -7,16 +8,16 @@ namespace SharpCompress.Archives { public interface IWritableArchive : IArchive { - void RemoveEntry(IArchiveEntry entry); + ValueTask RemoveEntryAsync(IArchiveEntry entry, CancellationToken cancellationToken = default); - IArchiveEntry AddEntry(string key, Stream source, bool closeStream, long size = 0, DateTime? modified = null); + ValueTask AddEntryAsync(string key, Stream source, bool closeStream, long size = 0, DateTime? modified = null, CancellationToken cancellationToken = default); - Task SaveToAsync(Stream stream, WriterOptions options); + ValueTask SaveToAsync(Stream stream, WriterOptions options, CancellationToken cancellationToken = default); /// /// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended. /// /// IDisposeable to resume entry rebuilding - IDisposable PauseEntryRebuilding(); + IAsyncDisposable PauseEntryRebuilding(); } } \ No newline at end of file diff --git a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs index f37ce479..30e19263 100644 --- a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using SharpCompress.Writers; @@ -7,16 +8,17 @@ namespace SharpCompress.Archives { public static class IWritableArchiveExtensions { - public static void AddEntry(this IWritableArchive writableArchive, - string entryPath, string filePath) + public static async ValueTask AddEntryAsync(this IWritableArchive writableArchive, + string entryPath, string filePath, + CancellationToken cancellationToken = default) { var fileInfo = new FileInfo(filePath); if (!fileInfo.Exists) { throw new FileNotFoundException("Could not AddEntry: " + filePath); } - writableArchive.AddEntry(entryPath, new FileInfo(filePath).OpenRead(), true, fileInfo.Length, - fileInfo.LastWriteTime); + await writableArchive.AddEntryAsync(entryPath, new FileInfo(filePath).OpenRead(), true, fileInfo.Length, + fileInfo.LastWriteTime, cancellationToken); } public static Task SaveToAsync(this IWritableArchive writableArchive, string filePath, WriterOptions options) @@ -30,27 +32,31 @@ namespace SharpCompress.Archives await writableArchive.SaveToAsync(stream, options); } - public static void AddAllFromDirectory( + public static async ValueTask AddAllFromDirectoryAsync( this IWritableArchive writableArchive, - string filePath, string searchPattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories) + string filePath, string searchPattern = "*.*", + SearchOption searchOption = SearchOption.AllDirectories, + CancellationToken cancellationToken = default) { - using (writableArchive.PauseEntryRebuilding()) + await using (writableArchive.PauseEntryRebuilding()) { foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)) { var fileInfo = new FileInfo(path); - writableArchive.AddEntry(path.Substring(filePath.Length), fileInfo.OpenRead(), true, fileInfo.Length, - fileInfo.LastWriteTime); + await writableArchive.AddEntryAsync(path.Substring(filePath.Length), fileInfo.OpenRead(), true, fileInfo.Length, + fileInfo.LastWriteTime, + cancellationToken); } } } - public static IArchiveEntry AddEntry(this IWritableArchive writableArchive, string key, FileInfo fileInfo) + public static ValueTask AddEntryAsync(this IWritableArchive writableArchive, string key, FileInfo fileInfo, + CancellationToken cancellationToken = default) { if (!fileInfo.Exists) { throw new ArgumentException("FileInfo does not exist."); } - return writableArchive.AddEntry(key, fileInfo.OpenRead(), true, fileInfo.Length, fileInfo.LastWriteTime); + return writableArchive.AddEntryAsync(key, fileInfo.OpenRead(), true, fileInfo.Length, fileInfo.LastWriteTime, cancellationToken); } } } \ No newline at end of file diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 6604e301..37a653a0 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -118,9 +118,9 @@ namespace SharpCompress.Archives.Zip headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding); } - protected override IEnumerable LoadVolumes(FileInfo file) + protected override IAsyncEnumerable LoadVolumes(FileInfo file) { - return new ZipVolume(file.OpenRead(), ReaderOptions).AsEnumerable(); + return new ZipVolume(file.OpenRead(), ReaderOptions).AsAsyncEnumerable(); } internal ZipArchive() @@ -139,14 +139,15 @@ namespace SharpCompress.Archives.Zip headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding); } - protected override IEnumerable LoadVolumes(IEnumerable streams) + protected override async IAsyncEnumerable LoadVolumes(IAsyncEnumerable streams) { - return new ZipVolume(streams.First(), ReaderOptions).AsEnumerable(); + yield return new ZipVolume(await streams.FirstAsync(), ReaderOptions); } - protected override IEnumerable LoadEntries(IEnumerable volumes) + protected override async IAsyncEnumerable LoadEntries(IAsyncEnumerable volumes) { - var volume = volumes.Single(); + await Task.CompletedTask; + var volume = await volumes.SingleAsync(); Stream stream = volume.Stream; foreach (ZipHeader h in headerFactory.ReadSeekableHeader(stream)) { @@ -155,51 +156,50 @@ namespace SharpCompress.Archives.Zip switch (h.ZipHeaderType) { case ZipHeaderType.DirectoryEntry: - { - yield return new ZipArchiveEntry(this, - new SeekableZipFilePart(headerFactory, - (DirectoryEntryHeader)h, - stream)); - } + { + yield return new ZipArchiveEntry(this, + new SeekableZipFilePart(headerFactory, + (DirectoryEntryHeader)h, + stream)); + } break; case ZipHeaderType.DirectoryEnd: - { - byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty(); - volume.Comment = ReaderOptions.ArchiveEncoding.Decode(bytes); - yield break; - } + { + byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty(); + volume.Comment = ReaderOptions.ArchiveEncoding.Decode(bytes); + yield break; + } } } } } - public Task SaveToAsync(Stream stream) + public ValueTask SaveToAsync(Stream stream, CancellationToken cancellationToken = default) { - return SaveToAsync(stream, new WriterOptions(CompressionType.Deflate)); + return SaveToAsync(stream, new WriterOptions(CompressionType.Deflate), cancellationToken); } - protected override async Task SaveToAsync(Stream stream, WriterOptions options, - IEnumerable oldEntries, - IEnumerable newEntries, - CancellationToken cancellationToken = default) + protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options, + IAsyncEnumerable oldEntries, + IAsyncEnumerable newEntries, + CancellationToken cancellationToken = default) { - await using (var writer = new ZipWriter(stream, new ZipWriterOptions(options))) + await using var writer = new ZipWriter(stream, new ZipWriterOptions(options)); + await foreach (var entry in oldEntries.Concat(newEntries) + .Where(x => !x.IsDirectory) + .WithCancellation(cancellationToken)) { - foreach (var entry in oldEntries.Concat(newEntries) - .Where(x => !x.IsDirectory)) + await using (var entryStream = entry.OpenEntryStream()) { - await using (var entryStream = entry.OpenEntryStream()) - { - await writer.WriteAsync(entry.Key, entryStream, entry.LastModifiedTime, cancellationToken); - } + await writer.WriteAsync(entry.Key, entryStream, entry.LastModifiedTime, cancellationToken); } } } - protected override ZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified, - bool closeStream) + protected override ValueTask CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified, + bool closeStream, CancellationToken cancellationToken = default) { - return new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream); + return new(new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream)); } public static ZipArchive Create() @@ -207,9 +207,9 @@ namespace SharpCompress.Archives.Zip return new(); } - protected override IReader CreateReaderForSolidExtraction() + protected override async ValueTask CreateReaderForSolidExtraction() { - var stream = Volumes.Single().Stream; + var stream = (await Volumes.SingleAsync()).Stream; stream.Position = 0; return ZipReader.Open(stream, ReaderOptions); } diff --git a/src/SharpCompress/AsyncEnumerable.cs b/src/SharpCompress/AsyncEnumerable.cs new file mode 100644 index 00000000..de6d849b --- /dev/null +++ b/src/SharpCompress/AsyncEnumerable.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpCompress +{ + public static class AsyncEnumerable + { + public static IAsyncEnumerable Empty() => EmptyAsyncEnumerable.Instance; + + + private class EmptyAsyncEnumerable : IAsyncEnumerator, IAsyncEnumerable + { + public static readonly EmptyAsyncEnumerable Instance = + new(); + public T Current => default!; + public ValueTask DisposeAsync() => default; + public ValueTask MoveNextAsync() => new(false); + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) + { + return this; + } + } + } +} \ No newline at end of file diff --git a/src/SharpCompress/LazyReadOnlyCollection.cs b/src/SharpCompress/LazyReadOnlyCollection.cs index 415e9904..c019705f 100644 --- a/src/SharpCompress/LazyReadOnlyCollection.cs +++ b/src/SharpCompress/LazyReadOnlyCollection.cs @@ -1,23 +1,31 @@ -#nullable disable - -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace SharpCompress { - internal sealed class LazyReadOnlyCollection : ICollection + internal sealed class LazyReadOnlyCollection : IAsyncEnumerable { - private readonly List backing = new List(); - private readonly IEnumerator source; + private readonly List backing = new(); + private IAsyncEnumerator? enumerator; + private readonly IAsyncEnumerable enumerable; private bool fullyLoaded; - public LazyReadOnlyCollection(IEnumerable source) + public LazyReadOnlyCollection(IAsyncEnumerable source) { - this.source = source.GetEnumerator(); + enumerable = source; } - private class LazyLoader : IEnumerator + private IAsyncEnumerator GetEnumerator() + { + if (enumerator is null) + { + enumerator = enumerable.GetAsyncEnumerator(); + } + return enumerator; + } + + private class LazyLoader : IAsyncEnumerator { private readonly LazyReadOnlyCollection lazyReadOnlyCollection; private bool disposed; @@ -28,58 +36,43 @@ namespace SharpCompress this.lazyReadOnlyCollection = lazyReadOnlyCollection; } - #region IEnumerator Members - public T Current => lazyReadOnlyCollection.backing[index]; - - #endregion - - #region IDisposable Members - - public void Dispose() + + public ValueTask DisposeAsync() { if (!disposed) { disposed = true; } + return new ValueTask(Task.CompletedTask); } - #endregion - #region IEnumerator Members - - object IEnumerator.Current => Current; - - public bool MoveNext() + public async ValueTask MoveNextAsync() { if (index + 1 < lazyReadOnlyCollection.backing.Count) { index++; return true; } - if (!lazyReadOnlyCollection.fullyLoaded && lazyReadOnlyCollection.source.MoveNext()) + if (!lazyReadOnlyCollection.fullyLoaded && await lazyReadOnlyCollection.GetEnumerator().MoveNextAsync()) { - lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.source.Current); + lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.GetEnumerator().Current); index++; return true; } lazyReadOnlyCollection.fullyLoaded = true; return false; } - - public void Reset() - { - throw new NotSupportedException(); - } - - #endregion } - internal void EnsureFullyLoaded() + internal async ValueTask EnsureFullyLoaded() { if (!fullyLoaded) { - this.ForEach(x => { }); + await foreach (var x in this) + { + } fullyLoaded = true; } } @@ -89,65 +82,14 @@ namespace SharpCompress return backing; } - #region ICollection Members - - public void Add(T item) - { - throw new NotSupportedException(); - } - - public void Clear() - { - throw new NotSupportedException(); - } - - public bool Contains(T item) - { - EnsureFullyLoaded(); - return backing.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - EnsureFullyLoaded(); - backing.CopyTo(array, arrayIndex); - } - - public int Count - { - get - { - EnsureFullyLoaded(); - return backing.Count; - } - } - - public bool IsReadOnly => true; - - public bool Remove(T item) - { - throw new NotSupportedException(); - } - - #endregion - #region IEnumerable Members //TODO check for concurrent access - public IEnumerator GetEnumerator() + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken) { return new LazyLoader(this); } #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion } } diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 0abdc06b..9983b5be 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -42,7 +42,7 @@ namespace SharpCompress.Readers return ZipReader.Open(rewindableStream, options); } rewindableStream.Rewind(false); - if (GZipArchive.IsGZipFile(rewindableStream)) + if (await GZipArchive.IsGZipFileAsync(rewindableStream)) { rewindableStream.Rewind(false); /*GZipStream testStream = new GZipStream(rewindableStream, CompressionMode.Decompress); diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 22766b5e..d974ec74 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -8,6 +8,7 @@ Adam Hathcock netstandard2.1;netcoreapp3.1;net5.0 true + true false SharpCompress ../../SharpCompress.snk @@ -29,6 +30,7 @@ + diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 95aca23e..e427f456 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -94,6 +94,11 @@ namespace SharpCompress { yield return item; } + public static async IAsyncEnumerable AsAsyncEnumerable(this T item) + { + await Task.CompletedTask; + yield return item; + } public static void CheckNotNull(this object obj, string name) { @@ -312,6 +317,21 @@ namespace SharpCompress } return (total >= buffer.Length); } + + public static async ValueTask ReadFullyAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken) + { + int total = 0; + int read; + while ((read = await stream.ReadAsync(buffer, cancellationToken)) > 0) + { + total += read; + if (total >= buffer.Length) + { + return true; + } + } + return (total >= buffer.Length); + } public static string TrimNulls(this string source) { diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 9ba5a7db..7320c3d5 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -25,21 +25,21 @@ namespace SharpCompress.Test foreach (var path in testArchives) { await using (var stream = new NonDisposingStream(File.OpenRead(path), true)) - using (var archive = await ArchiveFactory.OpenAsync(stream)) + await using (var archive = await ArchiveFactory.OpenAsync(stream)) { - Assert.True(archive.IsSolid); - await using (var reader = archive.ExtractAllEntries()) + Assert.True(await archive.IsSolidAsync()); + await using (var reader = await archive.ExtractAllEntries()) { await ReadAsync(reader, compression); } VerifyFiles(); - if (archive.Entries.First().CompressionType == CompressionType.Rar) + if ((await archive.Entries.FirstAsync()).CompressionType == CompressionType.Rar) { stream.ThrowOnDispose = false; return; } - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions @@ -70,11 +70,11 @@ namespace SharpCompress.Test foreach (var path in testArchives) { using (var stream = new NonDisposingStream(File.OpenRead(path), true)) - using (var archive = await ArchiveFactory.OpenAsync(stream, readerOptions)) + await using (var archive = await ArchiveFactory.OpenAsync(stream, readerOptions)) { try { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions() @@ -99,9 +99,9 @@ namespace SharpCompress.Test protected async ValueTask ArchiveFileReadAsync(string testArchive, ReaderOptions readerOptions = null) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = await ArchiveFactory.OpenAsync(testArchive, readerOptions)) + await using (var archive = await ArchiveFactory.OpenAsync(testArchive, readerOptions)) { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions() @@ -120,9 +120,9 @@ namespace SharpCompress.Test protected async ValueTask ArchiveFileReadEx(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = await ArchiveFactory.OpenAsync(testArchive)) + await using (var archive = await ArchiveFactory.OpenAsync(testArchive)) { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions() diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs index a9c2e21a..9603ef2d 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs @@ -19,9 +19,9 @@ namespace SharpCompress.Test.GZip public async ValueTask GZip_Archive_Generic() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = await ArchiveFactory.OpenAsync(stream)) + await using (var archive = await ArchiveFactory.OpenAsync(stream)) { - var entry = archive.Entries.First(); + var entry = await archive.Entries.FirstAsync(); await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); long size = entry.Size; @@ -39,9 +39,9 @@ namespace SharpCompress.Test.GZip public async ValueTask GZip_Archive() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = GZipArchive.Open(stream)) + await using (var archive = GZipArchive.Open(stream)) { - var entry = archive.Entries.First(); + var entry = await archive.Entries.FirstAsync(); await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); long size = entry.Size; @@ -61,38 +61,38 @@ namespace SharpCompress.Test.GZip { string jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); await using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = GZipArchive.Open(stream)) + await using (var archive = GZipArchive.Open(stream)) { - Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); + await Assert.ThrowsAsync(async () => await archive.AddEntryAsync("jpg\\test.jpg", jpg)); await archive.SaveToAsync(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); } } [Fact] - public void GZip_Archive_Multiple_Reads() + public async ValueTask GZip_Archive_Multiple_Reads() { var inputStream = new MemoryStream(); - using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) + await using (var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) { - fileStream.CopyTo(inputStream); + await fileStream.CopyToAsync(inputStream); inputStream.Position = 0; } - using (var archive = GZipArchive.Open(inputStream)) + await using (var archive = GZipArchive.Open(inputStream)) { - var archiveEntry = archive.Entries.First(); + var archiveEntry = await archive.Entries.FirstAsync(); MemoryStream tarStream; - using (var entryStream = archiveEntry.OpenEntryStream()) + await using (var entryStream = archiveEntry.OpenEntryStream()) { tarStream = new MemoryStream(); - entryStream.CopyTo(tarStream); + await entryStream.CopyToAsync(tarStream); } var size = tarStream.Length; - using (var entryStream = archiveEntry.OpenEntryStream()) + await using (var entryStream = archiveEntry.OpenEntryStream()) { tarStream = new MemoryStream(); - entryStream.CopyTo(tarStream); + await entryStream.CopyToAsync(tarStream); } Assert.Equal(size, tarStream.Length); /*using (var entryStream = archiveEntry.OpenEntryStream()) @@ -100,10 +100,10 @@ namespace SharpCompress.Test.GZip var result = Archives.Tar.TarArchive.IsTarFile(entryStream); } Assert.Equal(size, tarStream.Length); */ - using (var entryStream = archiveEntry.OpenEntryStream()) + await using (var entryStream = archiveEntry.OpenEntryStream()) { tarStream = new MemoryStream(); - entryStream.CopyTo(tarStream); + await entryStream.CopyToAsync(tarStream); } Assert.Equal(size, tarStream.Length); } diff --git a/tests/SharpCompress.Test/SharpCompress.Test.csproj b/tests/SharpCompress.Test/SharpCompress.Test.csproj index 6c2a8f92..db67f752 100644 --- a/tests/SharpCompress.Test/SharpCompress.Test.csproj +++ b/tests/SharpCompress.Test/SharpCompress.Test.csproj @@ -7,6 +7,8 @@ true SharpCompress.Test true + true + true diff --git a/tests/SharpCompress.Test/Zip/Zip64Tests.cs b/tests/SharpCompress.Test/Zip/Zip64Tests.cs index 14cb205c..8f5ca01f 100644 --- a/tests/SharpCompress.Test/Zip/Zip64Tests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64Tests.cs @@ -201,11 +201,11 @@ namespace SharpCompress.Test.Zip public async ValueTask<(long, long)> ReadArchive(string filename) { - using (var archive = await ArchiveFactory.OpenAsync(filename)) + await using (var archive = await ArchiveFactory.OpenAsync(filename)) { return ( - archive.Entries.Count(), - archive.Entries.Select(x => x.Size).Sum() + await archive.Entries.CountAsync(), + await archive.Entries.Select(x => x.Size).SumAsync() ); } } diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 0c06b3b5..94ad2dca 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -159,10 +159,10 @@ namespace SharpCompress.Test.Zip string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); string modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); - using (var archive = ZipArchive.Open(unmodified)) + await using (var archive = ZipArchive.Open(unmodified)) { - var entry = archive.Entries.Single(x => x.Key.EndsWith("jpg")); - archive.RemoveEntry(entry); + var entry = await archive.Entries.SingleAsync(x => x.Key.EndsWith("jpg")); + await archive.RemoveEntryAsync(entry); WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); @@ -180,9 +180,9 @@ namespace SharpCompress.Test.Zip string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); string modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod2.zip"); - using (var archive = ZipArchive.Open(unmodified)) + await using (var archive = ZipArchive.Open(unmodified)) { - archive.AddEntry("jpg\\test.jpg", jpg); + await archive.AddEntryAsync("jpg\\test.jpg", jpg); WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); @@ -198,11 +198,11 @@ namespace SharpCompress.Test.Zip string scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); string scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); - using (var arc = ZipArchive.Create()) + await using (var arc = ZipArchive.Create()) { string str = "test.txt"; var source = new MemoryStream(Encoding.UTF8.GetBytes(str)); - arc.AddEntry("test.txt", source, true, source.Length); + await arc.AddEntryAsync("test.txt", source, true, source.Length); await arc.SaveToAsync(scratchPath1, CompressionType.Deflate); await arc.SaveToAsync(scratchPath2, CompressionType.Deflate); } @@ -216,20 +216,20 @@ namespace SharpCompress.Test.Zip string scratchPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using ZipArchive vfs = (ZipArchive)await ArchiveFactory.OpenAsync(scratchPath); - var e = vfs.Entries.First(v => v.Key.EndsWith("jpg")); - vfs.RemoveEntry(e); - Assert.Null(vfs.Entries.FirstOrDefault(v => v.Key.EndsWith("jpg"))); - Assert.Null(((IArchive)vfs).Entries.FirstOrDefault(v => v.Key.EndsWith("jpg"))); + await using ZipArchive vfs = (ZipArchive)await ArchiveFactory.OpenAsync(scratchPath); + var e = await vfs.Entries.FirstAsync(v => v.Key.EndsWith("jpg")); + await vfs.RemoveEntryAsync(e); + Assert.Null(await vfs.Entries.FirstOrDefaultAsync(v => v.Key.EndsWith("jpg"))); + Assert.Null(await ((IArchive)vfs).Entries.FirstOrDefaultAsync(v => v.Key.EndsWith("jpg"))); } [Fact] - public void Zip_Create_NoDups() + public async ValueTask Zip_Create_NoDups() { - using (var arc = ZipArchive.Create()) + await using (var arc = ZipArchive.Create()) { - arc.AddEntry("1.txt", new MemoryStream()); - Assert.Throws(() => arc.AddEntry("\\1.txt", new MemoryStream())); + await arc.AddEntryAsync("1.txt", new MemoryStream()); + await Assert.ThrowsAsync(async () => await arc.AddEntryAsync("\\1.txt", new MemoryStream())); } } @@ -239,12 +239,12 @@ namespace SharpCompress.Test.Zip string scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); string scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); - using (var arc = ZipArchive.Create()) + await using (var arc = ZipArchive.Create()) { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("qwert"))) + await using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("qwert"))) { - arc.AddEntry("1.txt", stream, false, stream.Length); - arc.AddEntry("2.txt", stream, false, stream.Length); + await arc.AddEntryAsync("1.txt", stream, false, stream.Length); + await arc.AddEntryAsync("2.txt", stream, false, stream.Length); await arc.SaveToAsync(scratchPath1, CompressionType.Deflate); await arc.SaveToAsync(scratchPath2, CompressionType.Deflate); } @@ -274,9 +274,9 @@ namespace SharpCompress.Test.Zip string scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); string unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using (var archive = ZipArchive.Create()) + await using (var archive = ZipArchive.Create()) { - archive.AddAllFromDirectory(SCRATCH_FILES_PATH); + await archive.AddAllFromDirectoryAsync(SCRATCH_FILES_PATH); WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); @@ -288,7 +288,7 @@ namespace SharpCompress.Test.Zip } [Fact] - public void Zip_Create_New_Add_Remove() + public async ValueTask Zip_Create_New_Add_Remove() { foreach (var file in Directory.EnumerateFiles(ORIGINAL_FILES_PATH, "*.*", SearchOption.AllDirectories)) { @@ -307,11 +307,11 @@ namespace SharpCompress.Test.Zip } string scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using (var archive = ZipArchive.Create()) + await using (var archive = ZipArchive.Create()) { - archive.AddAllFromDirectory(SCRATCH_FILES_PATH); - archive.RemoveEntry(archive.Entries.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))); - Assert.Null(archive.Entries.FirstOrDefault(x => x.Key.EndsWith("jpg"))); + await archive.AddAllFromDirectoryAsync(SCRATCH_FILES_PATH); + await archive.RemoveEntryAsync(await archive.Entries.SingleAsync(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))); + Assert.Null(await archive.Entries.FirstOrDefaultAsync(x => x.Key.EndsWith("jpg"))); } Directory.Delete(SCRATCH_FILES_PATH, true); } @@ -319,12 +319,12 @@ namespace SharpCompress.Test.Zip [Fact] public async ValueTask Zip_Deflate_WinzipAES_Read() { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"), new ReaderOptions() + await using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"), new ReaderOptions() { Password = "test" })) { - foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) + await foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions() { @@ -337,14 +337,14 @@ namespace SharpCompress.Test.Zip } [Fact] - public void Zip_Deflate_WinzipAES_MultiOpenEntryStream() + public async ValueTask Zip_Deflate_WinzipAES_MultiOpenEntryStream() { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES2.zip"), new ReaderOptions() + await using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES2.zip"), new ReaderOptions() { Password = "test" })) { - foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) + await foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) { var stream = entry.OpenEntryStream(); Assert.NotNull(stream); @@ -355,30 +355,30 @@ namespace SharpCompress.Test.Zip } [Fact] - public void Zip_Read_Volume_Comment() + public async ValueTask Zip_Read_Volume_Comment() { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.zip"), new ReaderOptions() + await using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.zip"), new ReaderOptions() { Password = "test" })) { - var isComplete = reader.IsComplete; - Assert.Equal(1, reader.Volumes.Count); + var isComplete = await reader.IsCompleteAsync(); + Assert.Equal(1, await reader.Volumes.CountAsync()); string expectedComment = "Encoding:utf-8 || Compression:Deflate levelDefault || Encrypt:None || ZIP64:Always\r\nCreated at 2017-Jan-23 14:10:43 || DotNetZip Tool v1.9.1.8\r\nTest zip64 archive"; - Assert.Equal(expectedComment, reader.Volumes.First().Comment); + Assert.Equal(expectedComment, (await reader.Volumes.FirstAsync()).Comment); } } [Fact] public async ValueTask Zip_BZip2_Pkware_Read() { - using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"), new ReaderOptions() + await using (var reader = ZipArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"), new ReaderOptions() { Password = "test" })) { - foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) + await foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions() { @@ -397,19 +397,19 @@ namespace SharpCompress.Test.Zip ZipArchive a = ZipArchive.Open(unmodified); int count = 0; - foreach (var e in a.Entries) + await foreach (var e in a.Entries) { count++; } //Prints 3 Assert.Equal(3, count); - a.Dispose(); + await a.DisposeAsync(); a = ZipArchive.Open(unmodified); int count2 = 0; - foreach (var e in a.Entries) + await foreach (var e in a.Entries) { count2++; @@ -425,7 +425,7 @@ namespace SharpCompress.Test.Zip } int count3 = 0; - foreach (var e in a.Entries) + await foreach (var e in a.Entries) { count3++; } @@ -439,9 +439,9 @@ namespace SharpCompress.Test.Zip string zipFile = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.pkware.zip"); await using FileStream fileStream = File.Open(zipFile, FileMode.Open); - using IArchive archive = await ArchiveFactory.OpenAsync(fileStream, new ReaderOptions { Password = "12345678" }); + await using IArchive archive = await ArchiveFactory.OpenAsync(fileStream, new ReaderOptions { Password = "12345678" }); var entries = archive.Entries.Where(entry => !entry.IsDirectory); - foreach (IArchiveEntry entry in entries) + await foreach (IArchiveEntry entry in entries) { for (var i = 0; i < 100; i++) { @@ -464,9 +464,9 @@ namespace SharpCompress.Test.Zip await Assert.ThrowsAnyAsync(async () => { - using (var archive = ZipArchive.Open(zipFile)) + await using (var archive = ZipArchive.Open(zipFile)) { - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, new ExtractionOptions() { @@ -497,9 +497,9 @@ namespace SharpCompress.Test.Zip stream = new MemoryStream(stream.ToArray()); await File.WriteAllBytesAsync(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), stream.ToArray()); - using (var zipArchive = ZipArchive.Open(stream)) + await using (var zipArchive = ZipArchive.Open(stream)) { - foreach (var entry in zipArchive.Entries) + await foreach (var entry in zipArchive.Entries) { await using (var entryStream = entry.OpenEntryStream()) { @@ -521,14 +521,14 @@ namespace SharpCompress.Test.Zip { string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.badlocalextra.zip"); - using (ZipArchive za = ZipArchive.Open(zipPath)) + await using (ZipArchive za = ZipArchive.Open(zipPath)) { var ex = await Record.ExceptionAsync(async () => { - var firstEntry = za.Entries.First(x => x.Key == "first.txt"); + var firstEntry = await za.Entries.FirstAsync(x => x.Key == "first.txt"); var buffer = new byte[4096]; - using (var memoryStream = new MemoryStream()) + await using (var memoryStream = new MemoryStream()) using (var firstStream = firstEntry.OpenEntryStream()) { await firstStream.CopyToAsync(memoryStream); @@ -541,19 +541,19 @@ namespace SharpCompress.Test.Zip } [Fact] - public void Zip_NoCompression_DataDescriptors_Read() + public async ValueTask Zip_NoCompression_DataDescriptors_Read() { string zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.datadescriptors.zip"); - using (ZipArchive za = ZipArchive.Open(zipPath)) + await using (ZipArchive za = ZipArchive.Open(zipPath)) { - var firstEntry = za.Entries.First(x => x.Key == "first.txt"); + var firstEntry = await za.Entries.FirstAsync(x => x.Key == "first.txt"); var buffer = new byte[4096]; using (var memoryStream = new MemoryStream()) using (var firstStream = firstEntry.OpenEntryStream()) { - firstStream.CopyTo(memoryStream); + await firstStream.CopyToAsync(memoryStream); Assert.Equal(199, memoryStream.Length); } }