From d1f6fd9af1ed86cc2fd0b5106591f154f35bfdc3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 22 Jan 2026 14:05:23 +0000 Subject: [PATCH] move more and fmt --- .../Archives/AbstractArchive.Async.cs | 1 - src/SharpCompress/Archives/AbstractArchive.cs | 1 - .../Archives/ArchiveFactory.Async.cs | 157 ++++++++++++++++ src/SharpCompress/Archives/ArchiveFactory.cs | 145 +------------- .../Archives/GZip/GZipArchive.cs | 1 - .../Archives/SevenZip/SevenZipArchive.cs | 1 - .../Archives/Tar/TarArchive.Async.cs | 1 - src/SharpCompress/Archives/Tar/TarArchive.cs | 1 - .../Archives/Zip/ZipArchive.Async.cs | 2 - .../Common/GZip/GZipEntry.Async.cs | 15 ++ src/SharpCompress/Common/GZip/GZipEntry.cs | 10 +- .../Common/Tar/TarEntry.Async.cs | 36 ++++ src/SharpCompress/Common/Tar/TarEntry.cs | 31 +-- .../Common/Tar/TarHeaderFactory.Async.cs | 53 ++++++ .../Common/Tar/TarHeaderFactory.cs | 48 +---- .../Zip/SeekableZipHeaderFactory.Async.cs | 1 + .../Common/Zip/SeekableZipHeaderFactory.cs | 1 - .../Compressors/ADC/ADCBase.Async.cs | 177 ++++++++++++++++++ src/SharpCompress/Compressors/ADC/ADCBase.cs | 169 +---------------- .../Compressors/BZip2/BZip2Stream.Async.cs | 58 ++++++ .../Compressors/BZip2/BZip2Stream.cs | 58 +----- .../Compressors/LZMA/LZipStream.Async.cs | 51 +++++ .../Compressors/LZMA/LZipStream.cs | 51 +---- .../MultiVolumeReadOnlyAsyncStream.Async.cs | 10 + .../Rar/MultiVolumeReadOnlyAsyncStream.cs | 10 +- .../Rar/RarBLAKE2spStream.Async.cs | 12 ++ .../Compressors/Rar/RarBLAKE2spStream.cs | 12 +- .../Compressors/Rar/RarCrcStream.Async.cs | 12 ++ .../Compressors/Rar/RarCrcStream.cs | 12 +- .../Readers/ReaderFactory.Async.cs | 107 +++++++++++ src/SharpCompress/Readers/ReaderFactory.cs | 96 +--------- src/SharpCompress/Utility.Async.cs | 55 ++++++ src/SharpCompress/Utility.cs | 48 +---- 33 files changed, 763 insertions(+), 680 deletions(-) create mode 100644 src/SharpCompress/Archives/ArchiveFactory.Async.cs create mode 100644 src/SharpCompress/Common/GZip/GZipEntry.Async.cs create mode 100644 src/SharpCompress/Common/Tar/TarEntry.Async.cs create mode 100644 src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs create mode 100644 src/SharpCompress/Compressors/ADC/ADCBase.Async.cs create mode 100644 src/SharpCompress/Readers/ReaderFactory.Async.cs create mode 100644 src/SharpCompress/Utility.Async.cs diff --git a/src/SharpCompress/Archives/AbstractArchive.Async.cs b/src/SharpCompress/Archives/AbstractArchive.Async.cs index 07da2652..9ba878d6 100644 --- a/src/SharpCompress/Archives/AbstractArchive.Async.cs +++ b/src/SharpCompress/Archives/AbstractArchive.Async.cs @@ -17,7 +17,6 @@ public abstract partial class AbstractArchive public IAsyncEnumerable VolumesAsync => _lazyVolumesAsync; - protected virtual async IAsyncEnumerable LoadEntriesAsync( IAsyncEnumerable volumes ) diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index cd0d9155..989eca70 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -81,7 +81,6 @@ public abstract partial class AbstractArchive : IArchive, IAsyn protected virtual IAsyncEnumerable LoadVolumesAsync(SourceStream sourceStream) => LoadVolumes(sourceStream).ToAsyncEnumerable(); - IEnumerable IArchive.Entries => Entries.Cast(); IEnumerable IArchive.Volumes => _lazyVolumes.Cast(); diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs new file mode 100644 index 00000000..4050e532 --- /dev/null +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Factories; +using SharpCompress.IO; + +namespace SharpCompress.Archives; + +public static partial class ArchiveFactory +{ + public static async ValueTask OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + readerOptions ??= new ReaderOptions(); + stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); + var factory = await FindFactoryAsync(stream, cancellationToken); + return factory.OpenAsyncArchive(stream, readerOptions); + } + + public static ValueTask OpenAsyncArchive( + string filePath, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken); + } + + public static async ValueTask OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + var factory = await FindFactoryAsync(fileInfo, cancellationToken); + return factory.OpenAsyncArchive(fileInfo, options); + } + + public static async ValueTask OpenAsyncArchive( + IEnumerable fileInfos, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var filesArray = fileInfos.ToArray(); + if (filesArray.Length == 0) + { + throw new InvalidOperationException("No files to open"); + } + + var fileInfo = filesArray[0]; + if (filesArray.Length == 1) + { + return await OpenAsyncArchive(fileInfo, options, cancellationToken); + } + + fileInfo.NotNull(nameof(fileInfo)); + options ??= new ReaderOptions { LeaveStreamOpen = false }; + + var factory = await FindFactoryAsync(fileInfo, cancellationToken); + return factory.OpenAsyncArchive(filesArray, options, cancellationToken); + } + + public static async ValueTask OpenAsyncArchive( + IEnumerable streams, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + streams.NotNull(nameof(streams)); + var streamsArray = streams.ToArray(); + if (streamsArray.Length == 0) + { + throw new InvalidOperationException("No streams"); + } + + var firstStream = streamsArray[0]; + if (streamsArray.Length == 1) + { + return await OpenAsyncArchive(firstStream, options, cancellationToken); + } + + firstStream.NotNull(nameof(firstStream)); + options ??= new ReaderOptions(); + + var factory = await FindFactoryAsync(firstStream, cancellationToken); + return factory.OpenAsyncArchive(streamsArray, options); + } + + public static ValueTask FindFactoryAsync( + string path, + CancellationToken cancellationToken = default + ) + where T : IFactory + { + path.NotNullOrEmpty(nameof(path)); + return FindFactoryAsync(new FileInfo(path), cancellationToken); + } + + private static async ValueTask FindFactoryAsync( + FileInfo finfo, + CancellationToken cancellationToken + ) + where T : IFactory + { + finfo.NotNull(nameof(finfo)); + using Stream stream = finfo.OpenRead(); + return await FindFactoryAsync(stream, cancellationToken); + } + + private static async ValueTask FindFactoryAsync( + Stream stream, + CancellationToken cancellationToken + ) + where T : IFactory + { + stream.NotNull(nameof(stream)); + if (!stream.CanRead || !stream.CanSeek) + { + throw new ArgumentException("Stream should be readable and seekable"); + } + + var factories = Factory.Factories.OfType(); + + var startPosition = stream.Position; + + foreach (var factory in factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + + return factory; + } + } + + var extensions = string.Join(", ", factories.Select(item => item.Name)); + + throw new InvalidOperationException( + $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" + ); + } +} diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 81b5701f..eca2c579 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -11,7 +11,7 @@ using SharpCompress.Readers; namespace SharpCompress.Archives; -public static class ArchiveFactory +public static partial class ArchiveFactory { public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) { @@ -20,18 +20,6 @@ public static class ArchiveFactory return FindFactory(stream).OpenArchive(stream, readerOptions); } - public static async ValueTask OpenAsyncArchive( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - readerOptions ??= new ReaderOptions(); - stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); - var factory = await FindFactoryAsync(stream, cancellationToken); - return factory.OpenAsyncArchive(stream, readerOptions); - } - public static IWritableArchive CreateArchive(ArchiveType type) { var factory = Factory @@ -52,16 +40,6 @@ public static class ArchiveFactory return OpenArchive(new FileInfo(filePath), options); } - public static ValueTask OpenAsyncArchive( - string filePath, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken); - } - public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null) { options ??= new ReaderOptions { LeaveStreamOpen = false }; @@ -69,18 +47,6 @@ public static class ArchiveFactory return FindFactory(fileInfo).OpenArchive(fileInfo, options); } - public static async ValueTask OpenAsyncArchive( - FileInfo fileInfo, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - var factory = await FindFactoryAsync(fileInfo, cancellationToken); - return factory.OpenAsyncArchive(fileInfo, options); - } - public static IArchive OpenArchive( IEnumerable fileInfos, ReaderOptions? options = null @@ -105,32 +71,6 @@ public static class ArchiveFactory return FindFactory(fileInfo).OpenArchive(filesArray, options); } - public static async ValueTask OpenAsyncArchive( - IEnumerable fileInfos, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var filesArray = fileInfos.ToArray(); - if (filesArray.Length == 0) - { - throw new InvalidOperationException("No files to open"); - } - - var fileInfo = filesArray[0]; - if (filesArray.Length == 1) - { - return await OpenAsyncArchive(fileInfo, options, cancellationToken); - } - - fileInfo.NotNull(nameof(fileInfo)); - options ??= new ReaderOptions { LeaveStreamOpen = false }; - - var factory = await FindFactoryAsync(fileInfo, cancellationToken); - return factory.OpenAsyncArchive(filesArray, options, cancellationToken); - } - public static IArchive OpenArchive(IEnumerable streams, ReaderOptions? options = null) { streams.NotNull(nameof(streams)); @@ -152,33 +92,6 @@ public static class ArchiveFactory return FindFactory(firstStream).OpenArchive(streamsArray, options); } - public static async ValueTask OpenAsyncArchive( - IEnumerable streams, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - streams.NotNull(nameof(streams)); - var streamsArray = streams.ToArray(); - if (streamsArray.Length == 0) - { - throw new InvalidOperationException("No streams"); - } - - var firstStream = streamsArray[0]; - if (streamsArray.Length == 1) - { - return await OpenAsyncArchive(firstStream, options, cancellationToken); - } - - firstStream.NotNull(nameof(firstStream)); - options ??= new ReaderOptions(); - - var factory = await FindFactoryAsync(firstStream, cancellationToken); - return factory.OpenAsyncArchive(streamsArray, options); - } - public static void WriteToDirectory( string sourceArchive, string destinationDirectory, @@ -197,16 +110,6 @@ public static class ArchiveFactory return FindFactory(stream); } - public static ValueTask FindFactoryAsync( - string path, - CancellationToken cancellationToken = default - ) - where T : IFactory - { - path.NotNullOrEmpty(nameof(path)); - return FindFactoryAsync(new FileInfo(path), cancellationToken); - } - public static T FindFactory(FileInfo finfo) where T : IFactory { @@ -247,51 +150,7 @@ public static class ArchiveFactory ); } - private static async ValueTask FindFactoryAsync( - FileInfo finfo, - CancellationToken cancellationToken - ) - where T : IFactory - { - finfo.NotNull(nameof(finfo)); - using Stream stream = finfo.OpenRead(); - return await FindFactoryAsync(stream, cancellationToken); - } - - private static async ValueTask FindFactoryAsync( - Stream stream, - CancellationToken cancellationToken - ) - where T : IFactory - { - stream.NotNull(nameof(stream)); - if (!stream.CanRead || !stream.CanSeek) - { - throw new ArgumentException("Stream should be readable and seekable"); - } - - var factories = Factory.Factories.OfType(); - - var startPosition = stream.Position; - - foreach (var factory in factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken)) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - return factory; - } - } - - var extensions = string.Join(", ", factories.Select(item => item.Name)); - - throw new InvalidOperationException( - $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" - ); - } + // Async methods moved to ArchiveFactory.Async.cs public static bool IsArchive( string filePath, diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index dd8816ab..a7dfb427 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -88,7 +88,6 @@ public partial class GZipArchive : AbstractWritableArchive LoadEntriesAsync( IAsyncEnumerable volumes ) diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index a80fa04d..ded45135 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -91,7 +91,6 @@ public partial class TarArchive : AbstractWritableArchive LoadEntriesAsync( IAsyncEnumerable volumes ) @@ -70,7 +69,6 @@ public partial class ZipArchive } } - protected override async ValueTask SaveToAsync( Stream stream, WriterOptions options, diff --git a/src/SharpCompress/Common/GZip/GZipEntry.Async.cs b/src/SharpCompress/Common/GZip/GZipEntry.Async.cs new file mode 100644 index 00000000..6f304f94 --- /dev/null +++ b/src/SharpCompress/Common/GZip/GZipEntry.Async.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.IO; + +namespace SharpCompress.Common.GZip; + +public partial class GZipEntry +{ + internal static async IAsyncEnumerable GetEntriesAsync( + Stream stream, + OptionsBase options + ) + { + yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding)); + } +} diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index a544e9b3..12d0d6e6 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -4,7 +4,7 @@ using System.IO; namespace SharpCompress.Common.GZip; -public class GZipEntry : Entry +public partial class GZipEntry : Entry { private readonly GZipFilePart? _filePart; @@ -43,11 +43,5 @@ public class GZipEntry : Entry yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding)); } - internal static async IAsyncEnumerable GetEntriesAsync( - Stream stream, - OptionsBase options - ) - { - yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding)); - } + // Async methods moved to GZipEntry.Async.cs } diff --git a/src/SharpCompress/Common/Tar/TarEntry.Async.cs b/src/SharpCompress/Common/Tar/TarEntry.Async.cs new file mode 100644 index 00000000..62d1c4d4 --- /dev/null +++ b/src/SharpCompress/Common/Tar/TarEntry.Async.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.IO; + +namespace SharpCompress.Common.Tar; + +public partial class TarEntry +{ + internal static async IAsyncEnumerable GetEntriesAsync( + StreamingMode mode, + Stream stream, + CompressionType compressionType, + IArchiveEncoding archiveEncoding + ) + { + await foreach ( + var header in TarHeaderFactory.ReadHeaderAsync(mode, stream, archiveEncoding) + ) + { + if (header != null) + { + if (mode == StreamingMode.Seekable) + { + yield return new TarEntry(new TarFilePart(header, stream), compressionType); + } + else + { + yield return new TarEntry(new TarFilePart(header, null), compressionType); + } + } + else + { + throw new IncompleteArchiveException("Unexpected EOF reading tar file"); + } + } + } +} diff --git a/src/SharpCompress/Common/Tar/TarEntry.cs b/src/SharpCompress/Common/Tar/TarEntry.cs index 8759663d..fbe93de8 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.cs @@ -6,7 +6,7 @@ using SharpCompress.IO; namespace SharpCompress.Common.Tar; -public class TarEntry : Entry +public partial class TarEntry : Entry { private readonly TarFilePart? _filePart; @@ -77,32 +77,5 @@ public class TarEntry : Entry } } - internal static async IAsyncEnumerable GetEntriesAsync( - StreamingMode mode, - Stream stream, - CompressionType compressionType, - IArchiveEncoding archiveEncoding - ) - { - await foreach ( - var header in TarHeaderFactory.ReadHeaderAsync(mode, stream, archiveEncoding) - ) - { - if (header != null) - { - if (mode == StreamingMode.Seekable) - { - yield return new TarEntry(new TarFilePart(header, stream), compressionType); - } - else - { - yield return new TarEntry(new TarFilePart(header, null), compressionType); - } - } - else - { - throw new IncompleteArchiveException("Unexpected EOF reading tar file"); - } - } - } + // Async methods moved to TarEntry.Async.cs } diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs new file mode 100644 index 00000000..48708796 --- /dev/null +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.IO; + +namespace SharpCompress.Common.Tar; + +internal static partial class TarHeaderFactory +{ + internal static async IAsyncEnumerable ReadHeaderAsync( + StreamingMode mode, + Stream stream, + IArchiveEncoding archiveEncoding + ) + { + while (true) + { + TarHeader? header = null; + try + { + var reader = new AsyncBinaryReader(stream, false); + header = new TarHeader(archiveEncoding); + if (!await header.ReadAsync(reader)) + { + yield break; + } + switch (mode) + { + case StreamingMode.Seekable: + { + header.DataStartPosition = stream.Position; + + //skip to nearest 512 + stream.Position += PadTo512(header.Size); + } + break; + case StreamingMode.Streaming: + { + header.PackedStream = new TarReadOnlySubStream(stream, header.Size); + } + break; + default: + { + throw new InvalidFormatException("Invalid StreamingMode"); + } + } + } + catch + { + header = null; + } + yield return header; + } + } +} diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs index f9c58e2b..c141bc8e 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs @@ -5,7 +5,7 @@ using SharpCompress.IO; namespace SharpCompress.Common.Tar; -internal static class TarHeaderFactory +internal static partial class TarHeaderFactory { internal static IEnumerable ReadHeader( StreamingMode mode, @@ -54,51 +54,7 @@ internal static class TarHeaderFactory } } - internal static async IAsyncEnumerable ReadHeaderAsync( - StreamingMode mode, - Stream stream, - IArchiveEncoding archiveEncoding - ) - { - while (true) - { - TarHeader? header = null; - try - { - var reader = new AsyncBinaryReader(stream, false); - header = new TarHeader(archiveEncoding); - if (!await header.ReadAsync(reader)) - { - yield break; - } - switch (mode) - { - case StreamingMode.Seekable: - { - header.DataStartPosition = stream.Position; - - //skip to nearest 512 - stream.Position += PadTo512(header.Size); - } - break; - case StreamingMode.Streaming: - { - header.PackedStream = new TarReadOnlySubStream(stream, header.Size); - } - break; - default: - { - throw new InvalidFormatException("Invalid StreamingMode"); - } - } - } - catch - { - header = null; - } - yield return header; - } - } + // Async methods moved to TarHeaderFactory.Async.cs private static long PadTo512(long size) { diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs index 6150bd58..4eaf7c1f 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.Async.cs @@ -75,6 +75,7 @@ internal sealed partial class SeekableZipHeaderFactory } } } + private static async ValueTask SeekBackToHeaderAsync(Stream stream, AsyncBinaryReader reader) { // Minimum EOCD length diff --git a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs index 8761d58f..ccbf2082 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipHeaderFactory.cs @@ -22,7 +22,6 @@ internal sealed partial class SeekableZipHeaderFactory : ZipHeaderFactory internal SeekableZipHeaderFactory(string? password, IArchiveEncoding archiveEncoding) : base(StreamingMode.Seekable, password, archiveEncoding) { } - internal IEnumerable ReadSeekableHeader(Stream stream) { var reader = new BinaryReader(stream); diff --git a/src/SharpCompress/Compressors/ADC/ADCBase.Async.cs b/src/SharpCompress/Compressors/ADC/ADCBase.Async.cs new file mode 100644 index 00000000..5ef29c2a --- /dev/null +++ b/src/SharpCompress/Compressors/ADC/ADCBase.Async.cs @@ -0,0 +1,177 @@ +using System; +using System.Buffers; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpCompress.Compressors.ADC; + +public static partial class ADCBase +{ + /// + /// Decompresses a byte buffer asynchronously that's compressed with ADC + /// + /// Compressed buffer + /// Max size for decompressed data + /// Cancellation token + /// Result containing bytes read and decompressed data + public static async ValueTask DecompressAsync( + byte[] input, + int bufferSize = 262144, + CancellationToken cancellationToken = default + ) => await DecompressAsync(new MemoryStream(input), bufferSize, cancellationToken); + + /// + /// Decompresses a stream asynchronously that's compressed with ADC + /// + /// Stream containing compressed data + /// Max size for decompressed data + /// Cancellation token + /// Result containing bytes read and decompressed data + public static async ValueTask DecompressAsync( + Stream input, + int bufferSize = 262144, + CancellationToken cancellationToken = default + ) + { + var result = new AdcDecompressResult(); + + if (input is null || input.Length == 0) + { + result.BytesRead = 0; + result.Output = null; + return result; + } + + var start = (int)input.Position; + var position = (int)input.Position; + int chunkSize; + int offset; + int chunkType; + var buffer = ArrayPool.Shared.Rent(bufferSize); + var outPosition = 0; + var full = false; + byte[] temp = ArrayPool.Shared.Rent(3); + + try + { + while (position < input.Length) + { + cancellationToken.ThrowIfCancellationRequested(); + var readByte = input.ReadByte(); + if (readByte == -1) + { + break; + } + + chunkType = GetChunkType((byte)readByte); + + switch (chunkType) + { + case PLAIN: + chunkSize = GetChunkSize((byte)readByte); + if (outPosition + chunkSize > bufferSize) + { + full = true; + break; + } + + var readCount = await input.ReadAsync( + buffer, + outPosition, + chunkSize, + cancellationToken + ); + outPosition += readCount; + position += readCount + 1; + break; + case TWO_BYTE: + chunkSize = GetChunkSize((byte)readByte); + temp[0] = (byte)readByte; + temp[1] = (byte)input.ReadByte(); + offset = GetOffset(temp.AsSpan(0, 2)); + if (outPosition + chunkSize > bufferSize) + { + full = true; + break; + } + + if (offset == 0) + { + var lastByte = buffer[outPosition - 1]; + for (var i = 0; i < chunkSize; i++) + { + buffer[outPosition] = lastByte; + outPosition++; + } + + position += 2; + } + else + { + for (var i = 0; i < chunkSize; i++) + { + buffer[outPosition] = buffer[outPosition - offset - 1]; + outPosition++; + } + + position += 2; + } + + break; + case THREE_BYTE: + chunkSize = GetChunkSize((byte)readByte); + temp[0] = (byte)readByte; + temp[1] = (byte)input.ReadByte(); + temp[2] = (byte)input.ReadByte(); + offset = GetOffset(temp.AsSpan(0, 3)); + if (outPosition + chunkSize > bufferSize) + { + full = true; + break; + } + + if (offset == 0) + { + var lastByte = buffer[outPosition - 1]; + for (var i = 0; i < chunkSize; i++) + { + buffer[outPosition] = lastByte; + outPosition++; + } + + position += 3; + } + else + { + for (var i = 0; i < chunkSize; i++) + { + buffer[outPosition] = buffer[outPosition - offset - 1]; + outPosition++; + } + + position += 3; + } + + break; + } + + if (full) + { + break; + } + } + + var output = new byte[outPosition]; + Array.Copy(buffer, output, outPosition); + result.BytesRead = position - start; + result.Output = output; + return result; + } + finally + { + ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(temp); + } + } +} diff --git a/src/SharpCompress/Compressors/ADC/ADCBase.cs b/src/SharpCompress/Compressors/ADC/ADCBase.cs index ad521898..6a6f2c2e 100644 --- a/src/SharpCompress/Compressors/ADC/ADCBase.cs +++ b/src/SharpCompress/Compressors/ADC/ADCBase.cs @@ -50,7 +50,7 @@ public class AdcDecompressResult /// /// Provides static methods for decompressing Apple Data Compression data /// -public static class ADCBase +public static partial class ADCBase { private const int PLAIN = 1; private const int TWO_BYTE = 2; @@ -97,172 +97,7 @@ public static class ADCBase public static int Decompress(byte[] input, out byte[]? output, int bufferSize = 262144) => Decompress(new MemoryStream(input), out output, bufferSize); - /// - /// Decompresses a byte buffer asynchronously that's compressed with ADC - /// - /// Compressed buffer - /// Max size for decompressed data - /// Cancellation token - /// Result containing bytes read and decompressed data - public static async ValueTask DecompressAsync( - byte[] input, - int bufferSize = 262144, - CancellationToken cancellationToken = default - ) => await DecompressAsync(new MemoryStream(input), bufferSize, cancellationToken); - - /// - /// Decompresses a stream asynchronously that's compressed with ADC - /// - /// Stream containing compressed data - /// Max size for decompressed data - /// Cancellation token - /// Result containing bytes read and decompressed data - public static async ValueTask DecompressAsync( - Stream input, - int bufferSize = 262144, - CancellationToken cancellationToken = default - ) - { - var result = new AdcDecompressResult(); - - if (input is null || input.Length == 0) - { - result.BytesRead = 0; - result.Output = null; - return result; - } - - var start = (int)input.Position; - var position = (int)input.Position; - int chunkSize; - int offset; - int chunkType; - var buffer = ArrayPool.Shared.Rent(bufferSize); - var outPosition = 0; - var full = false; - byte[] temp = ArrayPool.Shared.Rent(3); - - try - { - while (position < input.Length) - { - cancellationToken.ThrowIfCancellationRequested(); - var readByte = input.ReadByte(); - if (readByte == -1) - { - break; - } - - chunkType = GetChunkType((byte)readByte); - - switch (chunkType) - { - case PLAIN: - chunkSize = GetChunkSize((byte)readByte); - if (outPosition + chunkSize > bufferSize) - { - full = true; - break; - } - - var readCount = await input.ReadAsync( - buffer, - outPosition, - chunkSize, - cancellationToken - ); - outPosition += readCount; - position += readCount + 1; - break; - case TWO_BYTE: - chunkSize = GetChunkSize((byte)readByte); - temp[0] = (byte)readByte; - temp[1] = (byte)input.ReadByte(); - offset = GetOffset(temp.AsSpan(0, 2)); - if (outPosition + chunkSize > bufferSize) - { - full = true; - break; - } - - if (offset == 0) - { - var lastByte = buffer[outPosition - 1]; - for (var i = 0; i < chunkSize; i++) - { - buffer[outPosition] = lastByte; - outPosition++; - } - - position += 2; - } - else - { - for (var i = 0; i < chunkSize; i++) - { - buffer[outPosition] = buffer[outPosition - offset - 1]; - outPosition++; - } - - position += 2; - } - - break; - case THREE_BYTE: - chunkSize = GetChunkSize((byte)readByte); - temp[0] = (byte)readByte; - temp[1] = (byte)input.ReadByte(); - temp[2] = (byte)input.ReadByte(); - offset = GetOffset(temp.AsSpan(0, 3)); - if (outPosition + chunkSize > bufferSize) - { - full = true; - break; - } - - if (offset == 0) - { - var lastByte = buffer[outPosition - 1]; - for (var i = 0; i < chunkSize; i++) - { - buffer[outPosition] = lastByte; - outPosition++; - } - - position += 3; - } - else - { - for (var i = 0; i < chunkSize; i++) - { - buffer[outPosition] = buffer[outPosition - offset - 1]; - outPosition++; - } - - position += 3; - } - - break; - } - - if (full) - { - break; - } - } - - var output = new byte[outPosition]; - Array.Copy(buffer, output, outPosition); - result.BytesRead = position - start; - result.Output = output; - return result; - } - finally - { - ArrayPool.Shared.Return(buffer); - ArrayPool.Shared.Return(temp); - } - } + // Async methods moved to ADCBase.Async.cs /// /// Decompresses a stream that's compressed with ADC diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs index ce125bd5..276a57e6 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs @@ -8,6 +8,64 @@ namespace SharpCompress.Compressors.BZip2; public sealed partial class BZip2Stream { + /// + /// Create a BZip2Stream asynchronously + /// + /// The stream to read from + /// Compression Mode + /// Decompress Concatenated + /// Cancellation Token + public static async ValueTask CreateAsync( + Stream stream, + CompressionMode compressionMode, + bool decompressConcatenated, + bool leaveOpen = false, + CancellationToken cancellationToken = default + ) + { + var bZip2Stream = new BZip2Stream(); + bZip2Stream.leaveOpen = leaveOpen; +#if DEBUG_STREAMS + bZip2Stream.DebugConstruct(typeof(BZip2Stream)); +#endif + bZip2Stream.Mode = compressionMode; + if (bZip2Stream.Mode == CompressionMode.Compress) + { + bZip2Stream.stream = new CBZip2OutputStream(stream); + } + else + { + bZip2Stream.stream = await CBZip2InputStream.CreateAsync( + stream, + decompressConcatenated, + cancellationToken + ); + } + + return bZip2Stream; + } + + /// + /// Asynchronously consumes two bytes to test if there is a BZip2 header + /// + /// + /// + /// + public static async ValueTask IsBZip2Async( + Stream stream, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + var buffer = new byte[2]; + var bytesRead = await stream.ReadAsync(buffer, 0, 2, cancellationToken); + if (bytesRead < 2 || buffer[0] != 'B' || buffer[1] != 'Z') + { + return false; + } + return true; + } + #if !LEGACY_DOTNET public override async ValueTask ReadAsync( Memory buffer, diff --git a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs index 5373f71f..b244fec4 100644 --- a/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs +++ b/src/SharpCompress/Compressors/BZip2/BZip2Stream.cs @@ -69,43 +69,6 @@ public sealed partial class BZip2Stream : Stream, IStreamStack return bZip2Stream; } - /// - /// Create a BZip2Stream asynchronously - /// - /// The stream to read from - /// Compression Mode - /// Decompress Concatenated - /// Cancellation Token - public static async ValueTask CreateAsync( - Stream stream, - CompressionMode compressionMode, - bool decompressConcatenated, - bool leaveOpen = false, - CancellationToken cancellationToken = default - ) - { - var bZip2Stream = new BZip2Stream(); - bZip2Stream.leaveOpen = leaveOpen; -#if DEBUG_STREAMS - bZip2Stream.DebugConstruct(typeof(BZip2Stream)); -#endif - bZip2Stream.Mode = compressionMode; - if (bZip2Stream.Mode == CompressionMode.Compress) - { - bZip2Stream.stream = new CBZip2OutputStream(stream); - } - else - { - bZip2Stream.stream = await CBZip2InputStream.CreateAsync( - stream, - decompressConcatenated, - cancellationToken - ); - } - - return bZip2Stream; - } - public void Finish() => (stream as CBZip2OutputStream)?.Finish(); protected override void Dispose(bool disposing) @@ -178,24 +141,5 @@ public sealed partial class BZip2Stream : Stream, IStreamStack return true; } - /// - /// Asynchronously consumes two bytes to test if there is a BZip2 header - /// - /// - /// - /// - public static async ValueTask IsBZip2Async( - Stream stream, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - var buffer = new byte[2]; - var bytesRead = await stream.ReadAsync(buffer, 0, 2, cancellationToken); - if (bytesRead < 2 || buffer[0] != 'B' || buffer[1] != 'Z') - { - return false; - } - return true; - } + // Async methods moved to BZip2Stream.Async.cs } diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.Async.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.Async.cs index 3e674239..f83cbff9 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.Async.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.Async.cs @@ -9,6 +9,57 @@ namespace SharpCompress.Compressors.LZMA; public sealed partial class LZipStream { + /// + /// Asynchronously determines if the given stream is positioned at the start of a v1 LZip + /// file, as indicated by the ASCII characters "LZIP" and a version byte + /// of 1, followed by at least one byte. + /// + /// The stream to read from. Must not be null. + /// Cancellation token. + /// true if the given stream is an LZip file, false otherwise. + public static async ValueTask IsLZipFileAsync( + Stream stream, + CancellationToken cancellationToken = default + ) => await ValidateAndReadSizeAsync(stream, cancellationToken) != 0; + + /// + /// Asynchronously reads the 6-byte header of the stream, and returns 0 if either the header + /// couldn't be read or it isn't a validate LZIP header, or the dictionary + /// size if it *is* a valid LZIP file. + /// + public static async ValueTask ValidateAndReadSizeAsync( + Stream stream, + CancellationToken cancellationToken + ) + { + // Read the header + byte[] header = new byte[6]; + var n = await stream + .ReadAsync(header, 0, header.Length, cancellationToken) + .ConfigureAwait(false); + + // TODO: Handle reading only part of the header? + + if (n != 6) + { + return 0; + } + + if ( + header[0] != 'L' + || header[1] != 'Z' + || header[2] != 'I' + || header[3] != 'P' + || header[4] != 1 /* version 1 */ + ) + { + return 0; + } + var basePower = header[5] & 0x1F; + var subtractionNumerator = (header[5] & 0xE0) >> 5; + return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4))); + } + #if !LEGACY_DOTNET /// /// Asynchronously reads bytes from the current stream into a buffer. diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.cs index 7ff97a78..1533d652 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -202,19 +202,6 @@ public sealed partial class LZipStream : Stream, IStreamStack /// true if the given stream is an LZip file, false otherwise. public static bool IsLZipFile(Stream stream) => ValidateAndReadSize(stream) != 0; - /// - /// Asynchronously determines if the given stream is positioned at the start of a v1 LZip - /// file, as indicated by the ASCII characters "LZIP" and a version byte - /// of 1, followed by at least one byte. - /// - /// The stream to read from. Must not be null. - /// Cancellation token. - /// true if the given stream is an LZip file, false otherwise. - public static async ValueTask IsLZipFileAsync( - Stream stream, - CancellationToken cancellationToken = default - ) => await ValidateAndReadSizeAsync(stream, cancellationToken) != 0; - /// /// Reads the 6-byte header of the stream, and returns 0 if either the header /// couldn't be read or it isn't a validate LZIP header, or the dictionary @@ -248,43 +235,7 @@ public sealed partial class LZipStream : Stream, IStreamStack return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4))); } - /// - /// Asynchronously reads the 6-byte header of the stream, and returns 0 if either the header - /// couldn't be read or it isn't a validate LZIP header, or the dictionary - /// size if it *is* a valid LZIP file. - /// - public static async ValueTask ValidateAndReadSizeAsync( - Stream stream, - CancellationToken cancellationToken - ) - { - // Read the header - byte[] header = new byte[6]; - var n = await stream - .ReadAsync(header, 0, header.Length, cancellationToken) - .ConfigureAwait(false); - - // TODO: Handle reading only part of the header? - - if (n != 6) - { - return 0; - } - - if ( - header[0] != 'L' - || header[1] != 'Z' - || header[2] != 'I' - || header[3] != 'P' - || header[4] != 1 /* version 1 */ - ) - { - return 0; - } - var basePower = header[5] & 0x1F; - var subtractionNumerator = (header[5] & 0xE0) >> 5; - return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4))); - } + // Async methods moved to LZipStream.Async.cs private static readonly byte[] headerBytes = [ diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.Async.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.Async.cs index ed005372..5804d669 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.Async.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.Async.cs @@ -13,6 +13,16 @@ internal sealed partial class MultiVolumeReadOnlyAsyncStream : MultiVolumeReadOnlyStreamBase, IStreamStack { + internal static async ValueTask Create( + IAsyncEnumerable parts + ) + { + var stream = new MultiVolumeReadOnlyAsyncStream(parts); + await stream.filePartEnumerator.MoveNextAsync(); + stream.InitializeNextFilePart(); + return stream; + } + #if NET8_0_OR_GREATER public override async ValueTask DisposeAsync() { diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.cs index 467c8dd3..565ae220 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyAsyncStream.cs @@ -44,15 +44,7 @@ internal sealed partial class MultiVolumeReadOnlyAsyncStream filePartEnumerator = parts.GetAsyncEnumerator(); } - internal static async ValueTask Create( - IAsyncEnumerable parts - ) - { - var stream = new MultiVolumeReadOnlyAsyncStream(parts); - await stream.filePartEnumerator.MoveNextAsync(); - stream.InitializeNextFilePart(); - return stream; - } + // Async methods moved to MultiVolumeReadOnlyAsyncStream.Async.cs private void InitializeNextFilePart() { diff --git a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.Async.cs b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.Async.cs index 2a38239d..5c657009 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.Async.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.Async.cs @@ -10,6 +10,18 @@ namespace SharpCompress.Compressors.Rar; internal partial class RarBLAKE2spStream : RarStream, IStreamStack { + public static async ValueTask CreateAsync( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyAsyncStream readStream, + CancellationToken cancellationToken = default + ) + { + var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream); + await stream.InitializeAsync(cancellationToken); + return stream; + } + public override async Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs index fb7fc98d..2fa0aa9a 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs @@ -134,17 +134,7 @@ internal partial class RarBLAKE2spStream : RarStream, IStreamStack return stream; } - public static async ValueTask CreateAsync( - IRarUnpack unpack, - FileHeader fileHeader, - MultiVolumeReadOnlyAsyncStream readStream, - CancellationToken cancellationToken = default - ) - { - var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream); - await stream.InitializeAsync(cancellationToken); - return stream; - } + // Async methods moved to RarBLAKE2spStream.Async.cs protected override void Dispose(bool disposing) { diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.Async.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.Async.cs index d7e672c8..040e01bc 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.Async.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.Async.cs @@ -10,6 +10,18 @@ namespace SharpCompress.Compressors.Rar; internal partial class RarCrcStream : RarStream, IStreamStack { + public static async ValueTask CreateAsync( + IRarUnpack unpack, + FileHeader fileHeader, + MultiVolumeReadOnlyStreamBase readStream, + CancellationToken cancellationToken = default + ) + { + var stream = new RarCrcStream(unpack, fileHeader, readStream); + await stream.InitializeAsync(cancellationToken); + return stream; + } + public override async Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 960783a8..2e4030d4 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -59,17 +59,7 @@ internal partial class RarCrcStream : RarStream, IStreamStack return stream; } - public static async ValueTask CreateAsync( - IRarUnpack unpack, - FileHeader fileHeader, - MultiVolumeReadOnlyStreamBase readStream, - CancellationToken cancellationToken = default - ) - { - var stream = new RarCrcStream(unpack, fileHeader, readStream); - await stream.InitializeAsync(cancellationToken); - return stream; - } + // Async methods moved to RarCrcStream.Async.cs protected override void Dispose(bool disposing) { diff --git a/src/SharpCompress/Readers/ReaderFactory.Async.cs b/src/SharpCompress/Readers/ReaderFactory.Async.cs new file mode 100644 index 00000000..b879b2ba --- /dev/null +++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs @@ -0,0 +1,107 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Factories; +using SharpCompress.IO; + +namespace SharpCompress.Readers; + +public static partial class ReaderFactory +{ + /// + /// Opens a Reader from a filepath asynchronously + /// + /// + /// + /// + /// + public static ValueTask OpenAsyncReader( + string filePath, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken); + } + + /// + /// Opens a Reader from a FileInfo asynchronously + /// + /// + /// + /// + /// + public static ValueTask OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + options ??= new ReaderOptions { LeaveStreamOpen = false }; + return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken); + } + + public static async ValueTask OpenAsyncReader( + Stream stream, + ReaderOptions? options = null, + CancellationToken cancellationToken = default + ) + { + stream.NotNull(nameof(stream)); + options ??= new ReaderOptions() { LeaveStreamOpen = false }; + + var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize); + long pos = bStream.GetPosition(); + + var factories = Factory.Factories.OfType(); + + Factory? testedFactory = null; + if (!string.IsNullOrWhiteSpace(options.ExtensionHint)) + { + testedFactory = factories.FirstOrDefault(a => + a.GetSupportedExtensions() + .Contains(options.ExtensionHint, StringComparer.CurrentCultureIgnoreCase) + ); + if (testedFactory is IReaderFactory readerFactory) + { + bStream.StackSeek(pos); + if ( + await testedFactory.IsArchiveAsync( + bStream, + cancellationToken: cancellationToken + ) + ) + { + bStream.StackSeek(pos); + return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken); + } + } + bStream.StackSeek(pos); + } + + foreach (var factory in factories) + { + if (testedFactory == factory) + { + continue; // Already tested above + } + bStream.StackSeek(pos); + if ( + factory is IReaderFactory readerFactory + && await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken) + ) + { + bStream.StackSeek(pos); + return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken); + } + } + + throw new InvalidFormatException( + "Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard" + ); + } +} diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 809714c7..b5c9b294 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -9,7 +9,7 @@ using SharpCompress.IO; namespace SharpCompress.Readers; -public static class ReaderFactory +public static partial class ReaderFactory { public static IReader OpenReader(string filePath, ReaderOptions? options = null) { @@ -17,46 +17,12 @@ public static class ReaderFactory return OpenReader(new FileInfo(filePath), options); } - /// - /// Opens a Reader from a filepath asynchronously - /// - /// - /// - /// - /// - public static ValueTask OpenAsyncReader( - string filePath, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken); - } - public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null) { options ??= new ReaderOptions { LeaveStreamOpen = false }; return OpenReader(fileInfo.OpenRead(), options); } - /// - /// Opens a Reader from a FileInfo asynchronously - /// - /// - /// - /// - /// - public static ValueTask OpenAsyncReader( - FileInfo fileInfo, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - options ??= new ReaderOptions { LeaveStreamOpen = false }; - return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken); - } - /// /// Opens a Reader for Non-seeking usage /// @@ -110,63 +76,5 @@ public static class ReaderFactory ); } - public static async ValueTask OpenAsyncReader( - Stream stream, - ReaderOptions? options = null, - CancellationToken cancellationToken = default - ) - { - stream.NotNull(nameof(stream)); - options ??= new ReaderOptions() { LeaveStreamOpen = false }; - - var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize); - long pos = bStream.GetPosition(); - - var factories = Factory.Factories.OfType(); - - Factory? testedFactory = null; - if (!string.IsNullOrWhiteSpace(options.ExtensionHint)) - { - testedFactory = factories.FirstOrDefault(a => - a.GetSupportedExtensions() - .Contains(options.ExtensionHint, StringComparer.CurrentCultureIgnoreCase) - ); - if (testedFactory is IReaderFactory readerFactory) - { - bStream.StackSeek(pos); - if ( - await testedFactory.IsArchiveAsync( - bStream, - cancellationToken: cancellationToken - ) - ) - { - bStream.StackSeek(pos); - return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken); - } - } - bStream.StackSeek(pos); - } - - foreach (var factory in factories) - { - if (testedFactory == factory) - { - continue; // Already tested above - } - bStream.StackSeek(pos); - if ( - factory is IReaderFactory readerFactory - && await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken) - ) - { - bStream.StackSeek(pos); - return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken); - } - } - - throw new InvalidFormatException( - "Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard" - ); - } + // Async methods moved to ReaderFactory.Async.cs } diff --git a/src/SharpCompress/Utility.Async.cs b/src/SharpCompress/Utility.Async.cs new file mode 100644 index 00000000..be417fd6 --- /dev/null +++ b/src/SharpCompress/Utility.Async.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpCompress; + +internal static partial class Utility +{ + /// + /// Read exactly the requested number of bytes from a stream asynchronously. Throws EndOfStreamException if not enough data is available. + /// + public static async ValueTask ReadExactAsync( + this Stream stream, + byte[] buffer, + int offset, + int length, + CancellationToken cancellationToken = default + ) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0 || offset > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (length < 0 || length > buffer.Length - offset) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + while (length > 0) + { + var fetched = await stream + .ReadAsync(buffer, offset, length, cancellationToken) + .ConfigureAwait(false); + if (fetched <= 0) + { + throw new EndOfStreamException(); + } + + offset += fetched; + length -= fetched; + } + } +} diff --git a/src/SharpCompress/Utility.cs b/src/SharpCompress/Utility.cs index 5b3ed2dd..649e9280 100644 --- a/src/SharpCompress/Utility.cs +++ b/src/SharpCompress/Utility.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace SharpCompress; -internal static class Utility +internal static partial class Utility { //80kb is a good industry standard temporary buffer size internal const int TEMP_BUFFER_SIZE = 81920; @@ -336,51 +336,7 @@ internal static class Utility } } - /// - /// Read exactly the requested number of bytes from a stream asynchronously. Throws EndOfStreamException if not enough data is available. - /// - public static async ValueTask ReadExactAsync( - this Stream stream, - byte[] buffer, - int offset, - int length, - CancellationToken cancellationToken = default - ) - { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - if (buffer is null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (length < 0 || length > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - while (length > 0) - { - var fetched = await stream - .ReadAsync(buffer, offset, length, cancellationToken) - .ConfigureAwait(false); - if (fetched <= 0) - { - throw new EndOfStreamException(); - } - - offset += fetched; - length -= fetched; - } - } + // Async methods moved to Utility.Async.cs public static string TrimNulls(this string source) => source.Replace('\0', ' ').Trim();