read async interface for reader

This commit is contained in:
Adam Hathcock
2026-01-08 11:28:15 +00:00
parent 406b198e0e
commit 7aec98d652
34 changed files with 360 additions and 213 deletions

View File

@@ -133,6 +133,7 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveAsync
}
protected abstract IReader CreateReaderForSolidExtraction();
protected abstract ValueTask<IReaderAsync> CreateReaderForSolidExtractionAsync();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
@@ -191,7 +192,7 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveAsync
public IAsyncEnumerable<IVolume> VolumesAsync => _lazyVolumesAsync.Cast<TVolume, IVolume>();
public async ValueTask<IReader> ExtractAllEntriesAsync()
public async ValueTask<IReaderAsync> ExtractAllEntriesAsync()
{
if (!IsSolid && Type != ArchiveType.SevenZip)
{
@@ -203,9 +204,6 @@ public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveAsync
return await CreateReaderForSolidExtractionAsync();
}
protected virtual ValueTask<IReader> CreateReaderForSolidExtractionAsync() =>
new(CreateReaderForSolidExtraction());
public virtual ValueTask<bool> IsSolidAsync() => new(false);
public async ValueTask<bool> IsCompleteAsync()

View File

@@ -336,4 +336,11 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
stream.Position = 0;
return GZipReader.Open(stream);
}
protected override ValueTask<IReaderAsync> CreateReaderForSolidExtractionAsync()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return new(GZipReader.Open(stream));
}
}

View File

@@ -18,7 +18,7 @@ public interface IArchiveAsync : IAsyncDisposable
/// 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();
ValueTask<IReaderAsync> ExtractAllEntriesAsync();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).

View File

@@ -30,7 +30,7 @@ public static class IArchiveAsyncExtensions
// For solid archives (Rar, 7Zip), use the optimized reader-based approach
if (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip)
{
using var reader = await archive.ExtractAllEntriesAsync();
await using var reader = await archive.ExtractAllEntriesAsync();
await reader.WriteAllToDirectoryAsync(
destinationDirectory,
options,

View File

@@ -67,7 +67,13 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable();
}
protected override IReader CreateReaderForSolidExtraction()
protected override IReader CreateReaderForSolidExtraction() =>
CreateReaderForSolidExtractionInternal();
protected override ValueTask<IReaderAsync> CreateReaderForSolidExtractionAsync() =>
new(CreateReaderForSolidExtractionInternal());
private RarReader CreateReaderForSolidExtractionInternal()
{
if (this.IsMultipartVolume())
{

View File

@@ -265,6 +265,9 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
protected override IReader CreateReaderForSolidExtraction() =>
new SevenZipReader(ReaderOptions, this);
protected override ValueTask<IReaderAsync> CreateReaderForSolidExtractionAsync() =>
new(new SevenZipReader(ReaderOptions, this));
public override bool IsSolid =>
Entries
.Where(x => !x.IsDirectory)

View File

@@ -366,4 +366,11 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
stream.Position = 0;
return TarReader.Open(stream);
}
protected override ValueTask<IReaderAsync> CreateReaderForSolidExtractionAsync()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return new(TarReader.Open(stream));
}
}

View File

@@ -592,4 +592,11 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
((IStreamStack)stream).StackSeek(0);
return ZipReader.Open(stream, ReaderOptions, Entries);
}
protected override ValueTask<IReaderAsync> CreateReaderForSolidExtractionAsync()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return new(ZipReader.Open(stream));
}
}

View File

@@ -27,18 +27,21 @@ namespace SharpCompress.Factories
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
return AceHeader.IsArchive(stream);
}
) => AceHeader.IsArchive(stream);
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
AceReader.Open(stream, options);
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
) => new(OpenReader(stream, options));
) => new(AceReader.Open(stream, options));
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
}
}

View File

@@ -44,14 +44,16 @@ namespace SharpCompress.Factories
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
ArcReader.Open(stream, options);
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new(OpenReader(stream, options));
}
) => new(ArcReader.Open(stream, options));
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
}
}

View File

@@ -35,14 +35,16 @@ namespace SharpCompress.Factories
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
ArjReader.Open(stream, options);
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new(OpenReader(stream, options));
}
) => new(ArjReader.Open(stream, options));
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
}
}

View File

