mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-04 05:25:00 +00:00
IArchiveAsync
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
public abstract class AbstractArchive<TEntry, TVolume> : IArchive
|
||||
public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveAsync
|
||||
where TEntry : IArchiveEntry
|
||||
where TVolume : IVolume
|
||||
{
|
||||
@@ -26,6 +25,8 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive
|
||||
_sourceStream = sourceStream;
|
||||
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(_sourceStream));
|
||||
_lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
|
||||
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(LoadVolumesAsync(_sourceStream));
|
||||
_lazyEntriesAsync = new LazyAsyncReadOnlyCollection<TEntry>(LoadEntriesAsync(_lazyVolumesAsync));
|
||||
}
|
||||
|
||||
internal AbstractArchive(ArchiveType type)
|
||||
@@ -34,24 +35,16 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive
|
||||
ReaderOptions = new();
|
||||
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
|
||||
_lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
|
||||
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(AsyncEnumerableEx.Empty<TVolume>());
|
||||
_lazyEntriesAsync = new LazyAsyncReadOnlyCollection<TEntry>(AsyncEnumerableEx.Empty<TEntry>());
|
||||
}
|
||||
|
||||
public ArchiveType Type { get; }
|
||||
|
||||
private static Stream CheckStreams(Stream stream)
|
||||
{
|
||||
if (!stream.CanSeek || !stream.CanRead)
|
||||
{
|
||||
throw new ArchiveException("Archive streams must be Readable and Seekable");
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
|
||||
/// </summary>
|
||||
public virtual ICollection<TEntry> Entries => _lazyEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
|
||||
/// </summary>
|
||||
@@ -72,6 +65,9 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive
|
||||
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream sourceStream);
|
||||
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
|
||||
|
||||
|
||||
protected abstract IAsyncEnumerable<TVolume> LoadVolumesAsync(SourceStream sourceStream);
|
||||
protected abstract IAsyncEnumerable<TEntry> LoadEntriesAsync(IAsyncEnumerable<TVolume> volumes);
|
||||
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
|
||||
|
||||
IEnumerable<IVolume> IArchive.Volumes => _lazyVolumes.Cast<IVolume>();
|
||||
@@ -140,4 +136,67 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive
|
||||
return Entries.All(x => x.IsComplete);
|
||||
}
|
||||
}
|
||||
|
||||
#region Async Support
|
||||
|
||||
private readonly LazyAsyncReadOnlyCollection<TVolume> _lazyVolumesAsync;
|
||||
private readonly LazyAsyncReadOnlyCollection<TEntry> _lazyEntriesAsync;
|
||||
|
||||
|
||||
public virtual async ValueTask DisposeAsync()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
await foreach (var v in _lazyVolumesAsync)
|
||||
{
|
||||
v.Dispose();
|
||||
}
|
||||
foreach (var v in _lazyEntriesAsync.GetLoaded().Cast<Entry>())
|
||||
{
|
||||
v.Close();
|
||||
}
|
||||
_sourceStream?.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask EnsureEntriesLoadedAsync()
|
||||
{
|
||||
await _lazyEntriesAsync.EnsureFullyLoaded();
|
||||
await _lazyVolumesAsync.EnsureFullyLoaded();
|
||||
}
|
||||
|
||||
public virtual IAsyncEnumerable<TEntry> EntriesAsync => _lazyEntriesAsync;
|
||||
IAsyncEnumerable<IArchiveEntry> IArchiveAsync.EntriesAsync => EntriesAsync.Cast<TEntry, IArchiveEntry>();
|
||||
|
||||
public IAsyncEnumerable<IVolume> VolumesAsync => _lazyVolumesAsync.Cast<TVolume, IVolume>();
|
||||
public async ValueTask<IReader> ExtractAllEntriesAsync()
|
||||
{
|
||||
if (!IsSolid && Type != ArchiveType.SevenZip)
|
||||
{
|
||||
throw new SharpCompressException(
|
||||
"ExtractAllEntries can only be used on solid archives or 7Zip archives (which require random access)."
|
||||
);
|
||||
}
|
||||
await EnsureEntriesLoadedAsync();
|
||||
return await CreateReaderForSolidExtractionAsync();
|
||||
}
|
||||
|
||||
|
||||
protected abstract ValueTask<IReader> CreateReaderForSolidExtractionAsync();
|
||||
|
||||
public virtual ValueTask<bool> IsSolidAsync() => new (false);
|
||||
|
||||
public async ValueTask<bool> IsCompleteAsync()
|
||||
{
|
||||
await EnsureEntriesLoadedAsync();
|
||||
return await EntriesAsync.All(x => x.IsComplete);
|
||||
}
|
||||
|
||||
public async ValueTask<long> TotalSizeAsync() => await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.CompressedSize);
|
||||
|
||||
public async ValueTask<long> TotalUncompressSizeAsync() => await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.Size);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,10 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
public interface IArchiveAsync : IAsyncDisposable
|
||||
{
|
||||
IAsyncEnumerable<IArchiveEntry> EntriesAsync { get; }
|
||||
IAsyncEnumerable<IVolume> VolumesAsync { get; }
|
||||
|
||||
ArchiveType Type { get; }
|
||||
|
||||
/// <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>
|
||||
ValueTask<IReader> ExtractAllEntriesAsync();
|
||||
|
||||
/// <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>
|
||||
ValueTask<bool> IsSolidAsync();
|
||||
|
||||
/// <summary>
|
||||
/// This checks to see if all the known entries have IsComplete = true
|
||||
/// </summary>
|
||||
ValueTask<bool> IsCompleteAsync();
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files compressed in the archive.
|
||||
/// </summary>
|
||||
ValueTask<long> TotalSizeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files as uncompressed in the archive.
|
||||
/// </summary>
|
||||
ValueTask<long> TotalUncompressSizeAsync();
|
||||
}
|
||||
|
||||
|
||||
public interface IArchive : IDisposable
|
||||
{
|
||||
IEnumerable<IArchiveEntry> Entries { get; }
|
||||
|
||||
96
src/SharpCompress/LazyAsyncReadOnlyCollection.cs
Normal file
96
src/SharpCompress/LazyAsyncReadOnlyCollection.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
#nullable disable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress;
|
||||
|
||||
internal sealed class LazyAsyncReadOnlyCollection<T>(IAsyncEnumerable<T> source) : IAsyncEnumerable<T>
|
||||
{
|
||||
private readonly List<T> backing = new();
|
||||
private readonly IAsyncEnumerator<T> source = source.GetAsyncEnumerator();
|
||||
private bool fullyLoaded;
|
||||
|
||||
private class LazyLoader(LazyAsyncReadOnlyCollection<T> lazyReadOnlyCollection, CancellationToken cancellationToken) : IAsyncEnumerator<T>
|
||||
{
|
||||
private bool disposed;
|
||||
private int index = -1;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> MoveNextAsync()
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (index + 1 < lazyReadOnlyCollection.backing.Count)
|
||||
{
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
if (!lazyReadOnlyCollection.fullyLoaded && await lazyReadOnlyCollection.source.MoveNextAsync())
|
||||
{
|
||||
lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.source.Current);
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
lazyReadOnlyCollection.fullyLoaded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#region IEnumerator<T> Members
|
||||
|
||||
public T Current => lazyReadOnlyCollection.backing[index];
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
internal async ValueTask EnsureFullyLoaded()
|
||||
{
|
||||
if (!fullyLoaded)
|
||||
{
|
||||
var loader = new LazyLoader(this, CancellationToken.None);
|
||||
while (await loader.MoveNextAsync())
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
fullyLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<T> GetLoaded() => backing;
|
||||
|
||||
#region ICollection<T> Members
|
||||
|
||||
public void Add(T item) => throw new NotSupportedException();
|
||||
|
||||
public void Clear() => throw new NotSupportedException();
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public bool Remove(T item) => throw new NotSupportedException();
|
||||
|
||||
#endregion
|
||||
|
||||
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) => new LazyLoader(this, cancellationToken);
|
||||
}
|
||||
@@ -1,13 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress;
|
||||
|
||||
public static class AsyncEnumerableEx
|
||||
{
|
||||
public static async IAsyncEnumerable<T> Empty<T>()
|
||||
where T : notnull
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AsyncEnumerableExtensions
|
||||
{
|
||||
extension<T>(IAsyncEnumerable<T> source)
|
||||
where T : notnull
|
||||
{
|
||||
public async IAsyncEnumerable<TResult> Cast<TResult>()
|
||||
where TResult : class
|
||||
{
|
||||
await foreach (var item in source)
|
||||
{
|
||||
yield return (item as TResult).NotNull();
|
||||
}
|
||||
}
|
||||
public async ValueTask<bool> All(Func<T, bool> predicate)
|
||||
{
|
||||
await foreach (var item in source)
|
||||
{
|
||||
if (!predicate(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public async IAsyncEnumerable<T> Where(Func<T, bool> predicate)
|
||||
{
|
||||
await foreach (var item in source)
|
||||
@@ -28,5 +60,15 @@ public static class AsyncEnumerableExtensions
|
||||
|
||||
return default; // Returns null/default if the stream is empty
|
||||
}
|
||||
|
||||
public async ValueTask<TAccumulate> Aggregate<TAccumulate>(TAccumulate seed, Func<TAccumulate, T, TAccumulate> func)
|
||||
{
|
||||
TAccumulate result = seed;
|
||||
await foreach (var element in source)
|
||||
{
|
||||
result = func(result, element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +609,7 @@ public class ArchiveTests : ReaderTests
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||
await foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
|
||||
{
|
||||
await entry.WriteToDirectoryAsync(
|
||||
SCRATCH_FILES_PATH,
|
||||
|
||||
Reference in New Issue
Block a user