AsyncEnumerable usage in entries

This commit is contained in:
Adam Hathcock
2021-02-07 09:08:15 +00:00
parent 9cf8a3dbbe
commit cdba5ec419
21 changed files with 357 additions and 407 deletions

View File

@@ -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<TEntry, TVolume> : IArchive, IArchiveExtractionListener
public abstract class AbstractArchive<TEntry, TVolume> : IArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private readonly LazyReadOnlyCollection<TVolume> lazyVolumes;
private readonly LazyReadOnlyCollection<TEntry> lazyEntries;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionBegin;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionEnd;
public event EventHandler<CompressedBytesReadEventArgs>? CompressedBytesRead;
public event EventHandler<FilePartExtractionBeginEventArgs>? FilePartExtractionBegin;
protected ReaderOptions ReaderOptions { get; }
protected ReaderOptions ReaderOptions { get; } = new ();
private bool disposed;
@@ -38,9 +33,9 @@ namespace SharpCompress.Archives
}
protected abstract IEnumerable<TVolume> LoadVolumes(FileInfo file);
protected abstract IAsyncEnumerable<TVolume> LoadVolumes(FileInfo file);
internal AbstractArchive(ArchiveType type, IEnumerable<Stream> streams, ReaderOptions readerOptions)
internal AbstractArchive(ArchiveType type, IAsyncEnumerable<Stream> streams, ReaderOptions readerOptions)
{
Type = type;
ReaderOptions = readerOptions;
@@ -48,27 +43,15 @@ namespace SharpCompress.Archives
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
}
#nullable disable
internal AbstractArchive(ArchiveType type)
{
Type = type;
lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
lazyVolumes = new LazyReadOnlyCollection<TVolume>( AsyncEnumerable.Empty<TVolume>());
lazyEntries = new LazyReadOnlyCollection<TEntry>(AsyncEnumerable.Empty<TEntry>());
}
#nullable enable
public ArchiveType Type { get; }
void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry)
{
EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
}
void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry)
{
EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
}
private static Stream CheckStreams(Stream stream)
{
if (!stream.CanSeek || !stream.CanRead)
@@ -81,63 +64,48 @@ namespace SharpCompress.Archives
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
/// </summary>
public virtual ICollection<TEntry> Entries => lazyEntries;
public virtual IAsyncEnumerable<TEntry> Entries => lazyEntries;
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
/// </summary>
public ICollection<TVolume> Volumes => lazyVolumes;
public IAsyncEnumerable<TVolume> Volumes => lazyVolumes;
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
public virtual long TotalSize => Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize);
public virtual async ValueTask<long> TotalSizeAsync()
{
await EnsureEntriesLoaded();
return await Entries.AggregateAsync(0L, (total, cf) => total + cf.CompressedSize);
}
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size);
public virtual async ValueTask<long> TotalUncompressedSizeAsync()
{
await EnsureEntriesLoaded();
return await Entries.AggregateAsync(0L, (total, cf) => total + cf.Size);
}
protected abstract IEnumerable<TVolume> LoadVolumes(IEnumerable<Stream> streams);
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
protected abstract IAsyncEnumerable<TVolume> LoadVolumes(IAsyncEnumerable<Stream> streams);
protected abstract IAsyncEnumerable<TEntry> LoadEntries(IAsyncEnumerable<TVolume> volumes);
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
IAsyncEnumerable<IArchiveEntry> IArchive.Entries => Entries.Select(x => (IArchiveEntry)x);
IEnumerable<IVolume> IArchive.Volumes => lazyVolumes.Cast<IVolume>();
IAsyncEnumerable<IVolume> 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<Entry>().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
));
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
public IReader ExtractAllEntries()
public async ValueTask<IReader> 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<IReader> CreateReaderForSolidExtraction();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// </summary>
public virtual bool IsSolid => false;
public virtual ValueTask<bool> IsSolidAsync() => new(false);
/// <summary>
/// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive.
/// </summary>
public bool IsComplete
public async ValueTask<bool> IsCompleteAsync()
{
get
{
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return Entries.All(x => x.IsComplete);
}
await EnsureEntriesLoaded();
return await Entries.AllAsync(x => x.IsComplete);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace SharpCompress.Archives
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private class RebuildPauseDisposable : IDisposable
private class RebuildPauseDisposable : IAsyncDisposable
{
private readonly AbstractWritableArchive<TEntry, TVolume> 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<TEntry> newEntries = new List<TEntry>();
private readonly List<TEntry> removedEntries = new List<TEntry>();
private readonly List<TEntry> newEntries = new();
private readonly List<TEntry> removedEntries = new();
private readonly List<TEntry> modifiedEntries = new List<TEntry>();
private readonly List<TEntry> 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<TEntry> Entries
public override IAsyncEnumerable<TEntry> 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<TEntry> OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } }
private IAsyncEnumerable<TEntry> 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<TEntry> 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<IArchiveEntry> 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<TEntry> 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<bool> 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<IWritableArchiveEntry>().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<TEntry> 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<TEntry> CreateEntryInternal(string key, Stream source, long size, DateTime? modified,
bool closeStream, CancellationToken cancellationToken);
protected abstract Task SaveToAsync(Stream stream, WriterOptions options, IEnumerable<TEntry> oldEntries, IEnumerable<TEntry> newEntries,
CancellationToken cancellationToken = default);
protected abstract ValueTask SaveToAsync(Stream stream, WriterOptions options, IAsyncEnumerable<TEntry> oldEntries, IAsyncEnumerable<TEntry> newEntries,
CancellationToken cancellationToken = default);
public override void Dispose()
public override async ValueTask DisposeAsync()
{
base.Dispose();
await base.DisposeAsync();
newEntries.Cast<Entry>().ForEach(x => x.Close());
removedEntries.Cast<Entry>().ForEach(x => x.Close());
modifiedEntries.Cast<Entry>().ForEach(x => x.Close());

View File

@@ -20,7 +20,7 @@ namespace SharpCompress.Archives
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <returns></returns>
public static async ValueTask<IArchive> OpenAsync(Stream stream, ReaderOptions? readerOptions = null)
public static async ValueTask<IArchive> 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
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static async ValueTask<IArchive> OpenAsync(FileInfo fileInfo, ReaderOptions? options = null)
public static async ValueTask<IArchive> 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);
}

