mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 06:54:40 +00:00
Add format-specific ArchiveInformation details
This commit is contained in:
committed by
GitHub
parent
0d317bb466
commit
d301cb365f
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user