Add format-specific ArchiveInformation details

This commit is contained in:
copilot-swe-agent[bot]
2026-07-28 16:45:31 +00:00
committed by GitHub
parent 0d317bb466
commit d301cb365f
4 changed files with 192 additions and 3 deletions

View File

@@ -56,6 +56,8 @@ if (info is not null)
{
Console.WriteLine($"Type: {info.Type}");
Console.WriteLine($"Supports random access: {info.SupportsRandomAccess}");
Console.WriteLine($"ZIP data descriptor entries: {info.ZipDataDescriptorEntryCount}");
Console.WriteLine($"Solid stream count: {info.SolidStreamCount}");
}
var asyncInfo = await ArchiveFactory.GetArchiveInformationAsync(
@@ -73,7 +75,7 @@ using (var archive = ArchiveFactory.OpenArchive(parts))
}
```
`ArchiveInformation.SupportsRandomAccess` is `true` when the detected format supports `IArchive` random access. It is `false` for reader-only formats such as Ace, Arc, Arj, and standalone LZW, where `ReaderFactory.OpenReader` should be used instead. Compressed tar wrappers such as `.tar.gz` and `.tar.xz` are also reader-only; `ArchiveFactory.GetArchiveInformation` returns `null` for them and `ArchiveFactory.OpenArchive` does not open them as the outer compression wrapper. Use `ReaderFactory.OpenReader` or `TarReader.OpenReader` for those files.
`ArchiveInformation.SupportsRandomAccess` is `true` when the detected format supports `IArchive` random access. It is `false` for reader-only formats such as Ace, Arc, Arj, and standalone LZW, where `ReaderFactory.OpenReader` should be used instead. Compressed tar wrappers such as `.tar.gz` and `.tar.xz` are also reader-only; `ArchiveFactory.GetArchiveInformation` returns `null` for them and `ArchiveFactory.OpenArchive` does not open them as the outer compression wrapper. Use `ReaderFactory.OpenReader` or `TarReader.OpenReader` for those files. Format-specific details are available through `ArchiveInformation.ZipDataDescriptorEntryCount` (ZIP) and `ArchiveInformation.SolidStreamCount` (solid-capable formats such as 7z and RAR).
### Creating Archives

View File

@@ -2,8 +2,13 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Archives.Rar;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Archives.Tar;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Factories;
using SharpCompress.IO;
using SharpCompress.Providers;
@@ -86,7 +91,11 @@ public static partial class ArchiveFactory
.ConfigureAwait(false);
return factory is null
? null
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
: BuildArchiveInformation(
stream,
readerOptions ?? ReaderOptions.ForExternalStream,
factory
);
}
internal static ValueTask<T> FindFactoryAsync<T>(
@@ -254,7 +263,11 @@ public static partial class ArchiveFactory
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
return factory is null
? null
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
: BuildArchiveInformation(
stream,
readerOptions ?? ReaderOptions.ForExternalStream,
factory
);
}
/// <summary>
@@ -411,4 +424,115 @@ public static partial class ArchiveFactory
LzwFactory => CompressionType.Lzw,
_ => null,
};
private static ArchiveInformation BuildArchiveInformation(
Stream stream,
ReaderOptions readerOptions,
IFactory factory
)
{
var info = new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
var startPosition = stream.Position;
try
{
var probeReaderOptions = readerOptions with { LeaveStreamOpen = true };
switch (factory)
{
case ZipFactory:
TryPopulateZipDetails(info, stream, probeReaderOptions);
break;
case SevenZipFactory:
TryPopulateSevenZipDetails(info, stream, probeReaderOptions);
break;
case RarFactory:
TryPopulateRarDetails(info, stream, probeReaderOptions);
break;
}
}
finally
{
stream.Seek(startPosition, SeekOrigin.Begin);
}
return info;
}
private static void TryPopulateZipDetails(
ArchiveInformation info,
Stream stream,
ReaderOptions readerOptions
)
{
try
{
info.ZipDataDescriptorEntryCount = GetZipDataDescriptorEntryCount(
stream,
readerOptions
);
}
catch
{
// Keep archive detection resilient even when format-specific probing fails.
}
}
private static void TryPopulateSevenZipDetails(
ArchiveInformation info,
Stream stream,
ReaderOptions readerOptions
)
{
try
{
info.SolidStreamCount = GetSevenZipSolidStreamCount(stream, readerOptions);
}
catch
{
// Keep archive detection resilient even when format-specific probing fails.
}
}
private static void TryPopulateRarDetails(
ArchiveInformation info,
Stream stream,
ReaderOptions readerOptions
)
{
try
{
info.SolidStreamCount = GetRarSolidStreamCount(stream, readerOptions);
}
catch
{
// Keep archive detection resilient even when format-specific probing fails.
}
}
private static int GetZipDataDescriptorEntryCount(Stream stream, ReaderOptions readerOptions)
{
using var archive = ZipArchive.OpenArchive(stream, readerOptions);
return archive
.Entries.OfType<ZipArchiveEntry>()
.SelectMany(entry => entry.Parts.OfType<ZipFilePart>())
.Count(part =>
FlagUtility.HasFlag(part.Header.Flags, HeaderFlags.UsePostDataDescriptor)
);
}
private static int GetSevenZipSolidStreamCount(Stream stream, ReaderOptions readerOptions)
{
using var archive = SevenZipArchive.OpenArchive(stream, readerOptions);
return archive
.Entries.OfType<SevenZipArchiveEntry>()
.Where(entry => !entry.IsDirectory && entry.FilePart.Folder is not null)
.GroupBy(entry => entry.FilePart.Folder)
.Count(group => group.Skip(1).Any());
}
private static int GetRarSolidStreamCount(Stream stream, ReaderOptions readerOptions)
{
using var archive = RarArchive.OpenArchive(stream, readerOptions);
return archive.IsSolid ? 1 : 0;
}
}

View File

@@ -25,6 +25,18 @@ public record ArchiveInformation
/// </summary>
public bool SupportsRandomAccess { get; set; }
/// <summary>
/// For ZIP archives, the number of entries that use post-data descriptor trailers.
/// This value is <see langword="null"/> for non-ZIP formats.
/// </summary>
public int? ZipDataDescriptorEntryCount { get; set; }
/// <summary>
/// For solid-capable archive formats, the number of solid compressed streams present.
/// This value is <see langword="null"/> for formats that do not support solid entries.
/// </summary>
public int? SolidStreamCount { get; set; }
/// <summary>
/// Creates a new archive information instance.
/// </summary>

View File

@@ -760,6 +760,57 @@ public class ArchiveFactoryTests : TestBase
Assert.Equal(startPosition, stream.Position);
}
[Theory]
[InlineData("Zip.none.datadescriptors.zip", true)]
[InlineData("Zip.deflate.zip", false)]
public void GetArchiveInformation_ZipDataDescriptorEntryCount(
string archiveName,
bool expectsDataDescriptorTrailers
)
{
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
var info = ArchiveFactory.GetArchiveInformation(stream);
Assert.NotNull(info);
Assert.Equal(ArchiveType.Zip, info.Type);
Assert.NotNull(info.ZipDataDescriptorEntryCount);
Assert.Equal(expectsDataDescriptorTrailers, info.ZipDataDescriptorEntryCount > 0);
}
[Theory]
[InlineData("7Zip.solid.7z", true)]
[InlineData("7Zip.nonsolid.7z", false)]
[InlineData("Rar.rar", false)]
public void GetArchiveInformation_SolidStreamCount(string archiveName, bool expectsSolidStreams)
{
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
var info = ArchiveFactory.GetArchiveInformation(stream);
Assert.NotNull(info);
Assert.NotNull(info.SolidStreamCount);
Assert.Equal(expectsSolidStreams, info.SolidStreamCount > 0);
}
[Theory]
[InlineData("Zip.none.datadescriptors.zip", true)]
[InlineData("7Zip.solid.7z", true)]
public async ValueTask GetArchiveInformationAsync_IncludesFormatSpecificDetails(
string archiveName,
bool expectsAnyFormatSpecificDetails
)
{
using var stream = File.OpenRead(GetTestArchivePath(archiveName));
var info = await ArchiveFactory.GetArchiveInformationAsync(stream);
Assert.NotNull(info);
var hasZipDetail = info.ZipDataDescriptorEntryCount > 0;
var hasSolidDetail = info.SolidStreamCount > 0;
Assert.Equal(expectsAnyFormatSpecificDetails, hasZipDetail || hasSolidDetail);
}
private MemoryStream CreatePrefixedArchiveStream(string archiveName, int prefixLength)
{
var archiveBytes = File.ReadAllBytes(GetTestArchivePath(archiveName));