mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 06:54:40 +00:00
move detection and use more sync method
This commit is contained in:
369
src/SharpCompress/Archives/ArchiveFactory.Detection.Async.cs
Normal file
369
src/SharpCompress/Archives/ArchiveFactory.Detection.Async.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the archive at the given file path without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveDetection?> DetectArchiveAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await DetectArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive at the given file path without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveDetection?> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive in the given stream without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveDetection?> DetectArchiveAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await DetectArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive in the given stream without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveDetection?> 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<T> FindFactoryAsync<T>(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
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<T>(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
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<T>().Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
private static async ValueTask<IFactory?> 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<bool> 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<bool> 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<ArchiveDetection?> 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<CompressionType?> 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the archive at the given file path without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveDetection?> DetectArchiveAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await DetectArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive at the given file path without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveDetection?> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive in the given stream without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveDetection?> DetectArchiveAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await DetectArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive in the given stream without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveDetection?> 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<T> FindFactoryAsync<T>(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
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<T>(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
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<T>().Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async counterpart of the synchronous factory detection path.
|
||||
/// 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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive at the given file path without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
public static ArchiveDetection? DetectArchive(string filePath) =>
|
||||
DetectArchive(filePath, ReaderOptions.ForFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive at the given file path without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
public static ArchiveDetection? DetectArchive(string filePath, ReaderOptions? readerOptions)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return DetectArchive(stream, readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive in the given stream without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
public static ArchiveDetection? DetectArchive(Stream stream) =>
|
||||
DetectArchive(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the archive in the given stream without enumerating its entries.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
public static ArchiveDetection? DetectArchive(Stream stream, ReaderOptions? readerOptions)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return TryDetectArchive(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <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="DetectArchive(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) =>
|
||||
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<bool> 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<bool> 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<ArchiveDetection?> 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<CompressionType?> 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;
|
||||
|
||||
@@ -20,11 +20,12 @@ namespace SharpCompress.Archives;
|
||||
public static partial class ArchiveFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronously collects metadata for the archive at the given file path.
|
||||
/// Collects metadata for the archive at the given file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the file is not a supported archive.</returns>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveInformation?> InspectArchiveAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -33,12 +34,13 @@ public static partial class ArchiveFactory
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously collects metadata for the archive at the given file path.
|
||||
/// Collects metadata for the archive at the given file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the file is not a supported archive.</returns>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveInformation?> InspectArchiveAsync(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
@@ -68,11 +70,12 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously collects metadata for the archive in the given stream.
|
||||
/// Collects metadata for the archive in the given stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the stream is not a supported archive.</returns>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveInformation?> InspectArchiveAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -81,13 +84,14 @@ public static partial class ArchiveFactory
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously collects metadata for the archive in the given stream.
|
||||
/// Collects metadata for the archive in the given stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the stream is not a supported archive.</returns>
|
||||
/// <remarks>The supplied stream remains open and is restored to its original position.</remarks>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveInformation?> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously collects metadata for an archive opened from multiple files.
|
||||
/// Collects metadata for an archive opened from multiple files.
|
||||
/// </summary>
|
||||
/// <param name="fileInfos">Archive source files in archive order.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the files are not a supported archive.</returns>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveInformation?> InspectArchiveAsync(
|
||||
IReadOnlyList<FileInfo> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously collects metadata for an archive opened from multiple streams.
|
||||
/// Collects metadata for an archive opened from multiple streams.
|
||||
/// </summary>
|
||||
/// <param name="streams">Archive source streams in archive order.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the streams are not a supported archive.</returns>
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
public static async ValueTask<ArchiveInformation?> InspectArchiveAsync(
|
||||
IReadOnlyList<Stream> 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<AceMainHeader?> 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<ArchiveDetection?> RedetectArchiveAsync(
|
||||
Stream stream,
|
||||
long startPosition,
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Collects metadata for the archive at the given file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the file is not a supported archive.</returns>
|
||||
public static ArchiveInformation? InspectArchive(string filePath) =>
|
||||
InspectArchive(filePath, ReaderOptions.ForFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Collects metadata for the archive at the given file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the file is not a supported archive.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects metadata for the archive in the given stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the stream is not a supported archive.</returns>
|
||||
public static ArchiveInformation? InspectArchive(Stream stream) =>
|
||||
InspectArchive(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
/// <summary>
|
||||
/// Collects metadata for the archive in the given stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the stream is not a supported archive.</returns>
|
||||
/// <remarks>The supplied stream remains open and is restored to its original position.</remarks>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects metadata for an archive opened from multiple files.
|
||||
/// </summary>
|
||||
/// <param name="fileInfos">Archive source files in archive order.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the files are not a supported archive.</returns>
|
||||
public static ArchiveInformation? InspectArchive(
|
||||
IReadOnlyList<FileInfo> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects metadata for an archive opened from multiple streams.
|
||||
/// </summary>
|
||||
/// <param name="streams">Archive source streams in archive order.</param>
|
||||
/// <param name="readerOptions">Options controlling archive inspection.</param>
|
||||
/// <returns>Archive metadata, or <see langword="null"/> when the streams are not a supported archive.</returns>
|
||||
public static ArchiveInformation? InspectArchive(
|
||||
IReadOnlyList<Stream> 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<Stream> { 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();
|
||||
|
||||
@@ -123,37 +123,6 @@ public static partial class ArchiveFactory
|
||||
public static T FindFactory<T>(Stream stream)
|
||||
where T : IFactory => FindFactory<T>(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
private static T FindFactory<T>(FileInfo fileInfo, ReaderOptions readerOptions)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return FindFactory<T>(stream, readerOptions);
|
||||
}
|
||||
|
||||
private static T FindFactory<T>(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<T>().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);
|
||||
|
||||
Reference in New Issue
Block a user