View File

@@ -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<GZipVolume> LoadVolumes(FileInfo file)
protected override IAsyncEnumerable<GZipVolume> LoadVolumes(FileInfo file)
{
return new GZipVolume(file, ReaderOptions).AsEnumerable();
return new GZipVolume(file, ReaderOptions).AsAsyncEnumerable();
}
public static bool IsGZipFile(string filePath)
public static ValueTask<bool> 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<bool> 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<bool> IsGZipFileAsync(Stream stream, CancellationToken cancellationToken = default)
{
// read the header on the first read
byte[] header = new byte[10];
using var header = MemoryPool<byte>.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<GZipArchiveEntry> 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<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries,
protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options,
IAsyncEnumerable<GZipArchiveEntry> oldEntries,
IAsyncEnumerable<GZipArchiveEntry> 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<GZipVolume> LoadVolumes(IEnumerable<Stream> streams)
protected override async IAsyncEnumerable<GZipVolume> LoadVolumes(IAsyncEnumerable<Stream> streams)
{
return new GZipVolume(streams.First(), ReaderOptions).AsEnumerable();
yield return new GZipVolume(await streams.FirstAsync(), ReaderOptions);
}
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
protected override async IAsyncEnumerable<GZipArchiveEntry> LoadEntries(IAsyncEnumerable<GZipVolume> 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<IReader> CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
var stream = (await Volumes.SingleAsync()).Stream;
stream.Position = 0;
return GZipReader.Open(stream);
}

View File

@@ -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<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionBegin;
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionEnd;
event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
IEnumerable<IArchiveEntry> Entries { get; }
IEnumerable<IVolume> Volumes { get; }
IAsyncEnumerable<IArchiveEntry> Entries { get; }
IAsyncEnumerable<IVolume> Volumes { get; }
ArchiveType Type { get; }
ValueTask EnsureEntriesLoaded();
/// <summary>
/// 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.
/// </summary>
IReader ExtractAllEntries();
ValueTask<IReader> ExtractAllEntries();
/// <summary>
/// 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.
/// </summary>
bool IsSolid { get; }
ValueTask<bool> IsSolidAsync();
/// <summary>
/// This checks to see if all the known entries have IsComplete = true
/// </summary>
bool IsComplete { get; }
ValueTask<bool> IsCompleteAsync();
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
long TotalSize { get; }
ValueTask<long> TotalSizeAsync();
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
long TotalUncompressSize { get; }
ValueTask<long> TotalUncompressedSizeAsync();
}
}

View File

@@ -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);
}
/// <summary>

View File

@@ -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);
}

View File

@@ -1,11 +0,0 @@
using SharpCompress.Common;
namespace SharpCompress.Archives
{
internal interface IArchiveExtractionListener : IExtractionListener
{
void EnsureEntriesLoaded();
void FireEntryExtractionBegin(IArchiveEntry entry);
void FireEntryExtractionEnd(IArchiveEntry entry);
}
}

View File