@@ -59,6 +59,12 @@ public abstract class Factory : IFactory
int bufferSize = ReaderOptions.DefaultBufferSize
);
public abstract ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
);
/// <inheritdoc/>
public virtual ValueTask<bool> IsArchiveAsync(
Stream stream,
@@ -106,4 +112,34 @@ public abstract class Factory : IFactory
return false;
}
internal virtual async ValueTask<(bool, IReaderAsync?)> TryOpenReaderAsync(
SharpCompressStream stream,
ReaderOptions options,
CancellationToken cancellationToken
)
{
if (this is IReaderFactory readerFactory)
{
long pos = ((IStreamStack)stream).GetPosition();
if (
await IsArchiveAsync(
stream,
options.Password,
options.BufferSize,
cancellationToken
)
)
{
((IStreamStack)stream).StackSeek(pos);
return (
true,
await readerFactory.OpenReaderAsync(stream, options, cancellationToken)
);
}
}
return (false, null);
}
}

View File

@@ -71,6 +71,12 @@ public class GZipFactory
CancellationToken cancellationToken = default
) => GZipArchive.OpenAsync(stream, readerOptions, cancellationToken);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
/// <inheritdoc/>
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
GZipArchive.Open(fileInfo, readerOptions);
@@ -147,14 +153,14 @@ public class GZipFactory
GZipReader.Open(stream, options);
/// <inheritdoc/>
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new(OpenReader(stream, options));
return new(GZipReader.Open(stream, options));
}
#endregion

View File

@@ -67,6 +67,12 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
CancellationToken cancellationToken = default
) => RarArchive.OpenAsync(fileInfo, readerOptions, cancellationToken);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
#endregion
#region IMultiArchiveFactory
@@ -102,14 +108,14 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
RarReader.Open(stream, options);
/// <inheritdoc/>
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new(OpenReader(stream, options));
return new(RarReader.Open(stream, options));
}
#endregion

View File

@@ -62,6 +62,12 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory
CancellationToken cancellationToken = default
) => SevenZipArchive.OpenAsync(fileInfo, readerOptions, cancellationToken);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
#endregion
#region IMultiArchiveFactory

View File

@@ -61,6 +61,12 @@ public class TarFactory
int bufferSize = ReaderOptions.DefaultBufferSize
) => TarArchive.IsTarFile(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
#endregion
#region IArchiveFactory
@@ -265,14 +271,14 @@ public class TarFactory
TarReader.Open(stream, options);
/// <inheritdoc/>
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new(OpenReader(stream, options));
return new(TarReader.Open(stream, options));
}
#endregion

View File

@@ -25,4 +25,10 @@ internal class ZStandardFactory : Factory
string? password = null,
int bufferSize = 65536
) => ZStandardStream.IsZStandard(stream);
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
}

View File

@@ -81,6 +81,12 @@ public class ZipFactory
return false;
}
public override ValueTask<bool> IsArchiveAsync(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => new(IsArchive(stream, password, bufferSize));
/// <inheritdoc/>
public override async ValueTask<bool> IsArchiveAsync(
Stream stream,
@@ -189,14 +195,14 @@ public class ZipFactory
ZipReader.Open(stream, options);
/// <inheritdoc/>
public ValueTask<IReader> OpenReaderAsync(
public ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new(OpenReader(stream, options));
return new(ZipReader.Open(stream, options));
}
#endregion

View File

@@ -12,17 +12,13 @@ namespace SharpCompress.Readers;
/// <summary>
/// A generic push reader that reads unseekable comrpessed streams.
/// </summary>
public abstract class AbstractReader<TEntry, TVolume> : IReader
public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderAsync
where TEntry : Entry
where TVolume : Volume
{
private bool _completed;
private IEnumerator<TEntry>? _entriesForCurrentReadStream;
/// <summary>
/// Holds the async entry enumerator when the reader is operating in an async-only mode.
/// </summary>
private IAsyncEnumerator<TEntry>? _asyncEntriesForCurrentReadStream;
private IAsyncEnumerator<TEntry>? _entriesForCurrentReadStreamAsync;
private bool _wroteCurrentEntry;
internal AbstractReader(ReaderOptions options, ArchiveType archiveType)
@@ -43,19 +39,31 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
/// <summary>
/// Current file entry (from either sync or async enumeration).
/// </summary>
public TEntry Entry =>
_entriesForCurrentReadStream?.Current
?? _asyncEntriesForCurrentReadStream?.Current
?? throw new InvalidOperationException("No current entry is available.");
public TEntry Entry
{
get
{
if (_entriesForCurrentReadStreamAsync is not null)
{
return _entriesForCurrentReadStreamAsync.Current;
}
return _entriesForCurrentReadStream.NotNull().Current;
}
}
#region IDisposable Members
public virtual void Dispose()
{
_entriesForCurrentReadStream?.Dispose();
if (_asyncEntriesForCurrentReadStream is IDisposable disposable)
Volume?.Dispose();
}
public virtual async ValueTask DisposeAsync()
{
if (_entriesForCurrentReadStreamAsync is not null)
{
disposable.Dispose();
await _entriesForCurrentReadStreamAsync.DisposeAsync();
}
Volume?.Dispose();
}
@@ -79,7 +87,7 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
public bool MoveToNextEntry()
{
if (_asyncEntriesForCurrentReadStream is not null)
if (_entriesForCurrentReadStreamAsync is not null)
{
throw new InvalidOperationException(
$"{nameof(MoveToNextEntry)} cannot be used after {nameof(MoveToNextEntryAsync)} has been used."
@@ -120,17 +128,16 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
{
throw new ReaderCancelledException("Reader has been cancelled.");
}
if (_entriesForCurrentReadStream is null && _asyncEntriesForCurrentReadStream is null)
if (_entriesForCurrentReadStreamAsync is null)
{
return await LoadStreamForReadingAsync(RequestInitialStream(), cancellationToken)
.ConfigureAwait(false);
return await LoadStreamForReadingAsync(RequestInitialStream());
}
if (!_wroteCurrentEntry)
{
await SkipEntryAsync(cancellationToken).ConfigureAwait(false);
}
_wroteCurrentEntry = false;
if (await NextEntryForCurrentStreamAsync(cancellationToken).ConfigureAwait(false))
if (await NextEntryForCurrentStreamAsync(cancellationToken))
{
return true;
}
@@ -140,7 +147,7 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
protected bool LoadStreamForReading(Stream stream)
{
if (_asyncEntriesForCurrentReadStream is not null)
if (_entriesForCurrentReadStreamAsync is not null)
{
throw new InvalidOperationException(
$"{nameof(LoadStreamForReading)} cannot be used after {nameof(LoadStreamForReadingAsync)} has been used."
@@ -159,21 +166,12 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
return _entriesForCurrentReadStream.MoveNext();
}
/// <summary>
/// Loads the stream for reading entries asynchronously, using an async entry enumerator when available.
/// </summary>
protected async Task<bool> LoadStreamForReadingAsync(
Stream stream,
CancellationToken cancellationToken = default
)
protected async ValueTask<bool> LoadStreamForReadingAsync(Stream stream)
{
// Always reset the previous async enumerator so that a new stream can be loaded cleanly.
if (_asyncEntriesForCurrentReadStream is IDisposable disposable)
if (_entriesForCurrentReadStreamAsync is not null)
{
disposable.Dispose();
await _entriesForCurrentReadStreamAsync.DisposeAsync();
}
_asyncEntriesForCurrentReadStream = null;
if (stream is null || !stream.CanRead)
{
throw new MultipartStreamRequiredException(
@@ -182,16 +180,8 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
+ "'. A new readable stream is required. Use Cancel if it was intended."
);
}
var entriesAsync = GetEntriesAsync(stream);
if (entriesAsync is null)
{
_entriesForCurrentReadStream = GetEntries(stream).GetEnumerator();
return _entriesForCurrentReadStream.MoveNext();
}
_asyncEntriesForCurrentReadStream = entriesAsync.GetAsyncEnumerator(cancellationToken);
return await _asyncEntriesForCurrentReadStream.MoveNextAsync().ConfigureAwait(false);
_entriesForCurrentReadStreamAsync = GetEntriesAsync(stream).GetAsyncEnumerator();
return await _entriesForCurrentReadStreamAsync.MoveNextAsync();
}
protected virtual Stream RequestInitialStream() =>
@@ -200,16 +190,19 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
internal virtual bool NextEntryForCurrentStream() =>
_entriesForCurrentReadStream.NotNull().MoveNext();
internal virtual ValueTask<bool> NextEntryForCurrentStreamAsync() =>
_entriesForCurrentReadStreamAsync.NotNull().MoveNextAsync();
/// <summary>
/// Moves the current async enumerator to the next entry.
/// </summary>
internal virtual ValueTask<bool> NextEntryForCurrentStreamAsync(
CancellationToken cancellationToken = default
CancellationToken cancellationToken
)
{
if (_asyncEntriesForCurrentReadStream is not null)
if (_entriesForCurrentReadStreamAsync is not null)
{
return _asyncEntriesForCurrentReadStream.MoveNextAsync();
return _entriesForCurrentReadStreamAsync.MoveNextAsync();
}
return new ValueTask<bool>(NextEntryForCurrentStream());
@@ -217,10 +210,14 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
protected abstract IEnumerable<TEntry> GetEntries(Stream stream);
/// <summary>
/// Optionally returns an async entry sequence for formats that support true async header parsing.
/// </summary>
protected virtual IAsyncEnumerable<TEntry>? GetEntriesAsync(Stream stream) => null;
protected virtual async IAsyncEnumerable<TEntry> GetEntriesAsync(Stream stream)
{
await Task.CompletedTask;
foreach (var entry in GetEntries(stream))
{
yield return entry;
}
}
#region Entry Skip/Write
@@ -441,4 +438,5 @@ public abstract class AbstractReader<TEntry, TVolume> : IReader
#endregion
IEntry IReader.Entry => Entry;
IEntry IReaderAsync.Entry => Entry;
}

View File

@@ -18,6 +18,28 @@ public interface IReader : IDisposable
/// <param name="writableStream"></param>
void WriteEntryTo(Stream writableStream);
bool Cancelled { get; }
void Cancel();
/// <summary>
/// Moves to the next entry by reading more data from the underlying stream. This skips if data has not been read.
/// </summary>
/// <returns></returns>
bool MoveToNextEntry();
/// <summary>
/// Opens the current entry as a stream that will decompress as it is read.
/// Read the entire stream or use SkipEntry on EntryStream.
/// </summary>
EntryStream OpenEntryStream();
}
public interface IReaderAsync : IAsyncDisposable
{
ArchiveType ArchiveType { get; }
IEntry Entry { get; }
/// <summary>
/// Decompresses the current entry to the stream asynchronously. This cannot be called twice for the current entry.
/// </summary>
@@ -28,12 +50,6 @@ public interface IReader : IDisposable
bool Cancelled { get; }
void Cancel();
/// <summary>
/// Moves to the next entry by reading more data from the underlying stream. This skips if data has not been read.
/// </summary>
/// <returns></returns>
bool MoveToNextEntry();
/// <summary>
/// Moves to the next entry asynchronously by reading more data from the underlying stream. This skips if data has not been read.
/// </summary>
@@ -41,12 +57,6 @@ public interface IReader : IDisposable
/// <returns></returns>
Task<bool> MoveToNextEntryAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Opens the current entry as a stream that will decompress as it is read.
/// Read the entire stream or use SkipEntry on EntryStream.
/// </summary>
EntryStream OpenEntryStream();
/// <summary>
/// Opens the current entry asynchronously as a stream that will decompress as it is read.
/// Read the entire stream or use SkipEntry on EntryStream.

View File

@@ -0,0 +1,69 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.Readers;
public static class IReaderAsyncExtensions
{
extension(IReaderAsync reader)
{
/// <summary>
/// Extract to specific directory asynchronously, retaining filename
/// </summary>
public async Task WriteEntryToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
await ExtractionMethods
.WriteEntryToDirectoryAsync(
reader.Entry,
destinationDirectory,
options,
reader.WriteEntryToFileAsync,
cancellationToken
)
.ConfigureAwait(false);
/// <summary>
/// Extract to specific file asynchronously
/// </summary>
public async Task WriteEntryToFileAsync(
string destinationFileName,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
await ExtractionMethods
.WriteEntryToFileAsync(
reader.Entry,
destinationFileName,
options,
async (x, fm, ct) =>
{
using var fs = File.Open(destinationFileName, fm);
await reader.WriteEntryToAsync(fs, ct).ConfigureAwait(false);
},
cancellationToken
)
.ConfigureAwait(false);
/// <summary>
/// Extract all remaining unread entries to specific directory asynchronously, retaining filename
/// </summary>
public async Task WriteAllToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
)
{
while (await reader.MoveToNextEntryAsync(cancellationToken))
{
await reader
.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken)
.ConfigureAwait(false);
}
}
}
}

View File

@@ -1,6 +1,4 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.Readers;
@@ -66,62 +64,5 @@ public static class IReaderExtensions
reader.WriteEntryTo(fs);
}
);
/// <summary>
/// Extract to specific directory asynchronously, retaining filename
/// </summary>
public async Task WriteEntryToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
await ExtractionMethods
.WriteEntryToDirectoryAsync(
reader.Entry,
destinationDirectory,
options,
reader.WriteEntryToFileAsync,
cancellationToken
)
.ConfigureAwait(false);
/// <summary>
/// Extract to specific file asynchronously
/// </summary>
public async Task WriteEntryToFileAsync(
string destinationFileName,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
await ExtractionMethods
.WriteEntryToFileAsync(
reader.Entry,
destinationFileName,
options,
async (x, fm, ct) =>
{
using var fs = File.Open(destinationFileName, fm);
await reader.WriteEntryToAsync(fs, ct).ConfigureAwait(false);
},
cancellationToken
)
.ConfigureAwait(false);
/// <summary>
/// Extract all remaining unread entries to specific directory asynchronously, retaining filename
/// </summary>
public async Task WriteAllToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
)
{
while (await reader.MoveToNextEntryAsync(cancellationToken))
{
await reader
.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken)
.ConfigureAwait(false);
}
}
}
}

View File

@@ -13,17 +13,9 @@ public interface IReaderFactory : Factories.IFactory
/// <param name="options"></param>
/// <returns></returns>
IReader OpenReader(Stream stream, ReaderOptions? options);
/// <summary>
/// Opens a Reader asynchronously for Non-seeking usage
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
ValueTask<IReader> OpenReaderAsync(
ValueTask<IReaderAsync> OpenReaderAsync(
Stream stream,
ReaderOptions? options,
CancellationToken cancellationToken = default
CancellationToken cancellationToken
);
}

View File

@@ -24,7 +24,7 @@ public static class ReaderFactory
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static Task<IReader> OpenAsync(
public static ValueTask<IReaderAsync> OpenAsync(
string filePath,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
@@ -47,7 +47,7 @@ public static class ReaderFactory
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static Task<IReader> OpenAsync(
public static ValueTask<IReaderAsync> OpenAsync(
FileInfo fileInfo,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
@@ -110,14 +110,7 @@ public static class ReaderFactory
);
}
/// <summary>
/// Opens a Reader for Non-seeking usage asynchronously
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<IReader> OpenAsync(
public static async ValueTask<IReaderAsync> OpenAsync(
Stream stream,
ReaderOptions? options = null,
CancellationToken cancellationToken = default

View File

@@ -98,7 +98,7 @@ public class ZipReader : AbstractReader<ZipEntry, ZipVolume>
/// <summary>
/// Returns entries asynchronously for streams that only support async reads.
/// </summary>
protected override IAsyncEnumerable<ZipEntry>? GetEntriesAsync(Stream stream) =>
protected override IAsyncEnumerable<ZipEntry> GetEntriesAsync(Stream stream) =>
new ZipEntryAsyncEnumerable(_headerFactory, stream);
/// <summary>

View File

@@ -9,6 +9,7 @@ using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Readers;
using SharpCompress.Test.Mocks;
using SharpCompress.Writers;
using Xunit;
@@ -25,7 +26,7 @@ public class AsyncTests : TestBase
#else
await using var stream = File.OpenRead(testArchive);
#endif
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
await reader.WriteAllToDirectoryAsync(
SCRATCH_FILES_PATH,
@@ -50,9 +51,9 @@ public class AsyncTests : TestBase
#else
await using var stream = File.OpenRead(testArchive);
#endif
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
while (reader.MoveToNextEntry())
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
@@ -118,7 +119,10 @@ public class AsyncTests : TestBase
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz");
using var stream = File.OpenRead(testArchive);
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(stream),
cancellationToken: cts.Token
);
await reader.WriteAllToDirectoryAsync(
SCRATCH_FILES_PATH,

View File

@@ -70,7 +70,7 @@ public class GZipReaderAsyncTests : ReaderTests
bufferSize: options.BufferSize
);
using var testStream = new TestStream(protectedStream);
using (var reader = ReaderFactory.Open(testStream, options))
await using (var reader = await ReaderFactory.OpenAsync(testStream, options, default))
{
await UseReaderAsync(reader, expectedCompression);
protectedStream.ThrowOnDispose = false;
@@ -82,9 +82,9 @@ public class GZipReaderAsyncTests : ReaderTests
Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message);
}
private async Task UseReaderAsync(IReader reader, CompressionType expectedCompression)
private async Task UseReaderAsync(IReaderAsync reader, CompressionType expectedCompression)
{
while (reader.MoveToNextEntry())
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{

View File

@@ -7,7 +7,9 @@ using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Test.Mocks;
using SharpCompress.Writers;
using SharpCompress.Writers.Tar;
using SharpCompress.Writers.Zip;
@@ -538,9 +540,14 @@ public class ProgressReportTests : TestBase
archiveStream.Position = 0;
var readerOptions = new ReaderOptions { Progress = progress };
using (var reader = ReaderFactory.Open(archiveStream, readerOptions))
await using (
var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(archiveStream),
readerOptions
)
)
{
while (reader.MoveToNextEntry())
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{

View File

@@ -647,11 +647,11 @@ public class RarArchiveAsyncTests : ArchiveTests
{
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
using var stream = File.OpenRead(testArchive);
using var archive = ArchiveFactory.Open(stream);
Assert.True(archive.IsSolid);
using (var reader = archive.ExtractAllEntries())
await using var archive = await ArchiveFactory.OpenAsync(stream);
Assert.True(await archive.IsSolidAsync());
await using (var reader = await archive.ExtractAllEntriesAsync())
{
while (reader.MoveToNextEntry())
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
@@ -665,7 +665,7 @@ public class RarArchiveAsyncTests : ArchiveTests
}
VerifyFiles();
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
{
await entry.WriteToDirectoryAsync(
SCRATCH_FILES_PATH,

View File

@@ -7,6 +7,7 @@ using SharpCompress.Archives.Rar;
using SharpCompress.Common;
using SharpCompress.Readers;
using SharpCompress.Readers.Rar;
using SharpCompress.Test.Mocks;
using Xunit;
namespace SharpCompress.Test.Rar;
@@ -204,7 +205,7 @@ public class RarReaderAsyncTests : ReaderTests
private async Task DoRar_Entry_Stream_Async(string filename)
{
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)))
using (var reader = ReaderFactory.Open(stream))
await using (var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)))
{
while (await reader.MoveToNextEntryAsync())
{
@@ -248,9 +249,14 @@ public class RarReaderAsyncTests : ReaderTests
using (
var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar"))
)
using (var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true }))
await using (
var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(stream),
new ReaderOptions { LookForHeader = true }
)
)
{
while (reader.MoveToNextEntry())
while (await reader.MoveToNextEntryAsync())
{
Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType);
await reader.WriteEntryToDirectoryAsync(
@@ -310,8 +316,11 @@ public class RarReaderAsyncTests : ReaderTests
private async Task DoRar_Solid_Skip_Reader_Async(string filename)
{
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
using var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true });
while (reader.MoveToNextEntry())
await using var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(stream),
new ReaderOptions { LookForHeader = true }
);
while (await reader.MoveToNextEntryAsync())
{
if (reader.Entry.Key.NotNull().Contains("jpg"))
{
@@ -333,8 +342,11 @@ public class RarReaderAsyncTests : ReaderTests
private async Task DoRar_Reader_Skip_Async(string filename)
{
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
using var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true });
while (reader.MoveToNextEntry())
await using var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(stream),
new ReaderOptions { LookForHeader = true }
);
while (await reader.MoveToNextEntryAsync())
{
if (reader.Entry.Key.NotNull().Contains("jpg"))
{
@@ -355,7 +367,10 @@ public class RarReaderAsyncTests : ReaderTests
{
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
using Stream stream = File.OpenRead(testArchive);
using var reader = ReaderFactory.Open(stream, readerOptions ?? new ReaderOptions());
await using var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(stream),
readerOptions ?? new ReaderOptions()
);
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)

View File

@@ -145,7 +145,13 @@ public abstract class ReaderTests : TestBase
bufferSize: options.BufferSize
);
using var testStream = new TestStream(protectedStream);
using (var reader = ReaderFactory.Open(testStream, options))
await using (
var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(testStream),
options,
cancellationToken
)
)
{
await UseReaderAsync(reader, expectedCompression, cancellationToken);
protectedStream.ThrowOnDispose = false;
@@ -158,7 +164,7 @@ public abstract class ReaderTests : TestBase
}
public async Task UseReaderAsync(
IReader reader,
IReaderAsync reader,
CompressionType? expectedCompression,
CancellationToken cancellationToken = default
)

View File

@@ -23,9 +23,9 @@ public class TarReaderAsyncTests : ReaderTests
using Stream stream = new ForwardOnlyStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"))
);
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
var x = 0;
while (reader.MoveToNextEntry())
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)
{
@@ -182,14 +182,16 @@ public class TarReaderAsyncTests : ReaderTests
{
var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar");
using Stream stream = File.OpenRead(archiveFullPath);
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
var memoryStream = new MemoryStream();
Assert.True(reader.MoveToNextEntry());
Assert.True(reader.MoveToNextEntry());
Assert.True(await reader.MoveToNextEntryAsync());
Assert.True(await reader.MoveToNextEntryAsync());
await reader.WriteEntryToAsync(memoryStream);
stream.Close();
Assert.Throws<IncompleteArchiveException>(() => reader.MoveToNextEntry());
await Assert.ThrowsAsync<IncompleteArchiveException>(async () =>
await reader.MoveToNextEntryAsync()
);
}
[Fact]
@@ -197,14 +199,16 @@ public class TarReaderAsyncTests : ReaderTests
{
var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "TarCorrupted.tar");
using Stream stream = File.OpenRead(archiveFullPath);
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
var memoryStream = new MemoryStream();
Assert.True(reader.MoveToNextEntry());
Assert.True(reader.MoveToNextEntry());
Assert.True(await reader.MoveToNextEntryAsync());
Assert.True(await reader.MoveToNextEntryAsync());
await reader.WriteEntryToAsync(memoryStream);
stream.Close();
Assert.Throws<IncompleteArchiveException>(() => reader.MoveToNextEntry());
await Assert.ThrowsAsync<IncompleteArchiveException>(async () =>
await reader.MoveToNextEntryAsync()
);
}
#if LINUX

View File

@@ -92,9 +92,10 @@ public class WriterTests : TestBase
readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default;
using var reader = ReaderFactory.Open(
SharpCompressStream.Create(stream, leaveOpen: true),
readerOptions
await using var reader = await ReaderFactory.OpenAsync(
new AsyncOnlyStream(SharpCompressStream.Create(stream, leaveOpen: true)),
readerOptions,
cancellationToken
);
await reader.WriteAllToDirectoryAsync(
SCRATCH_FILES_PATH,

View File

@@ -20,7 +20,7 @@ public class ZipReaderAsyncTests : ReaderTests
{
var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip");
using Stream stream = new ForwardOnlyStream(File.OpenRead(path));
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
var count = 0;
while (await reader.MoveToNextEntryAsync())
{
@@ -65,7 +65,7 @@ public class ZipReaderAsyncTests : ReaderTests
using Stream stream = new ForwardOnlyStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
);
using var reader = ReaderFactory.Open(stream);
await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream));
var x = 0;
while (await reader.MoveToNextEntryAsync())
{
@@ -144,7 +144,7 @@ public class ZipReaderAsyncTests : ReaderTests
using var stream = new TestStream(
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
);
using (var reader = ReaderFactory.Open(stream))
await using (var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)))
{
while (await reader.MoveToNextEntryAsync())
{
@@ -168,7 +168,7 @@ public class ZipReaderAsyncTests : ReaderTests
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip"))
)
);
var reader = await ReaderFactory.OpenAsync(stream);
await using var reader = await ReaderFactory.OpenAsync(stream);
while (await reader.MoveToNextEntryAsync())
{
if (!reader.Entry.IsDirectory)