diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs index f2c945c6..a96bbefa 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -140,22 +140,10 @@ public static partial class ArchiveFactory stream.RequireReadable(); stream.RequireSeekable(); - var startPosition = stream.Position; - - foreach (var factory in Factory.Factories) - { - var isArchive = await factory - .IsArchiveAsync(stream, cancellationToken: cancellationToken) - .ConfigureAwait(false); - stream.Position = startPosition; - - if (isArchive) - { - return new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); - } - } - - return null; + var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false); + return factory is null + ? null + : new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); } internal static ValueTask FindFactoryAsync( @@ -188,14 +176,39 @@ public static partial class ArchiveFactory stream.RequireReadable(); stream.RequireSeekable(); - var factories = Factory.Factories.OfType(); + // 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 factories) + foreach (var factory in Factory.Factories) { stream.Seek(startPosition, SeekOrigin.Begin); - if ( await factory .IsArchiveAsync(stream, cancellationToken: cancellationToken) @@ -203,15 +216,11 @@ public static partial class ArchiveFactory ) { stream.Seek(startPosition, SeekOrigin.Begin); - return factory; } } - var extensions = string.Join(", ", factories.Select(item => item.Name)); - - throw new ArchiveOperationException( - $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" - ); + stream.Seek(startPosition, SeekOrigin.Begin); + return null; } } diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 3767c7c9..0f236a0e 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -123,23 +123,17 @@ public static partial class ArchiveFactory stream.RequireReadable(); stream.RequireSeekable(); - var factories = Factory.Factories.OfType(); - - var startPosition = stream.Position; - - foreach (var factory in factories) + // 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); + if (factory is T typedFactory) { - stream.Seek(startPosition, SeekOrigin.Begin); - - if (factory.IsArchive(stream)) - { - stream.Seek(startPosition, SeekOrigin.Begin); - - return factory; - } + return typedFactory; } - var extensions = string.Join(", ", factories.Select(item => item.Name)); + var extensions = string.Join(", ", Factory.Factories.OfType().Select(item => item.Name)); throw new ArchiveOperationException( $"Cannot determine compressed stream type. Supported Archive Formats: {extensions}" @@ -155,25 +149,12 @@ public static partial class ArchiveFactory public static bool IsArchive(Stream stream, out ArchiveType? type) { - type = null; stream.RequireReadable(); stream.RequireSeekable(); - var startPosition = stream.Position; - - foreach (var factory in Factory.Factories) - { - var isArchive = factory.IsArchive(stream); - stream.Position = startPosition; - - if (isArchive) - { - type = factory.KnownArchiveType; - return true; - } - } - - return false; + var factory = TryFindFactory(stream); + type = factory?.KnownArchiveType; + return factory is not null; } /// @@ -198,19 +179,42 @@ public static partial class ArchiveFactory 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) { - var isArchive = factory.IsArchive(stream); - stream.Position = startPosition; - - if (isArchive) + stream.Seek(startPosition, SeekOrigin.Begin); + if (factory.IsArchive(stream)) { - return new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory); + stream.Seek(startPosition, SeekOrigin.Begin); + return factory; } } + stream.Seek(startPosition, SeekOrigin.Begin); return null; } @@ -232,22 +236,8 @@ public static partial class ArchiveFactory stream.RequireReadable(); stream.RequireSeekable(); - var startPosition = stream.Position; - - foreach (var factory in Factory.Factories) - { - var isArchive = await factory - .IsArchiveAsync(stream, cancellationToken: cancellationToken) - .ConfigureAwait(false); - stream.Position = startPosition; - - if (isArchive) - { - return (true, factory.KnownArchiveType); - } - } - - return (false, null); + var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false); + return (factory is not null, factory?.KnownArchiveType); } public static IEnumerable GetFileParts(string part1) diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index a401c702..03c03a9a 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -268,9 +268,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" + "requested": "[10.0.5, )", + "resolved": "10.0.5", + "contentHash": "A+5ZuQ0f449tM+MQrhf6R9ZX7lYpjk/ODEwLYKrnF6111rtARx8fVsm4YznUnQiKnnXfaXNBqgxmil6RW3L3SA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -400,9 +400,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.22, )", - "resolved": "8.0.22", - "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" + "requested": "[8.0.25, )", + "resolved": "8.0.25", + "contentHash": "sqX4nmBft05ivqKvUT4nxaN8rT3apCLt9SWFkfRrQPwra1zPwFknQAw1lleuMCKOCLvVmOWwrC2iPSm9RiXZUg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct",