first commit of async reader

This commit is contained in:
Adam Hathcock
2025-11-26 08:09:20 +00:00
parent 3bdaba46a9
commit fb76bd82f2
9 changed files with 134 additions and 60 deletions

View File

@@ -0,0 +1,88 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Common
{
public sealed class AsyncBinaryReader(Stream stream, bool leaveOpen = false) : IDisposable
{
private readonly Stream _stream = stream ?? throw new ArgumentNullException(nameof(stream));
private readonly byte[] _buffer = new byte[8];
private bool _disposed;
public Stream BaseStream => _stream;
public async ValueTask<byte> ReadByteAsync(CancellationToken ct = default)
{
await ReadExactAsync(_buffer, 0, 1, ct).ConfigureAwait(false);
return _buffer[0];
}
public async ValueTask<ushort> ReadUInt16Async(CancellationToken ct = default)
{
await ReadExactAsync(_buffer, 0, 2, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadUInt16LittleEndian(_buffer);
}
public async ValueTask<uint> ReadUInt32Async(CancellationToken ct = default)
{
await ReadExactAsync(_buffer, 0, 4, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadUInt32LittleEndian(_buffer);
}
public async ValueTask<ulong> ReadUInt64Async(CancellationToken ct = default)
{
await ReadExactAsync(_buffer, 0, 8, ct).ConfigureAwait(false);
return BinaryPrimitives.ReadUInt64LittleEndian(_buffer);
}
public async ValueTask<byte[]> ReadBytesAsync(int count, CancellationToken ct = default)
{
var result = new byte[count];
await ReadExactAsync(result, 0, count, ct).ConfigureAwait(false);
return result;
}
private async ValueTask ReadExactAsync(byte[] destination, int offset, int length, CancellationToken ct)
{
var read = 0;
while (read < length)
{
var n = await _stream.ReadAsync(destination, offset + read, length - read, ct).ConfigureAwait(false);
if (n == 0)
{
throw new EndOfStreamException();
}
read += n;
}
}
public void Dispose()
{
if (_disposed || leaveOpen)
{
_disposed = true;
return;
}
_disposed = true;
_stream.Dispose();
}
#if NET6_0_OR_GREATER
public ValueTask DisposeAsync()
{
if (_disposed || leaveOpen)
{
_disposed = true;
return default;
}
_disposed = true;
return _stream.DisposeAsync();
}
#endif
}
}

View File

@@ -7,7 +7,7 @@ internal class DirectoryEndHeader : ZipHeader
public DirectoryEndHeader()
: base(ZipHeaderType.DirectoryEnd) { }
internal override void Read(BinaryReader reader)
internal override void Read(AsyncBinaryReader reader)
{
VolumeNumber = reader.ReadUInt16();
FirstVolumeWithDirectory = reader.ReadUInt16();

View File

@@ -1,27 +1,25 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
internal class LocalEntryHeader : ZipFileEntry
internal class LocalEntryHeader(ArchiveEncoding archiveEncoding) : ZipFileEntry(ZipHeaderType.LocalEntry, archiveEncoding)
{
public LocalEntryHeader(ArchiveEncoding archiveEncoding)
: base(ZipHeaderType.LocalEntry, archiveEncoding) { }
internal override void Read(BinaryReader reader)
internal override async ValueTask Read(AsyncBinaryReader reader)
{
Version = reader.ReadUInt16();
Flags = (HeaderFlags)reader.ReadUInt16();
CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16();
OriginalLastModifiedTime = LastModifiedTime = reader.ReadUInt16();
OriginalLastModifiedDate = LastModifiedDate = reader.ReadUInt16();
Crc = reader.ReadUInt32();
CompressedSize = reader.ReadUInt32();
UncompressedSize = reader.ReadUInt32();
var nameLength = reader.ReadUInt16();
var extraLength = reader.ReadUInt16();
var name = reader.ReadBytes(nameLength);
var extra = reader.ReadBytes(extraLength);
Version = await reader.ReadUInt16Async();
Flags = (HeaderFlags)await reader.ReadUInt16Async();
CompressionMethod = (ZipCompressionMethod)await reader.ReadUInt16Async();
OriginalLastModifiedTime = LastModifiedTime = await reader.ReadUInt16Async();
OriginalLastModifiedDate = LastModifiedDate = await reader.ReadUInt16Async();
Crc = await reader.ReadUInt32Async();
CompressedSize = await reader.ReadUInt32Async();
UncompressedSize = await reader.ReadUInt32Async();
var nameLength = await reader.ReadUInt16Async();
var extraLength = await reader.ReadUInt16Async();
var name = await reader.ReadBytesAsync(nameLength);
var extra = await reader.ReadBytesAsync(extraLength);
// According to .ZIP File Format Specification
//

View File

@@ -1,17 +1,15 @@
using System.IO;
using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
internal class Zip64DirectoryEndLocatorHeader : ZipHeader
internal class Zip64DirectoryEndLocatorHeader() : ZipHeader(ZipHeaderType.Zip64DirectoryEndLocator)
{
public Zip64DirectoryEndLocatorHeader()
: base(ZipHeaderType.Zip64DirectoryEndLocator) { }
internal override void Read(BinaryReader reader)
internal override async ValueTask Read(AsyncBinaryReader reader)
{
FirstVolumeWithDirectory = reader.ReadUInt32();
RelativeOffsetOfTheEndOfDirectoryRecord = (long)reader.ReadUInt64();
TotalNumberOfVolumes = reader.ReadUInt32();
FirstVolumeWithDirectory = await reader.ReadUInt32Async();
RelativeOffsetOfTheEndOfDirectoryRecord = (long)await reader.ReadUInt64Async();
TotalNumberOfVolumes = await reader.ReadUInt32Async();
}
public uint FirstVolumeWithDirectory { get; private set; }

View File

@@ -5,15 +5,8 @@ using System.IO;
namespace SharpCompress.Common.Zip.Headers;
internal abstract class ZipFileEntry : ZipHeader
internal abstract class ZipFileEntry(ZipHeaderType type, ArchiveEncoding archiveEncoding) : ZipHeader(type)
{
protected ZipFileEntry(ZipHeaderType type, ArchiveEncoding archiveEncoding)
: base(type)
{
Extra = new List<ExtraData>();
ArchiveEncoding = archiveEncoding;
}
internal bool IsDirectory
{
get
@@ -30,7 +23,7 @@ internal abstract class ZipFileEntry : ZipHeader
internal Stream? PackedStream { get; set; }
internal ArchiveEncoding ArchiveEncoding { get; }
internal ArchiveEncoding ArchiveEncoding { get; } = archiveEncoding;
internal string? Name { get; set; }
@@ -44,7 +37,7 @@ internal abstract class ZipFileEntry : ZipHeader
internal long UncompressedSize { get; set; }
internal List<ExtraData> Extra { get; set; }
internal List<ExtraData> Extra { get; set; } = new();
public string? Password { get; set; }

View File

@@ -1,18 +1,12 @@
using System.IO;
using System.Threading.Tasks;
namespace SharpCompress.Common.Zip.Headers;
internal abstract class ZipHeader
internal abstract class ZipHeader(ZipHeaderType type)
{
protected ZipHeader(ZipHeaderType type)
{
ZipHeaderType = type;
HasData = true;
}
internal ZipHeaderType ZipHeaderType { get; } = type;
internal ZipHeaderType ZipHeaderType { get; }
internal abstract ValueTask Read(AsyncBinaryReader reader);
internal abstract void Read(BinaryReader reader);
internal bool HasData { get; set; }
internal bool HasData { get; set; } = true;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -18,11 +19,11 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
internal SeekableZipHeaderFactory(string? password, ArchiveEncoding archiveEncoding)
: base(StreamingMode.Seekable, password, archiveEncoding) { }
internal IEnumerable<ZipHeader> ReadSeekableHeader(Stream stream)
internal async IAsyncEnumerable<ZipHeader> ReadSeekableHeader(Stream stream)
{
var reader = new BinaryReader(stream);
var reader = new AsyncBinaryReader(stream);
SeekBackToHeader(stream, reader);
await SeekBackToHeader(stream, reader);
var eocd_location = stream.Position;
var entry = new DirectoryEndHeader();
@@ -34,24 +35,24 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
// ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD
stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin);
var zip64_locator = reader.ReadUInt32();
int zip64_locator = await reader.ReadUInt16Async();
if (zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR)
{
throw new ArchiveException("Failed to locate the Zip64 Directory Locator");
}
var zip64Locator = new Zip64DirectoryEndLocatorHeader();
zip64Locator.Read(reader);
await zip64Locator.Read(reader);
stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin);
var zip64Signature = reader.ReadUInt32();
var zip64Signature = await reader.ReadUInt32Async();
if (zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY)
{
throw new ArchiveException("Failed to locate the Zip64 Header");
}
var zip64Entry = new Zip64DirectoryEndHeader();
zip64Entry.Read(reader);
await zip64Entry.Read(reader);
stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
}
else
@@ -63,8 +64,8 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
while (true)
{
stream.Position = position;
var signature = reader.ReadUInt32();
var nextHeader = ReadHeader(signature, reader, _zip64);
var signature = await reader.ReadUInt32Async();
var nextHeader = await ReadHeader(signature, reader, _zip64);
position = stream.Position;
if (nextHeader is null)
@@ -98,7 +99,7 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
return true;
}
private static void SeekBackToHeader(Stream stream, BinaryReader reader)
private static async ValueTask SeekBackToHeader(Stream stream, AsyncBinaryReader reader)
{
// Minimum EOCD length
if (stream.Length < MINIMUM_EOCD_LENGTH)
@@ -117,7 +118,7 @@ internal sealed class SeekableZipHeaderFactory : ZipHeaderFactory
stream.Seek(-len, SeekOrigin.End);
var seek = reader.ReadBytes(len);
var seek = await reader.ReadBytesAsync(len);
// Search in reverse
Array.Reverse(seek);

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -34,14 +35,14 @@ internal class ZipHeaderFactory
_archiveEncoding = archiveEncoding;
}
protected ZipHeader? ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false)
protected async ValueTask<ZipHeader?> ReadHeader(uint headerBytes, AsyncBinaryReader reader, bool zip64 = false)
{
switch (headerBytes)
{
case ENTRY_HEADER_BYTES:
{
var entryHeader = new LocalEntryHeader(_archiveEncoding);
entryHeader.Read(reader);
await entryHeader.Read(reader);
LoadHeader(entryHeader, reader.BaseStream);
_lastEntryHeader = entryHeader;

View File

@@ -1,3 +1,4 @@
using System;
using SharpCompress.Common;
using Xunit;