@@ -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<IArchiveEntry> 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);
/// <summary>
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
/// </summary>
/// <returns>IDisposeable to resume entry rebuilding</returns>
IDisposable PauseEntryRebuilding();
IAsyncDisposable PauseEntryRebuilding();
}
}

View File

@@ -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<IArchiveEntry> 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);
}
}
}

View File

@@ -118,9 +118,9 @@ namespace SharpCompress.Archives.Zip
headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding);
}
protected override IEnumerable<ZipVolume> LoadVolumes(FileInfo file)
protected override IAsyncEnumerable<ZipVolume> 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<ZipVolume> LoadVolumes(IEnumerable<Stream> streams)
protected override async IAsyncEnumerable<ZipVolume> LoadVolumes(IAsyncEnumerable<Stream> streams)
{
return new ZipVolume(streams.First(), ReaderOptions).AsEnumerable();
yield return new ZipVolume(await streams.FirstAsync(), ReaderOptions);
}
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
protected override async IAsyncEnumerable<ZipArchiveEntry> LoadEntries(IAsyncEnumerable<ZipVolume> 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<byte>();
volume.Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
yield break;
}
{
byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty<byte>();
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<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries,
CancellationToken cancellationToken = default)
protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options,
IAsyncEnumerable<ZipArchiveEntry> oldEntries,
IAsyncEnumerable<ZipArchiveEntry> 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<ZipArchiveEntry> 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<IReader> CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
var stream = (await Volumes.SingleAsync()).Stream;
stream.Position = 0;
return ZipReader.Open(stream, ReaderOptions);
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress
{
public static class AsyncEnumerable
{
public static IAsyncEnumerable<T> Empty<T>() => EmptyAsyncEnumerable<T>.Instance;
private class EmptyAsyncEnumerable<T> : IAsyncEnumerator<T>, IAsyncEnumerable<T>
{
public static readonly EmptyAsyncEnumerable<T> Instance =
new();
public T Current => default!;
public ValueTask DisposeAsync() => default;
public ValueTask<bool> MoveNextAsync() => new(false);
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
{
return this;
}
}
}
}

View File

@@ -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<T> : ICollection<T>
internal sealed class LazyReadOnlyCollection<T> : IAsyncEnumerable<T>
{
private readonly List<T> backing = new List<T>();
private readonly IEnumerator<T> source;
private readonly List<T> backing = new();
private IAsyncEnumerator<T>? enumerator;
private readonly IAsyncEnumerable<T> enumerable;
private bool fullyLoaded;
public LazyReadOnlyCollection(IEnumerable<T> source)
public LazyReadOnlyCollection(IAsyncEnumerable<T> source)
{
this.source = source.GetEnumerator();
enumerable = source;
}
private class LazyLoader : IEnumerator<T>
private IAsyncEnumerator<T> GetEnumerator()
{
if (enumerator is null)
{
enumerator = enumerable.GetAsyncEnumerator();
}
return enumerator;
}
private class LazyLoader : IAsyncEnumerator<T>
{
private readonly LazyReadOnlyCollection<T> lazyReadOnlyCollection;
private bool disposed;
@@ -28,58 +36,43 @@ namespace SharpCompress
this.lazyReadOnlyCollection = lazyReadOnlyCollection;
}
#region IEnumerator<T> 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<bool> 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<T> 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<T> Members
//TODO check for concurrent access
public IEnumerator<T> GetEnumerator()
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken)
{
return new LazyLoader(this);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}

View File

@@ -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);

View File

@@ -8,6 +8,7 @@
<Authors>Adam Hathcock</Authors>
<TargetFrameworks>netstandard2.1;netcoreapp3.1;net5.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>true</WarningsAsErrors>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<AssemblyName>SharpCompress</AssemblyName>
<AssemblyOriginatorKeyFile>../../SharpCompress.snk</AssemblyOriginatorKeyFile>
@@ -29,6 +30,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />

View File

@@ -94,6 +94,11 @@ namespace SharpCompress
{
yield return item;
}
public static async IAsyncEnumerable<T> AsAsyncEnumerable<T>(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<bool> ReadFullyAsync(this Stream stream, Memory<byte> 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)
{

View File

@@ -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()

View File

@@ -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<InvalidOperationException>(() => archive.AddEntry("jpg\\test.jpg", jpg));
await Assert.ThrowsAsync<InvalidOperationException>(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);
}

View File

@@ -7,6 +7,8 @@
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageId>SharpCompress.Test</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>true</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\SharpCompress\SharpCompress.csproj" />

View File

@@ -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()
);
}
}

View File

@@ -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<ArchiveException>(() => arc.AddEntry("\\1.txt", new MemoryStream()));
await arc.AddEntryAsync("1.txt", new MemoryStream());
await Assert.ThrowsAsync<ArchiveException>(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<Exception>(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);
}
}