Consolidate archive detection into shared TryFindFactory helpers

Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/9c5bf06d-3e2f-4bb5-9130-f5b7cdd1d0a0

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-25 11:13:00 +00:00
committed by GitHub
parent 6c59326628
commit f118935bb8
3 changed files with 81 additions and 82 deletions

View File

@@ -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<T> FindFactoryAsync<T>(
@@ -188,14 +176,39 @@ public static partial class ArchiveFactory
stream.RequireReadable();
stream.RequireSeekable();
var factories = Factory.Factories.OfType<T>();
// 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<T>().Select(item => item.Name));
throw new ArchiveOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
/// <summary>
/// Async counterpart of <see cref="ArchiveFactory.TryFindFactory"/>.
/// Iterates all registered factories and returns the first one whose
/// <see cref="IFactory.IsArchiveAsync"/> recognises the stream, or <see langword="null"/>.
/// Stream position is restored to its value at entry on both success and failure.
/// </summary>
private static async ValueTask<IFactory?> 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;
}
}

View File

@@ -123,23 +123,17 @@ public static partial class ArchiveFactory
stream.RequireReadable();
stream.RequireSeekable();
var factories = Factory.Factories.OfType<T>();
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<T>().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;
}
/// <summary>
@@ -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);
}
/// <summary>
/// Iterates all registered factories and returns the first one whose
/// <see cref="IFactory.IsArchive"/> recognises the stream, or <see langword="null"/>.
/// Stream position is restored to its value at entry on both success and failure.
/// </summary>
/// <remarks>
/// This is the shared, seekable-stream detection core used by
/// <see cref="FindFactory{T}(Stream)"/>, <see cref="IsArchive(Stream, out ArchiveType?)"/>,
/// and <see cref="GetArchiveInformation(Stream)"/>.
/// <para>
/// <see cref="ReaderFactory.OpenReader(Stream, ReaderOptions)"/> uses a separate code path
/// based on <see cref="IO.SharpCompressStream"/> rewindable buffering, which supports
/// non-seekable streams and is therefore not unified with this helper.
/// </para>
/// </remarks>
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<string> GetFileParts(string part1)

View File

@@ -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",