mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-25 00:15:15 +00:00
AsyncEnumerable usage in entries
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
{
|
||||
internal interface IArchiveExtractionListener : IExtractionListener
|
||||
{
|
||||
void EnsureEntriesLoaded();
|
||||
void FireEntryExtractionBegin(IArchiveEntry entry);
|
||||
void FireEntryExtractionEnd(IArchiveEntry entry);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
25
src/SharpCompress/AsyncEnumerable.cs
Normal file
25
src/SharpCompress/AsyncEnumerable.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user