diff --git a/src/SharpCompress/Archives/ArchiveFactory.Detection.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Detection.Async.cs new file mode 100644 index 00000000..04cc7886 --- /dev/null +++ b/src/SharpCompress/Archives/ArchiveFactory.Detection.Async.cs @@ -0,0 +1,369 @@ +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Archives.Tar; +using SharpCompress.Common; +using SharpCompress.Detection; +using SharpCompress.Factories; +using SharpCompress.IO; +using SharpCompress.Providers; +using SharpCompress.Readers; + +namespace SharpCompress.Archives; + +public static partial class ArchiveFactory +{ + /// + /// Identifies the archive at the given file path without enumerating its entries. + /// + /// Path to the archive file. + /// Cancellation token. + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public static async ValueTask DetectArchiveAsync( + string filePath, + CancellationToken cancellationToken = default + ) => + await DetectArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken) + .ConfigureAwait(false); + + /// + /// Identifies the archive at the given file path without enumerating its entries. + /// + /// Path to the archive file. + /// Options controlling archive detection. + /// Cancellation token. + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public static async ValueTask DetectArchiveAsync( + string filePath, + ReaderOptions? readerOptions, + CancellationToken cancellationToken = default + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + using Stream stream = File.OpenRead(filePath); + return await DetectArchiveAsync( + stream, + readerOptions ?? ReaderOptions.ForFilePath, + cancellationToken + ) + .ConfigureAwait(false); + } + + /// + /// Identifies the archive in the given stream without enumerating its entries. + /// + /// A readable and seekable stream positioned at the start of the archive. + /// Cancellation token. + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public static async ValueTask DetectArchiveAsync( + Stream stream, + CancellationToken cancellationToken = default + ) => + await DetectArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken) + .ConfigureAwait(false); + + /// + /// Identifies the archive in the given stream without enumerating its entries. + /// + /// A readable and seekable stream positioned at the start of the archive. + /// Options controlling archive detection. + /// Cancellation token. + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public static async ValueTask DetectArchiveAsync( + Stream stream, + ReaderOptions? readerOptions, + CancellationToken cancellationToken = default + ) + { + stream.RequireReadable(); + stream.RequireSeekable(); + + return await TryDetectArchiveAsync( + stream, + readerOptions ?? ReaderOptions.ForExternalStream, + cancellationToken + ) + .ConfigureAwait(false); + } + + internal static ValueTask FindFactoryAsync( + string filePath, + CancellationToken cancellationToken = default + ) + where T : IFactory + { + filePath.NotNullOrEmpty(nameof(filePath)); + return FindFactoryAsync(new FileInfo(filePath), cancellationToken); + } + + internal static async ValueTask FindFactoryAsync( + FileInfo fileInfo, + CancellationToken cancellationToken = default + ) + where T : IFactory + { + fileInfo.NotNull(nameof(fileInfo)); + using Stream stream = fileInfo.OpenRead(); + return await FindFactoryAsync(stream, cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask FindFactoryAsync( + FileInfo fileInfo, + ReaderOptions readerOptions, + CancellationToken cancellationToken + ) + where T : IFactory + { + fileInfo.NotNull(nameof(fileInfo)); + using Stream stream = fileInfo.OpenRead(); + return await FindFactoryAsync(stream, readerOptions, cancellationToken) + .ConfigureAwait(false); + } + + internal static async ValueTask FindFactoryAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + where T : IFactory + { + stream.RequireReadable(); + stream.RequireSeekable(); + + // Use the shared async detection loop over all factories. If the matched factory + // implements T we return it; otherwise (or if nothing matched) we fall through + // to the same "unsupported format" exception that the original code produced, + // listing the T-typed factories as the hint for the caller. + return await FindFactoryAsync(stream, ReaderOptions.ForExternalStream, cancellationToken) + .ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask FindFactoryAsync( + Stream stream, + ReaderOptions readerOptions, + CancellationToken cancellationToken + ) + where T : IFactory + { + stream.RequireReadable(); + stream.RequireSeekable(); + + var factory = await TryFindFactoryAsync(stream, readerOptions, cancellationToken) + .ConfigureAwait(false); + if (factory is T typedFactory) + { + return typedFactory; + } + + var extensions = string.Join(", ", Factory.Factories.OfType().Select(item => item.Name)); + + throw new ArchiveOperationException( + $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" + ); + } + + /// + /// Iterates all registered factories and returns the first one whose + /// recognises the stream, or . + /// Stream position is restored to its value at entry on both success and failure. + /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask TryFindFactoryAsync( + Stream stream, + ReaderOptions readerOptions, + CancellationToken cancellationToken + ) + { + var startPosition = stream.Position; + + try + { + foreach (var factory in Factory.Factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + var isArchive = await factory + .IsArchiveAsync(stream, readerOptions, cancellationToken) + .ConfigureAwait(false); + + if (isArchive) + { + stream.Seek(startPosition, SeekOrigin.Begin); + if ( + await IsCompressedTarAsync( + stream, + factory, + readerOptions, + cancellationToken + ) + .ConfigureAwait(false) + ) + { + continue; + } + + return factory; + } + } + + return null; + } + finally + { + stream.Seek(startPosition, SeekOrigin.Begin); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask IsCompressedTarAsync( + Stream stream, + IFactory factory, + ReaderOptions readerOptions, + CancellationToken cancellationToken + ) => + GetCompressedTarType(factory) is { } compressionType + && await IsCompressedTarAsync(stream, readerOptions, compressionType, cancellationToken) + .ConfigureAwait(false); + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask IsCompressedTarAsync( + Stream stream, + ReaderOptions readerOptions, + CompressionType compressionType, + CancellationToken cancellationToken + ) + { + using var nonDisposingStream = SharpCompressStream.CreateNonDisposing(stream); + var testStream = + compressionType == CompressionType.GZip + ? await readerOptions + .Providers.CreateDecompressStreamAsync( + compressionType, + nonDisposingStream, + CompressionContext + .FromStream(nonDisposingStream) + .WithReaderOptions(readerOptions), + cancellationToken + ) + .ConfigureAwait(false) + : await readerOptions + .Providers.CreateDecompressStreamAsync( + compressionType, + nonDisposingStream, + cancellationToken + ) + .ConfigureAwait(false); + + try + { + return await TarArchive + .IsTarFileAsync(testStream, cancellationToken) + .ConfigureAwait(false); + } + finally + { + DisposeProbeStream(testStream); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask TryDetectArchiveAsync( + Stream stream, + ReaderOptions readerOptions, + CancellationToken cancellationToken + ) + { + var startPosition = stream.Position; + + try + { + foreach (var factory in Factory.Factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + if ( + !await factory + .IsArchiveAsync(stream, readerOptions, cancellationToken) + .ConfigureAwait(false) + ) + { + continue; + } + + if (GetCompressedTarType(factory) is { } compressionType) + { + stream.Seek(startPosition, SeekOrigin.Begin); + if ( + await IsCompressedTarAsync( + stream, + readerOptions, + compressionType, + cancellationToken + ) + .ConfigureAwait(false) + ) + { + return CreateCompressedTarDetection(compressionType); + } + } + + return CreateDetection(factory); + } + + var compressedTarType = await TryDetectCompressedTarAsync( + stream, + readerOptions, + startPosition, + cancellationToken + ) + .ConfigureAwait(false); + return compressedTarType is { } value ? CreateCompressedTarDetection(value) : null; + } + finally + { + stream.Seek(startPosition, SeekOrigin.Begin); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async ValueTask TryDetectCompressedTarAsync( + Stream stream, + ReaderOptions readerOptions, + long startPosition, + CancellationToken cancellationToken + ) + { + foreach (var wrapper in TarWrapper.Wrappers) + { +#if !SYNC_ONLY + cancellationToken.ThrowIfCancellationRequested(); +#endif + if (wrapper.CompressionType == CompressionType.None) + { + continue; + } + + stream.Seek(startPosition, SeekOrigin.Begin); + if (!await wrapper.IsMatchAsync(stream, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + stream.Seek(startPosition, SeekOrigin.Begin); + if ( + await IsCompressedTarAsync( + stream, + readerOptions, + wrapper.CompressionType, + cancellationToken + ) + .ConfigureAwait(false) + ) + { + return wrapper.CompressionType; + } + } + + return null; + } +} diff --git a/src/SharpCompress/Archives/ArchiveFactory.Detection.cs b/src/SharpCompress/Archives/ArchiveFactory.Detection.cs index 88168d92..85721dbf 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Detection.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Detection.cs @@ -1,391 +1,13 @@ using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using SharpCompress.Archives.Tar; using SharpCompress.Common; using SharpCompress.Detection; using SharpCompress.Factories; -using SharpCompress.IO; -using SharpCompress.Providers; using SharpCompress.Readers; namespace SharpCompress.Archives; public static partial class ArchiveFactory { - /// - /// Identifies the archive at the given file path without enumerating its entries. - /// - /// Path to the archive file. - /// Cancellation token. - public static async ValueTask DetectArchiveAsync( - string filePath, - CancellationToken cancellationToken = default - ) => - await DetectArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken) - .ConfigureAwait(false); - - /// - /// Identifies the archive at the given file path without enumerating its entries. - /// - /// Path to the archive file. - /// Options controlling archive detection. - /// Cancellation token. - public static async ValueTask DetectArchiveAsync( - string filePath, - ReaderOptions? readerOptions, - CancellationToken cancellationToken = default - ) - { - filePath.NotNullOrEmpty(nameof(filePath)); - using Stream stream = File.OpenRead(filePath); - return await DetectArchiveAsync( - stream, - readerOptions ?? ReaderOptions.ForFilePath, - cancellationToken - ) - .ConfigureAwait(false); - } - - /// - /// Identifies the archive in the given stream without enumerating its entries. - /// - /// A readable and seekable stream positioned at the start of the archive. - /// Cancellation token. - public static async ValueTask DetectArchiveAsync( - Stream stream, - CancellationToken cancellationToken = default - ) => - await DetectArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken) - .ConfigureAwait(false); - - /// - /// Identifies the archive in the given stream without enumerating its entries. - /// - /// A readable and seekable stream positioned at the start of the archive. - /// Options controlling archive detection. - /// Cancellation token. - public static async ValueTask DetectArchiveAsync( - Stream stream, - ReaderOptions? readerOptions, - CancellationToken cancellationToken = default - ) - { - stream.RequireReadable(); - stream.RequireSeekable(); - - return await TryDetectArchiveAsync( - stream, - readerOptions ?? ReaderOptions.ForExternalStream, - cancellationToken - ) - .ConfigureAwait(false); - } - - internal static ValueTask FindFactoryAsync( - string filePath, - CancellationToken cancellationToken = default - ) - where T : IFactory - { - filePath.NotNullOrEmpty(nameof(filePath)); - return FindFactoryAsync(new FileInfo(filePath), cancellationToken); - } - - internal static async ValueTask FindFactoryAsync( - FileInfo fileInfo, - CancellationToken cancellationToken = default - ) - where T : IFactory - { - fileInfo.NotNull(nameof(fileInfo)); - using Stream stream = fileInfo.OpenRead(); - return await FindFactoryAsync(stream, cancellationToken).ConfigureAwait(false); - } - - private static async ValueTask FindFactoryAsync( - FileInfo fileInfo, - ReaderOptions readerOptions, - CancellationToken cancellationToken - ) - where T : IFactory - { - fileInfo.NotNull(nameof(fileInfo)); - using Stream stream = fileInfo.OpenRead(); - return await FindFactoryAsync(stream, readerOptions, cancellationToken) - .ConfigureAwait(false); - } - - internal static async ValueTask FindFactoryAsync( - Stream stream, - CancellationToken cancellationToken = default - ) - where T : IFactory - { - stream.RequireReadable(); - stream.RequireSeekable(); - - // Use the shared async detection loop over all factories. If the matched factory - // implements T we return it; otherwise (or if nothing matched) we fall through - // to the same "unsupported format" exception that the original code produced, - // listing the T-typed factories as the hint for the caller. - return await FindFactoryAsync(stream, ReaderOptions.ForExternalStream, cancellationToken) - .ConfigureAwait(false); - } - - private static async ValueTask FindFactoryAsync( - Stream stream, - ReaderOptions readerOptions, - CancellationToken cancellationToken - ) - where T : IFactory - { - stream.RequireReadable(); - stream.RequireSeekable(); - - var factory = await TryFindFactoryAsync(stream, readerOptions, cancellationToken) - .ConfigureAwait(false); - if (factory is T typedFactory) - { - return typedFactory; - } - - var extensions = string.Join(", ", Factory.Factories.OfType().Select(item => item.Name)); - - throw new ArchiveOperationException( - $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" - ); - } - - /// - /// Async counterpart of the synchronous factory detection path. - /// Iterates all registered factories and returns the first one whose - /// recognises the stream, or . - /// Stream position is restored to its value at entry on both success and failure. - /// - private static async ValueTask TryFindFactoryAsync( - Stream stream, - ReaderOptions readerOptions, - CancellationToken cancellationToken - ) - { - var startPosition = stream.Position; - - try - { - foreach (var factory in Factory.Factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - var isArchive = await factory - .IsArchiveAsync(stream, readerOptions, cancellationToken) - .ConfigureAwait(false); - - if (isArchive) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if ( - await IsCompressedTarAsync( - stream, - factory, - readerOptions, - cancellationToken - ) - .ConfigureAwait(false) - ) - { - continue; - } - - return factory; - } - } - - return null; - } - finally - { - stream.Seek(startPosition, SeekOrigin.Begin); - } - } - - /// - /// Identifies the archive at the given file path without enumerating its entries. - /// - /// Path to the archive file. - public static ArchiveDetection? DetectArchive(string filePath) => - DetectArchive(filePath, ReaderOptions.ForFilePath); - - /// - /// Identifies the archive at the given file path without enumerating its entries. - /// - /// Path to the archive file. - /// Options controlling archive detection. - public static ArchiveDetection? DetectArchive(string filePath, ReaderOptions? readerOptions) - { - filePath.NotNullOrEmpty(nameof(filePath)); - using Stream stream = File.OpenRead(filePath); - return DetectArchive(stream, readerOptions ?? ReaderOptions.ForFilePath); - } - - /// - /// Identifies the archive in the given stream without enumerating its entries. - /// - /// A readable and seekable stream positioned at the start of the archive. - public static ArchiveDetection? DetectArchive(Stream stream) => - DetectArchive(stream, ReaderOptions.ForExternalStream); - - /// - /// Identifies the archive in the given stream without enumerating its entries. - /// - /// A readable and seekable stream positioned at the start of the archive. - /// Options controlling archive detection. - public static ArchiveDetection? DetectArchive(Stream stream, ReaderOptions? readerOptions) - { - stream.RequireReadable(); - stream.RequireSeekable(); - - return TryDetectArchive(stream, readerOptions ?? ReaderOptions.ForExternalStream); - } - - /// - /// Iterates all registered factories and returns the first one whose - /// recognises the stream, or . - /// Stream position is restored to its value at entry on both success and failure. - /// - /// - /// This is the shared, seekable-stream detection core used by - /// , , - /// and . - /// - /// uses a separate code path - /// based on rewindable buffering, which supports - /// non-seekable streams and is therefore not unified with this helper. - /// - /// - private static IFactory? TryFindFactory(Stream stream) => - TryFindFactory(stream, ReaderOptions.ForExternalStream); - - private static IFactory? TryFindFactory(Stream stream, ReaderOptions readerOptions) - { - var startPosition = stream.Position; - - try - { - foreach (var factory in Factory.Factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - var isArchive = factory.IsArchive(stream, readerOptions); - - if (isArchive) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if (IsCompressedTar(stream, factory, readerOptions)) - { - continue; - } - - return factory; - } - } - - return null; - } - finally - { - stream.Seek(startPosition, SeekOrigin.Begin); - } - } - - private static bool IsCompressedTar( - Stream stream, - IFactory factory, - ReaderOptions readerOptions - ) => - GetCompressedTarType(factory) is { } compressionType - && IsCompressedTar(stream, readerOptions, compressionType); - - private static bool IsCompressedTar( - Stream stream, - ReaderOptions readerOptions, - CompressionType compressionType - ) - { - using var nonDisposingStream = SharpCompressStream.CreateNonDisposing(stream); - var testStream = - compressionType == CompressionType.GZip - ? readerOptions.Providers.CreateDecompressStream( - compressionType, - nonDisposingStream, - CompressionContext - .FromStream(nonDisposingStream) - .WithReaderOptions(readerOptions) - ) - : readerOptions.Providers.CreateDecompressStream( - compressionType, - nonDisposingStream - ); - - try - { - return TarArchive.IsTarFile(testStream); - } - finally - { - DisposeProbeStream(testStream); - } - } - - private static async ValueTask IsCompressedTarAsync( - Stream stream, - IFactory factory, - ReaderOptions readerOptions, - CancellationToken cancellationToken - ) => - GetCompressedTarType(factory) is { } compressionType - && await IsCompressedTarAsync(stream, readerOptions, compressionType, cancellationToken) - .ConfigureAwait(false); - - private static async ValueTask IsCompressedTarAsync( - Stream stream, - ReaderOptions readerOptions, - CompressionType compressionType, - CancellationToken cancellationToken - ) - { - using var nonDisposingStream = SharpCompressStream.CreateNonDisposing(stream); - var testStream = - compressionType == CompressionType.GZip - ? await readerOptions - .Providers.CreateDecompressStreamAsync( - compressionType, - nonDisposingStream, - CompressionContext - .FromStream(nonDisposingStream) - .WithReaderOptions(readerOptions), - cancellationToken - ) - .ConfigureAwait(false) - : await readerOptions - .Providers.CreateDecompressStreamAsync( - compressionType, - nonDisposingStream, - cancellationToken - ) - .ConfigureAwait(false); - - try - { - return await TarArchive - .IsTarFileAsync(testStream, cancellationToken) - .ConfigureAwait(false); - } - finally - { - DisposeProbeStream(testStream); - } - } - private static void DisposeProbeStream(Stream stream) { try @@ -408,169 +30,6 @@ public static partial class ArchiveFactory _ => null, }; - private static ArchiveDetection? TryDetectArchive(Stream stream, ReaderOptions readerOptions) - { - var startPosition = stream.Position; - - try - { - foreach (var factory in Factory.Factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if (!factory.IsArchive(stream, readerOptions)) - { - continue; - } - - if (GetCompressedTarType(factory) is { } compressionType) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if (IsCompressedTar(stream, readerOptions, compressionType)) - { - return CreateCompressedTarDetection(compressionType); - } - } - - return CreateDetection(factory); - } - - return - TryDetectCompressedTar(stream, readerOptions, startPosition) - is { } compressedTarType - ? CreateCompressedTarDetection(compressedTarType) - : null; - } - finally - { - stream.Seek(startPosition, SeekOrigin.Begin); - } - } - - private static async ValueTask TryDetectArchiveAsync( - Stream stream, - ReaderOptions readerOptions, - CancellationToken cancellationToken - ) - { - var startPosition = stream.Position; - - try - { - foreach (var factory in Factory.Factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if ( - !await factory - .IsArchiveAsync(stream, readerOptions, cancellationToken) - .ConfigureAwait(false) - ) - { - continue; - } - - if (GetCompressedTarType(factory) is { } compressionType) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if ( - await IsCompressedTarAsync( - stream, - readerOptions, - compressionType, - cancellationToken - ) - .ConfigureAwait(false) - ) - { - return CreateCompressedTarDetection(compressionType); - } - } - - return CreateDetection(factory); - } - - var compressedTarType = await TryDetectCompressedTarAsync( - stream, - readerOptions, - startPosition, - cancellationToken - ) - .ConfigureAwait(false); - return compressedTarType is { } value ? CreateCompressedTarDetection(value) : null; - } - finally - { - stream.Seek(startPosition, SeekOrigin.Begin); - } - } - - private static CompressionType? TryDetectCompressedTar( - Stream stream, - ReaderOptions readerOptions, - long startPosition - ) - { - foreach (var wrapper in TarWrapper.Wrappers) - { - if (wrapper.CompressionType == CompressionType.None) - { - continue; - } - - stream.Seek(startPosition, SeekOrigin.Begin); - if (!wrapper.IsMatch(stream)) - { - continue; - } - - stream.Seek(startPosition, SeekOrigin.Begin); - if (IsCompressedTar(stream, readerOptions, wrapper.CompressionType)) - { - return wrapper.CompressionType; - } - } - - return null; - } - - private static async ValueTask TryDetectCompressedTarAsync( - Stream stream, - ReaderOptions readerOptions, - long startPosition, - CancellationToken cancellationToken - ) - { - foreach (var wrapper in TarWrapper.Wrappers) - { - cancellationToken.ThrowIfCancellationRequested(); - if (wrapper.CompressionType == CompressionType.None) - { - continue; - } - - stream.Seek(startPosition, SeekOrigin.Begin); - if (!await wrapper.IsMatchAsync(stream, cancellationToken).ConfigureAwait(false)) - { - continue; - } - - stream.Seek(startPosition, SeekOrigin.Begin); - if ( - await IsCompressedTarAsync( - stream, - readerOptions, - wrapper.CompressionType, - cancellationToken - ) - .ConfigureAwait(false) - ) - { - return wrapper.CompressionType; - } - } - - return null; - } - private static ArchiveDetection CreateDetection(IFactory factory) { var supportedApis = ArchiveAccessMode.None; diff --git a/src/SharpCompress/Archives/ArchiveFactory.Information.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Information.Async.cs index 1f26bd3e..8ac74f5d 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Information.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Information.Async.cs @@ -20,11 +20,12 @@ namespace SharpCompress.Archives; public static partial class ArchiveFactory { /// - /// Asynchronously collects metadata for the archive at the given file path. + /// Collects metadata for the archive at the given file path. /// /// Path to the archive file. /// Cancellation token. /// Archive metadata, or when the file is not a supported archive. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async ValueTask InspectArchiveAsync( string filePath, CancellationToken cancellationToken = default @@ -33,12 +34,13 @@ public static partial class ArchiveFactory .ConfigureAwait(false); /// - /// Asynchronously collects metadata for the archive at the given file path. + /// Collects metadata for the archive at the given file path. /// /// Path to the archive file. /// Options controlling archive inspection. /// Cancellation token. /// Archive metadata, or when the file is not a supported archive. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async ValueTask InspectArchiveAsync( string filePath, ReaderOptions? readerOptions, @@ -68,11 +70,12 @@ public static partial class ArchiveFactory } /// - /// Asynchronously collects metadata for the archive in the given stream. + /// Collects metadata for the archive in the given stream. /// /// A readable and seekable stream positioned at the start of the archive. /// Cancellation token. /// Archive metadata, or when the stream is not a supported archive. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async ValueTask InspectArchiveAsync( Stream stream, CancellationToken cancellationToken = default @@ -81,13 +84,14 @@ public static partial class ArchiveFactory .ConfigureAwait(false); /// - /// Asynchronously collects metadata for the archive in the given stream. + /// Collects metadata for the archive in the given stream. /// /// A readable and seekable stream positioned at the start of the archive. /// Options controlling archive inspection. /// Cancellation token. /// Archive metadata, or when the stream is not a supported archive. /// The supplied stream remains open and is restored to its original position. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async ValueTask InspectArchiveAsync( Stream stream, ReaderOptions? readerOptions, @@ -96,7 +100,9 @@ public static partial class ArchiveFactory { stream.RequireReadable(); stream.RequireSeekable(); +#if !SYNC_ONLY cancellationToken.ThrowIfCancellationRequested(); +#endif var options = readerOptions ?? ReaderOptions.ForExternalStream; var startPosition = stream.Position; @@ -119,6 +125,10 @@ public static partial class ArchiveFactory if ((detection.SupportedApis & ArchiveAccessMode.Archive) != 0) { +#if SYNC_ONLY + using var archive = OpenArchive(archiveStream, inspectionOptions); + return InspectOpenedArchive(archive, detection, physicalSize, 1); +#else await using var archive = await OpenAsyncArchive( archiveStream, inspectionOptions, @@ -133,8 +143,14 @@ public static partial class ArchiveFactory cancellationToken ) .ConfigureAwait(false); +#endif } +#if SYNC_ONLY + var aceHeader = ReadAceHeader(archiveStream, detection, inspectionOptions); + using var reader = ReaderFactory.OpenReader(archiveStream, inspectionOptions); + return InspectOpenedReader(reader, detection, physicalSize, 1, aceHeader); +#else var aceHeader = await ReadAceHeaderAsync( archiveStream, detection, @@ -154,6 +170,7 @@ public static partial class ArchiveFactory cancellationToken ) .ConfigureAwait(false); +#endif } catch (CryptographicException) when (string.IsNullOrEmpty(options.Password)) { @@ -198,12 +215,13 @@ public static partial class ArchiveFactory } /// - /// Asynchronously collects metadata for an archive opened from multiple files. + /// Collects metadata for an archive opened from multiple files. /// /// Archive source files in archive order. /// Options controlling archive inspection. /// Cancellation token. /// Archive metadata, or when the files are not a supported archive. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async ValueTask InspectArchiveAsync( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, @@ -242,6 +260,10 @@ public static partial class ArchiveFactory var physicalSize = GetPhysicalSize(fileInfos); try { +#if SYNC_ONLY + using var archive = OpenArchive(fileInfos, options); + return InspectOpenedArchive(archive, detection, physicalSize, fileInfos.Count); +#else await using var archive = await OpenAsyncArchive(fileInfos, options, cancellationToken) .ConfigureAwait(false); return await InspectOpenedArchiveAsync( @@ -252,6 +274,7 @@ public static partial class ArchiveFactory cancellationToken ) .ConfigureAwait(false); +#endif } catch (CryptographicException) when (string.IsNullOrEmpty(options.Password)) { @@ -265,12 +288,13 @@ public static partial class ArchiveFactory } /// - /// Asynchronously collects metadata for an archive opened from multiple streams. + /// Collects metadata for an archive opened from multiple streams. /// /// Archive source streams in archive order. /// Options controlling archive inspection. /// Cancellation token. /// Archive metadata, or when the streams are not a supported archive. + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async ValueTask InspectArchiveAsync( IReadOnlyList streams, ReaderOptions? readerOptions = null, @@ -317,6 +341,10 @@ public static partial class ArchiveFactory archiveStreams.AddRange( streams.Skip(1).Select(stream => new ArchiveOffsetStream(stream)) ); +#if SYNC_ONLY + using var archive = OpenArchive(archiveStreams, inspectionOptions); + return InspectOpenedArchive(archive, detection, physicalSize, streams.Count); +#else await using var archive = await OpenAsyncArchive( archiveStreams, inspectionOptions, @@ -331,6 +359,7 @@ public static partial class ArchiveFactory cancellationToken ) .ConfigureAwait(false); +#endif } catch (CryptographicException) when (string.IsNullOrEmpty(options.Password)) { @@ -466,6 +495,7 @@ public static partial class ArchiveFactory ); } + [Zomp.SyncMethodGenerator.CreateSyncVersion] private static async ValueTask ReadAceHeaderAsync( Stream stream, ArchiveDetection detection, @@ -566,6 +596,7 @@ public static partial class ArchiveFactory return (new ZipArchiveInformation(deferredSizeEntryCount > 0), deferredSizeEntryCount); } + [Zomp.SyncMethodGenerator.CreateSyncVersion] private static async ValueTask RedetectArchiveAsync( Stream stream, long startPosition, diff --git a/src/SharpCompress/Archives/ArchiveFactory.Information.cs b/src/SharpCompress/Archives/ArchiveFactory.Information.cs index b89774ef..4b12fe8d 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Information.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Information.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Archives.Rar; using SharpCompress.Archives.SevenZip; using SharpCompress.Archives.Zip; @@ -12,7 +10,6 @@ using SharpCompress.Common.Rar; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; using SharpCompress.Detection; -using SharpCompress.IO; using SharpCompress.Readers; using AceMainHeader = SharpCompress.Common.Ace.Headers.AceMainHeader; @@ -20,236 +17,6 @@ namespace SharpCompress.Archives; public static partial class ArchiveFactory { - /// - /// Collects metadata for the archive at the given file path. - /// - /// Path to the archive file. - /// Archive metadata, or when the file is not a supported archive. - public static ArchiveInformation? InspectArchive(string filePath) => - InspectArchive(filePath, ReaderOptions.ForFilePath); - - /// - /// Collects metadata for the archive at the given file path. - /// - /// Path to the archive file. - /// Options controlling archive inspection. - /// Archive metadata, or when the file is not a supported archive. - public static ArchiveInformation? InspectArchive(string filePath, ReaderOptions? readerOptions) - { - filePath.NotNullOrEmpty(nameof(filePath)); - var options = readerOptions ?? ReaderOptions.ForFilePath; - var detection = DetectArchive(filePath, options); - if (detection is null) - { - return null; - } - if ((detection.SupportedApis & ArchiveAccessMode.Archive) != 0) - { - var fileInfos = GetArchiveFileParts(new FileInfo(filePath), options); - if (fileInfos.Length > 1) - { - return InspectArchive(fileInfos, options); - } - } - - using Stream stream = File.OpenRead(filePath); - return InspectArchive(stream, options); - } - - /// - /// Collects metadata for the archive in the given stream. - /// - /// A readable and seekable stream positioned at the start of the archive. - /// Archive metadata, or when the stream is not a supported archive. - public static ArchiveInformation? InspectArchive(Stream stream) => - InspectArchive(stream, ReaderOptions.ForExternalStream); - - /// - /// Collects metadata for the archive in the given stream. - /// - /// A readable and seekable stream positioned at the start of the archive. - /// Options controlling archive inspection. - /// Archive metadata, or when the stream is not a supported archive. - /// The supplied stream remains open and is restored to its original position. - public static ArchiveInformation? InspectArchive(Stream stream, ReaderOptions? readerOptions) - { - stream.RequireReadable(); - stream.RequireSeekable(); - - var options = readerOptions ?? ReaderOptions.ForExternalStream; - var startPosition = stream.Position; - var physicalSize = GetPhysicalSize(stream, startPosition); - - try - { - using var archiveStream = new ArchiveOffsetStream(stream); - var inspectionOptions = options with { LeaveStreamOpen = true }; - var detection = TryDetectArchive(archiveStream, inspectionOptions); - if (detection is null) - { - return null; - } - - if ((detection.SupportedApis & ArchiveAccessMode.Archive) != 0) - { - using var archive = OpenArchive(archiveStream, inspectionOptions); - return InspectOpenedArchive(archive, detection, physicalSize, 1); - } - - var aceHeader = ReadAceHeader(archiveStream, detection, inspectionOptions); - using var reader = ReaderFactory.OpenReader(archiveStream, inspectionOptions); - return InspectOpenedReader(reader, detection, physicalSize, 1, aceHeader); - } - catch (CryptographicException) when (string.IsNullOrEmpty(options.Password)) - { - var detection = RedetectArchive(stream, startPosition, options); - return detection is null - ? null - : CreatePartialInformation( - detection, - physicalSize, - 1, - ArchiveInformationLimitations.EncryptedHeaders - ); - } - catch (MultipartStreamRequiredException) - { - var detection = RedetectArchive(stream, startPosition, options); - return detection is null - ? null - : CreatePartialInformation( - detection, - physicalSize, - 1, - ArchiveInformationLimitations.MissingVolumes - ); - } - finally - { - stream.Seek(startPosition, SeekOrigin.Begin); - } - } - - /// - /// Collects metadata for an archive opened from multiple files. - /// - /// Archive source files in archive order. - /// Options controlling archive inspection. - /// Archive metadata, or when the files are not a supported archive. - public static ArchiveInformation? InspectArchive( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - if (fileInfos.Count == 0) - { - throw new ArchiveOperationException("No files to inspect"); - } - if (fileInfos.Count == 1) - { - return InspectArchive(fileInfos[0].FullName, readerOptions); - } - - var options = readerOptions ?? ReaderOptions.ForFilePath; - var detection = DetectArchive(fileInfos[0].FullName, options); - if (detection is null) - { - return null; - } - if ((detection.SupportedApis & ArchiveAccessMode.Archive) == 0) - { - throw new NotSupportedException( - "Inspecting multiple source files is supported only for formats with an Archive API." - ); - } - - var physicalSize = GetPhysicalSize(fileInfos); - try - { - using var archive = OpenArchive(fileInfos, options); - return InspectOpenedArchive(archive, detection, physicalSize, fileInfos.Count); - } - catch (CryptographicException) when (string.IsNullOrEmpty(options.Password)) - { - return CreatePartialInformation( - detection, - physicalSize, - fileInfos.Count, - ArchiveInformationLimitations.EncryptedHeaders - ); - } - } - - /// - /// Collects metadata for an archive opened from multiple streams. - /// - /// Archive source streams in archive order. - /// Options controlling archive inspection. - /// Archive metadata, or when the streams are not a supported archive. - public static ArchiveInformation? InspectArchive( - IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) - { - streams.NotNull(nameof(streams)); - if (streams.Count == 0) - { - throw new ArchiveOperationException("No streams to inspect"); - } - if (streams.Count == 1) - { - return InspectArchive(streams[0], readerOptions); - } - - var options = readerOptions ?? ReaderOptions.ForExternalStream; - var startPositions = streams.Select(stream => stream.Position).ToArray(); - var physicalSize = GetPhysicalSize(streams, startPositions); - - try - { - using var firstArchiveStream = new ArchiveOffsetStream(streams[0]); - var inspectionOptions = options with { LeaveStreamOpen = true }; - var detection = TryDetectArchive(firstArchiveStream, inspectionOptions); - if (detection is null) - { - return null; - } - if ((detection.SupportedApis & ArchiveAccessMode.Archive) == 0) - { - throw new NotSupportedException( - "Inspecting multiple source streams is supported only for formats with an Archive API." - ); - } - - var archiveStreams = new List { firstArchiveStream }; - archiveStreams.AddRange( - streams.Skip(1).Select(stream => new ArchiveOffsetStream(stream)) - ); - using var archive = OpenArchive(archiveStreams, inspectionOptions); - return InspectOpenedArchive(archive, detection, physicalSize, streams.Count); - } - catch (CryptographicException) when (string.IsNullOrEmpty(options.Password)) - { - var detection = RedetectArchive(streams[0], startPositions[0], options); - return detection is null - ? null - : CreatePartialInformation( - detection, - physicalSize, - streams.Count, - ArchiveInformationLimitations.EncryptedHeaders - ); - } - finally - { - for (var i = 0; i < streams.Count; i++) - { - streams[i].Seek(startPositions[i], SeekOrigin.Begin); - } - } - } - private static ArchiveInformation InspectOpenedArchive( IArchive archive, ArchiveDetection detection, @@ -331,28 +98,6 @@ public static partial class ArchiveFactory ); } - private static AceMainHeader? ReadAceHeader( - Stream stream, - ArchiveDetection detection, - ReaderOptions options - ) - { - if (detection.ContainerType != ArchiveType.Ace) - { - return null; - } - - try - { - stream.Position = 0; - return new AceMainHeader(options.ArchiveEncoding).Read(stream) as AceMainHeader; - } - finally - { - stream.Position = 0; - } - } - private static ArchiveInformation CreatePartialInformation( ArchiveDetection detection, long? physicalSize, @@ -486,17 +231,6 @@ public static partial class ArchiveFactory } } - private static ArchiveDetection? RedetectArchive( - Stream stream, - long startPosition, - ReaderOptions options - ) - { - stream.Seek(startPosition, SeekOrigin.Begin); - using var archiveStream = new ArchiveOffsetStream(stream); - return TryDetectArchive(archiveStream, options); - } - private static FileInfo[] GetArchiveFileParts(FileInfo firstPart, ReaderOptions options) { using Stream stream = firstPart.OpenRead(); diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 63b63954..efff5604 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -123,37 +123,6 @@ public static partial class ArchiveFactory public static T FindFactory(Stream stream) where T : IFactory => FindFactory(stream, ReaderOptions.ForExternalStream); - private static T FindFactory(FileInfo fileInfo, ReaderOptions readerOptions) - where T : IFactory - { - fileInfo.NotNull(nameof(fileInfo)); - using Stream stream = fileInfo.OpenRead(); - return FindFactory(stream, readerOptions); - } - - private static T FindFactory(Stream stream, ReaderOptions readerOptions) - where T : IFactory - { - stream.RequireReadable(); - stream.RequireSeekable(); - - // Use the shared detection loop over all factories. If the matched factory - // implements T we return it; otherwise (or if nothing matched) we fall through - // to the same "unsupported format" exception that the original code produced, - // listing the T-typed factories as the hint for the caller. - var factory = TryFindFactory(stream, readerOptions); - if (factory is T typedFactory) - { - return typedFactory; - } - - var extensions = string.Join(", ", Factory.Factories.OfType().Select(item => item.Name)); - - throw new ArchiveOperationException( - $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" - ); - } - public static bool IsArchive(string filePath, out ArchiveType? type) { return IsArchive(filePath, ReaderOptions.ForFilePath, out type);