From d2c06e95d6b1d805907a5582def96bfe886abbf4 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sun, 26 Apr 2026 11:07:11 +0100 Subject: [PATCH] Clean up and move detection to new file --- .../Archives/ArchiveFactory.Async.cs | 116 ----------- .../Archives/ArchiveFactory.Detection.cs | 187 ++++++++++++++++++ src/SharpCompress/Archives/ArchiveFactory.cs | 61 ------ 3 files changed, 187 insertions(+), 177 deletions(-) create mode 100644 src/SharpCompress/Archives/ArchiveFactory.Detection.cs diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs index a96bbefa..6c45cb1f 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -1,11 +1,9 @@ -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.Readers; namespace SharpCompress.Archives; @@ -109,118 +107,4 @@ public static partial class ArchiveFactory .OpenAsyncArchive(streamsArray, options, cancellationToken) .ConfigureAwait(false); } - - /// - /// Returns information about the archive at the given file path asynchronously, - /// or if the file is not a recognized archive. - /// - /// Path to the archive file. - /// Cancellation token. - public static async ValueTask GetArchiveInformationAsync( - string filePath, - CancellationToken cancellationToken = default - ) - { - filePath.NotNullOrEmpty(nameof(filePath)); - using Stream stream = File.OpenRead(filePath); - return await GetArchiveInformationAsync(stream, cancellationToken).ConfigureAwait(false); - } - - /// - /// Returns information about the archive in the given stream asynchronously, - /// or if the stream is not a recognized archive. - /// - /// A readable and seekable stream positioned at the start of the archive. - /// Cancellation token. - public static async ValueTask GetArchiveInformationAsync( - Stream stream, - CancellationToken cancellationToken = default - ) - { - stream.RequireReadable(); - stream.RequireSeekable(); - - var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false); - return factory is null - ? null - : new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); - } - - 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 finfo, - CancellationToken cancellationToken = default - ) - where T : IFactory - { - finfo.NotNull(nameof(finfo)); - using Stream stream = finfo.OpenRead(); - return await FindFactoryAsync(stream, 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. - var factory = await TryFindFactoryAsync(stream, 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 . - /// 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, - CancellationToken cancellationToken - ) - { - var startPosition = stream.Position; - - foreach (var factory in Factory.Factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if ( - await factory - .IsArchiveAsync(stream, cancellationToken: cancellationToken) - .ConfigureAwait(false) - ) - { - stream.Seek(startPosition, SeekOrigin.Begin); - return factory; - } - } - - stream.Seek(startPosition, SeekOrigin.Begin); - return null; - } } diff --git a/src/SharpCompress/Archives/ArchiveFactory.Detection.cs b/src/SharpCompress/Archives/ArchiveFactory.Detection.cs new file mode 100644 index 00000000..ae04e09f --- /dev/null +++ b/src/SharpCompress/Archives/ArchiveFactory.Detection.cs @@ -0,0 +1,187 @@ +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Factories; +using SharpCompress.Readers; + +namespace SharpCompress.Archives; + +public static partial class ArchiveFactory +{ + /// + /// Returns information about the archive at the given file path asynchronously, + /// or if the file is not a recognized archive. + /// + /// Path to the archive file. + /// Cancellation token. + public static async ValueTask GetArchiveInformationAsync( + string filePath, + CancellationToken cancellationToken = default + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + using Stream stream = File.OpenRead(filePath); + return await GetArchiveInformationAsync(stream, cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns information about the archive in the given stream asynchronously, + /// or if the stream is not a recognized archive. + /// + /// A readable and seekable stream positioned at the start of the archive. + /// Cancellation token. + public static async ValueTask GetArchiveInformationAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + stream.RequireReadable(); + stream.RequireSeekable(); + + var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false); + return factory is null + ? null + : new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); + } + + 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); + } + + 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. + var factory = await TryFindFactoryAsync(stream, 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 . + /// 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, + CancellationToken cancellationToken + ) + { + var startPosition = stream.Position; + + foreach (var factory in Factory.Factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + if ( + await factory + .IsArchiveAsync(stream, cancellationToken: cancellationToken) + .ConfigureAwait(false) + ) + { + stream.Seek(startPosition, SeekOrigin.Begin); + return factory; + } + } + + stream.Seek(startPosition, SeekOrigin.Begin); + return null; + } + + /// + /// Returns information about the archive at the given file path, + /// or if the file is not a recognized archive. + /// + /// Path to the archive file. + public static ArchiveInformation? GetArchiveInformation(string filePath) + { + filePath.NotNullOrEmpty(nameof(filePath)); + using Stream stream = File.OpenRead(filePath); + return GetArchiveInformation(stream); + } + + /// + /// Returns information about the archive in the given stream, + /// or if the stream is not a recognized archive. + /// + /// A readable and seekable stream positioned at the start of the archive. + public static ArchiveInformation? GetArchiveInformation(Stream stream) + { + stream.RequireReadable(); + stream.RequireSeekable(); + + var factory = TryFindFactory(stream); + return factory is null + ? null + : new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); + } + + /// + /// 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) + { + var startPosition = stream.Position; + + foreach (var factory in Factory.Factories) + { + stream.Seek(startPosition, SeekOrigin.Begin); + if (factory.IsArchive(stream)) + { + stream.Seek(startPosition, SeekOrigin.Begin); + return factory; + } + } + + stream.Seek(startPosition, SeekOrigin.Begin); + return null; + } +} diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 0f236a0e..d6f08cc6 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -157,67 +157,6 @@ public static partial class ArchiveFactory return factory is not null; } - /// - /// Returns information about the archive at the given file path, - /// or if the file is not a recognized archive. - /// - /// Path to the archive file. - public static ArchiveInformation? GetArchiveInformation(string filePath) - { - filePath.NotNullOrEmpty(nameof(filePath)); - using Stream stream = File.OpenRead(filePath); - return GetArchiveInformation(stream); - } - - /// - /// Returns information about the archive in the given stream, - /// or if the stream is not a recognized archive. - /// - /// A readable and seekable stream positioned at the start of the archive. - public static ArchiveInformation? GetArchiveInformation(Stream stream) - { - stream.RequireReadable(); - stream.RequireSeekable(); - - var factory = TryFindFactory(stream); - return factory is null - ? null - : new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); - } - - /// - /// 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) - { - var startPosition = stream.Position; - - foreach (var factory in Factory.Factories) - { - stream.Seek(startPosition, SeekOrigin.Begin); - if (factory.IsArchive(stream)) - { - stream.Seek(startPosition, SeekOrigin.Begin); - return factory; - } - } - - stream.Seek(startPosition, SeekOrigin.Begin); - return null; - } - public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync( string filePath, CancellationToken cancellationToken = default