mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-23 15:34:34 +00:00
first commit of async reader
This commit is contained in:
88
src/SharpCompress/Common/AsyncBinaryReader.cs
Normal file
88
src/SharpCompress/Common/AsyncBinaryReader.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using Xunit;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user