mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 06:54:40 +00:00
better zip streaming info
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Archives.SevenZip;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Ace.Headers;
|
||||
using SharpCompress.Common.Rar;
|
||||
@@ -389,7 +390,12 @@ public static partial class ArchiveFactory
|
||||
|
||||
var entryArray = entries.ToArray();
|
||||
var volumeArray = volumes.ToArray();
|
||||
var zip = GetZipInformation(entryArray);
|
||||
var (zipInformation, deferredSizeEntryCount) = await GetZipInformationAsync(
|
||||
archive.Type == ArchiveType.Zip,
|
||||
entryArray,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
var (isSolid, solidStreamCount) = await GetSolidInformationAsync(archive, entryArray)
|
||||
.ConfigureAwait(false);
|
||||
var isComplete = await archive.IsCompleteAsync().ConfigureAwait(false);
|
||||
@@ -411,7 +417,7 @@ public static partial class ArchiveFactory
|
||||
limitations,
|
||||
GetFormatVersion(volumeArray),
|
||||
entryArray.LongLength,
|
||||
zip?.DataDescriptorEntryCount ?? 0,
|
||||
deferredSizeEntryCount,
|
||||
physicalSize,
|
||||
isComplete ? GetCompressedPayloadSize(archive, entryArray) : null,
|
||||
isComplete && archive.Type != ArchiveType.GZip
|
||||
@@ -426,7 +432,7 @@ public static partial class ArchiveFactory
|
||||
volumeArray.Length,
|
||||
isComplete,
|
||||
GetArchiveComment(volumeArray),
|
||||
zip
|
||||
zipInformation
|
||||
);
|
||||
}
|
||||
|
||||
@@ -522,6 +528,40 @@ public static partial class ArchiveFactory
|
||||
_ => entries.Aggregate(0L, (total, entry) => total + entry.CompressedSize),
|
||||
};
|
||||
|
||||
private static async ValueTask<(
|
||||
ZipArchiveInformation? Information,
|
||||
long DeferredSizeEntryCount
|
||||
)> GetZipInformationAsync(
|
||||
bool isZipArchive,
|
||||
IEnumerable<IArchiveEntry> entries,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (!isZipArchive)
|
||||
{
|
||||
return (null, 0);
|
||||
}
|
||||
|
||||
long deferredSizeEntryCount = 0;
|
||||
foreach (var entry in entries.OfType<ZipArchiveEntry>())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var filePart = entry.Parts.OfType<SeekableZipFilePart>().Single();
|
||||
if (!filePart.HasDeferredSizes)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var localHeader = await filePart.GetRawLocalHeaderAsync().ConfigureAwait(false);
|
||||
if (localHeader.CompressedSize == 0 && localHeader.UncompressedSize == 0)
|
||||
{
|
||||
deferredSizeEntryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (new ZipArchiveInformation(deferredSizeEntryCount > 0), deferredSizeEntryCount);
|
||||
}
|
||||
|
||||
private static async ValueTask<ArchiveDetection?> RedetectArchiveAsync(
|
||||
Stream stream,
|
||||
long startPosition,
|
||||
|
||||
@@ -258,7 +258,10 @@ public static partial class ArchiveFactory
|
||||
{
|
||||
var entries = archive.Entries.ToArray();
|
||||
var volumes = archive.Volumes.ToArray();
|
||||
var zip = GetZipInformation(entries);
|
||||
var (zipInformation, deferredSizeEntryCount) = GetZipInformation(
|
||||
archive.Type == ArchiveType.Zip,
|
||||
entries
|
||||
);
|
||||
var (isSolid, solidStreamCount) = GetSolidInformation(archive, entries);
|
||||
var isMultiVolume = GetIsMultiVolume(archive.Type, volumes);
|
||||
var comment = GetArchiveComment(volumes);
|
||||
@@ -281,7 +284,7 @@ public static partial class ArchiveFactory
|
||||
limitations,
|
||||
GetFormatVersion(volumes),
|
||||
entries.LongLength,
|
||||
zip?.DataDescriptorEntryCount ?? 0,
|
||||
deferredSizeEntryCount,
|
||||
physicalSize,
|
||||
isComplete ? GetCompressedPayloadSize(archive, entries) : null,
|
||||
isComplete && archive.Type != ArchiveType.GZip
|
||||
@@ -296,7 +299,7 @@ public static partial class ArchiveFactory
|
||||
volumes.Length,
|
||||
isComplete,
|
||||
comment,
|
||||
zip
|
||||
zipInformation
|
||||
);
|
||||
}
|
||||
|
||||
@@ -384,23 +387,33 @@ public static partial class ArchiveFactory
|
||||
? ArchiveInformationStatus.Complete
|
||||
: ArchiveInformationStatus.Partial;
|
||||
|
||||
private static ZipArchiveInformation? GetZipInformation(IEnumerable<IArchiveEntry> entries)
|
||||
private static (
|
||||
ZipArchiveInformation? Information,
|
||||
long DeferredSizeEntryCount
|
||||
) GetZipInformation(bool isZipArchive, IEnumerable<IArchiveEntry> entries)
|
||||
{
|
||||
var dataDescriptorEntryCount = entries
|
||||
.OfType<ZipArchiveEntry>()
|
||||
.LongCount(entry =>
|
||||
entry
|
||||
.Parts.OfType<ZipFilePart>()
|
||||
.Any(part =>
|
||||
FlagUtility.HasFlag(
|
||||
part.Header.Flags,
|
||||
SharpCompress.Common.Zip.Headers.HeaderFlags.UsePostDataDescriptor
|
||||
)
|
||||
)
|
||||
);
|
||||
return dataDescriptorEntryCount == 0 && !entries.OfType<ZipArchiveEntry>().Any()
|
||||
? null
|
||||
: new ZipArchiveInformation(dataDescriptorEntryCount);
|
||||
if (!isZipArchive)
|
||||
{
|
||||
return (null, 0);
|
||||
}
|
||||
|
||||
long deferredSizeEntryCount = 0;
|
||||
foreach (var entry in entries.OfType<ZipArchiveEntry>())
|
||||
{
|
||||
var filePart = entry.Parts.OfType<SeekableZipFilePart>().Single();
|
||||
if (!filePart.HasDeferredSizes)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var localHeader = filePart.GetRawLocalHeader();
|
||||
if (localHeader.CompressedSize == 0 && localHeader.UncompressedSize == 0)
|
||||
{
|
||||
deferredSizeEntryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (new ZipArchiveInformation(deferredSizeEntryCount > 0), deferredSizeEntryCount);
|
||||
}
|
||||
|
||||
private static (bool IsSolid, long SolidStreamCount) GetSolidInformation(
|
||||
@@ -563,7 +576,7 @@ public static partial class ArchiveFactory
|
||||
private readonly ArchiveDetection detection;
|
||||
private long? compressedPayloadSize = 0;
|
||||
private long? uncompressedPayloadSize = 0;
|
||||
private long dataDescriptorEntryCount;
|
||||
private long deferredSizeEntryCount;
|
||||
private long entriesWithUnknownSizeCount;
|
||||
private bool isEncrypted;
|
||||
private ArchiveInformationLimitations limitations;
|
||||
@@ -595,15 +608,15 @@ public static partial class ArchiveFactory
|
||||
EntryCount++;
|
||||
isEncrypted |= entry.IsEncrypted;
|
||||
|
||||
var usesDataDescriptor = UsesZipDataDescriptor(entry);
|
||||
if (usesDataDescriptor)
|
||||
var hasDeferredSizes = HasDeferredSizes(entry);
|
||||
if (hasDeferredSizes)
|
||||
{
|
||||
dataDescriptorEntryCount++;
|
||||
deferredSizeEntryCount++;
|
||||
}
|
||||
|
||||
if (
|
||||
detection.ContainerType == ArchiveType.Lzw
|
||||
|| usesDataDescriptor
|
||||
|| hasDeferredSizes
|
||||
|| !TryGetSize(entry, out var size)
|
||||
)
|
||||
{
|
||||
@@ -650,7 +663,7 @@ public static partial class ArchiveFactory
|
||||
true,
|
||||
null,
|
||||
detection.ContainerType == ArchiveType.Zip
|
||||
? new ZipArchiveInformation(dataDescriptorEntryCount)
|
||||
? new ZipArchiveInformation(deferredSizeEntryCount > 0)
|
||||
: null
|
||||
);
|
||||
|
||||
@@ -668,7 +681,7 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
}
|
||||
|
||||
private static bool UsesZipDataDescriptor(IEntry entry) =>
|
||||
private static bool HasDeferredSizes(IEntry entry) =>
|
||||
entry is ZipEntry zipEntry
|
||||
&& zipEntry
|
||||
.Parts.OfType<ZipFilePart>()
|
||||
@@ -677,6 +690,8 @@ public static partial class ArchiveFactory
|
||||
part.Header.Flags,
|
||||
SharpCompress.Common.Zip.Headers.HeaderFlags.UsePostDataDescriptor
|
||||
)
|
||||
&& part.Header.CompressedSize == 0
|
||||
&& part.Header.UncompressedSize == 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ namespace SharpCompress.Archives;
|
||||
/// </summary>
|
||||
public sealed class ZipArchiveInformation
|
||||
{
|
||||
internal ZipArchiveInformation(long dataDescriptorEntryCount) =>
|
||||
DataDescriptorEntryCount = dataDescriptorEntryCount;
|
||||
internal ZipArchiveInformation(bool hasEntriesWithDeferredSizes) =>
|
||||
HasEntriesWithDeferredSizes = hasEntriesWithDeferredSizes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of entries whose local header defers its CRC and sizes to a data descriptor.
|
||||
/// Gets whether any entry sizes are available only after reading the entry data.
|
||||
/// When <see langword="true"/>, a forward-only reader can initially report zero sizes for those entries.
|
||||
/// </summary>
|
||||
public long DataDescriptorEntryCount { get; }
|
||||
public bool HasEntriesWithDeferredSizes { get; }
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ internal partial class SeekableZipFilePart
|
||||
[Zomp.SyncMethodGenerator.CreateSyncVersion]
|
||||
private async ValueTask LoadLocalHeaderAsync(CancellationToken cancellationToken = default) =>
|
||||
Header = await _headerFactory
|
||||
.GetLocalHeaderAsync(BaseStream, (DirectoryEntryHeader)Header)
|
||||
.GetLocalHeaderAsync(BaseStream, _directoryEntryHeader)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
internal ValueTask<LocalEntryHeader> GetRawLocalHeaderAsync() =>
|
||||
_headerFactory.GetRawLocalHeaderAsync(BaseStream, _directoryEntryHeader);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ internal partial class SeekableZipFilePart : ZipFilePart
|
||||
{
|
||||
private bool _isLocalHeaderLoaded;
|
||||
private readonly SeekableZipHeaderFactory _headerFactory;
|
||||
private readonly DirectoryEntryHeader _directoryEntryHeader;
|
||||
|
||||
internal SeekableZipFilePart(
|
||||
SeekableZipHeaderFactory headerFactory,
|
||||
@@ -15,7 +16,17 @@ internal partial class SeekableZipFilePart : ZipFilePart
|
||||
Stream stream,
|
||||
CompressionProviderRegistry compressionProviders
|
||||
)
|
||||
: base(header, stream, compressionProviders) => _headerFactory = headerFactory;
|
||||
: base(header, stream, compressionProviders)
|
||||
{
|
||||
_headerFactory = headerFactory;
|
||||
_directoryEntryHeader = header;
|
||||
}
|
||||
|
||||
internal LocalEntryHeader GetRawLocalHeader() =>
|
||||
_headerFactory.GetRawLocalHeader(BaseStream, _directoryEntryHeader);
|
||||
|
||||
internal bool HasDeferredSizes =>
|
||||
FlagUtility.HasFlag(_directoryEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor);
|
||||
|
||||
protected override Stream CreateBaseStream()
|
||||
{
|
||||
|
||||
@@ -146,17 +146,27 @@ internal sealed partial class SeekableZipHeaderFactory
|
||||
throw new ArchiveOperationException();
|
||||
}
|
||||
|
||||
// populate fields only known from the DirectoryEntryHeader
|
||||
localEntryHeader.HasData = directoryEntryHeader.HasData;
|
||||
localEntryHeader.ExternalFileAttributes = directoryEntryHeader.ExternalFileAttributes;
|
||||
localEntryHeader.Comment = directoryEntryHeader.Comment;
|
||||
|
||||
if (FlagUtility.HasFlag(localEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor))
|
||||
{
|
||||
localEntryHeader.Crc = directoryEntryHeader.Crc;
|
||||
localEntryHeader.CompressedSize = directoryEntryHeader.CompressedSize;
|
||||
localEntryHeader.UncompressedSize = directoryEntryHeader.UncompressedSize;
|
||||
}
|
||||
PopulateDirectoryEntryMetadata(localEntryHeader, directoryEntryHeader);
|
||||
return localEntryHeader;
|
||||
}
|
||||
|
||||
internal async ValueTask<LocalEntryHeader> GetRawLocalHeaderAsync(
|
||||
Stream stream,
|
||||
DirectoryEntryHeader directoryEntryHeader
|
||||
)
|
||||
{
|
||||
stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin);
|
||||
#if NET8_0_OR_GREATER
|
||||
await using var reader = new AsyncBinaryReader(stream, leaveOpen: true);
|
||||
#else
|
||||
using var reader = new AsyncBinaryReader(stream, leaveOpen: true);
|
||||
#endif
|
||||
var signature = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
if (signature != ENTRY_HEADER_BYTES)
|
||||
{
|
||||
throw new ArchiveOperationException();
|
||||
}
|
||||
|
||||
return await ReadLocalHeader(reader).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,31 @@ internal sealed partial class SeekableZipHeaderFactory : ZipHeaderFactory
|
||||
throw new ArchiveOperationException();
|
||||
}
|
||||
|
||||
PopulateDirectoryEntryMetadata(localEntryHeader, directoryEntryHeader);
|
||||
return localEntryHeader;
|
||||
}
|
||||
|
||||
internal LocalEntryHeader GetRawLocalHeader(
|
||||
Stream stream,
|
||||
DirectoryEntryHeader directoryEntryHeader
|
||||
)
|
||||
{
|
||||
stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin);
|
||||
var reader = new BinaryReader(stream);
|
||||
var signature = reader.ReadUInt32();
|
||||
if (signature != ENTRY_HEADER_BYTES)
|
||||
{
|
||||
throw new ArchiveOperationException();
|
||||
}
|
||||
|
||||
return ReadLocalHeader(reader);
|
||||
}
|
||||
|
||||
private static void PopulateDirectoryEntryMetadata(
|
||||
LocalEntryHeader localEntryHeader,
|
||||
DirectoryEntryHeader directoryEntryHeader
|
||||
)
|
||||
{
|
||||
// populate fields only known from the DirectoryEntryHeader
|
||||
localEntryHeader.HasData = directoryEntryHeader.HasData;
|
||||
localEntryHeader.ExternalFileAttributes = directoryEntryHeader.ExternalFileAttributes;
|
||||
@@ -163,6 +188,5 @@ internal sealed partial class SeekableZipHeaderFactory : ZipHeaderFactory
|
||||
localEntryHeader.CompressedSize = directoryEntryHeader.CompressedSize;
|
||||
localEntryHeader.UncompressedSize = directoryEntryHeader.UncompressedSize;
|
||||
}
|
||||
return localEntryHeader;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ internal partial class ZipHeaderFactory
|
||||
{
|
||||
case ENTRY_HEADER_BYTES:
|
||||
{
|
||||
var entryHeader = new LocalEntryHeader(_archiveEncoding);
|
||||
await entryHeader.Read(reader).ConfigureAwait(false);
|
||||
var entryHeader = await ReadLocalHeader(reader).ConfigureAwait(false);
|
||||
await LoadHeaderAsync(entryHeader, reader.BaseStream).ConfigureAwait(false);
|
||||
|
||||
_lastEntryHeader = entryHeader;
|
||||
@@ -87,6 +86,13 @@ internal partial class ZipHeaderFactory
|
||||
}
|
||||
}
|
||||
|
||||
protected async ValueTask<LocalEntryHeader> ReadLocalHeader(AsyncBinaryReader reader)
|
||||
{
|
||||
var entryHeader = new LocalEntryHeader(_archiveEncoding);
|
||||
await entryHeader.Read(reader).ConfigureAwait(false);
|
||||
return entryHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads encryption metadata and stream positioning for a header using async reads where needed.
|
||||
/// </summary>
|
||||
|
||||
@@ -42,8 +42,7 @@ internal partial class ZipHeaderFactory
|
||||
{
|
||||
case ENTRY_HEADER_BYTES:
|
||||
{
|
||||
var entryHeader = new LocalEntryHeader(_archiveEncoding);
|
||||
entryHeader.Read(reader);
|
||||
var entryHeader = ReadLocalHeader(reader);
|
||||
LoadHeader(entryHeader, reader.BaseStream);
|
||||
|
||||
_lastEntryHeader = entryHeader;
|
||||
@@ -109,6 +108,13 @@ internal partial class ZipHeaderFactory
|
||||
}
|
||||
}
|
||||
|
||||
protected LocalEntryHeader ReadLocalHeader(BinaryReader reader)
|
||||
{
|
||||
var entryHeader = new LocalEntryHeader(_archiveEncoding);
|
||||
entryHeader.Read(reader);
|
||||
return entryHeader;
|
||||
}
|
||||
|
||||
internal static bool IsHeader(uint headerBytes)
|
||||
{
|
||||
switch (headerBytes)
|
||||
|
||||
Reference in New Issue
Block a user