move more and fmt

This commit is contained in:
Adam Hathcock
2026-01-22 14:05:23 +00:00
parent 61c6f8403a
commit d1f6fd9af1
33 changed files with 763 additions and 680 deletions

View File

@@ -17,7 +17,6 @@ public abstract partial class AbstractArchive<TEntry, TVolume>
public IAsyncEnumerable<TVolume> VolumesAsync => _lazyVolumesAsync;
protected virtual async IAsyncEnumerable<TEntry> LoadEntriesAsync(
IAsyncEnumerable<TVolume> volumes
)

View File

@@ -81,7 +81,6 @@ public abstract partial class AbstractArchive<TEntry, TVolume> : IArchive, IAsyn
protected virtual IAsyncEnumerable<TVolume> LoadVolumesAsync(SourceStream sourceStream) =>
LoadVolumes(sourceStream).ToAsyncEnumerable();
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
IEnumerable<IVolume> IArchive.Volumes => _lazyVolumes.Cast<IVolume>();

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.IO;
namespace SharpCompress.Archives;
public static partial class ArchiveFactory
{
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
readerOptions ??= new ReaderOptions();
stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize);
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken);
return factory.OpenAsyncArchive(stream, readerOptions);
}
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
string filePath,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken);
return factory.OpenAsyncArchive(fileInfo, options);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
fileInfos.NotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
{
throw new InvalidOperationException("No files to open");
}
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
{
return await OpenAsyncArchive(fileInfo, options, cancellationToken);
}
fileInfo.NotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
var factory = await FindFactoryAsync<IMultiArchiveFactory>(fileInfo, cancellationToken);
return factory.OpenAsyncArchive(filesArray, options, cancellationToken);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
IEnumerable<Stream> streams,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
streams.NotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
{
throw new InvalidOperationException("No streams");
}
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
{
return await OpenAsyncArchive(firstStream, options, cancellationToken);
}
firstStream.NotNull(nameof(firstStream));
options ??= new ReaderOptions();
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken);
return factory.OpenAsyncArchive(streamsArray, options);
}
public static ValueTask<T> FindFactoryAsync<T>(
string path,
CancellationToken cancellationToken = default
)
where T : IFactory
{
path.NotNullOrEmpty(nameof(path));
return FindFactoryAsync<T>(new FileInfo(path), cancellationToken);
}
private static async ValueTask<T> FindFactoryAsync<T>(
FileInfo finfo,
CancellationToken cancellationToken
)
where T : IFactory
{
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken);
}
private static async ValueTask<T> FindFactoryAsync<T>(
Stream stream,
CancellationToken cancellationToken
)
where T : IFactory
{
stream.NotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken))
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new InvalidOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
}

View File

@@ -11,7 +11,7 @@ using SharpCompress.Readers;
namespace SharpCompress.Archives;
public static class ArchiveFactory
public static partial class ArchiveFactory
{
public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
{
@@ -20,18 +20,6 @@ public static class ArchiveFactory
return FindFactory<IArchiveFactory>(stream).OpenArchive(stream, readerOptions);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
readerOptions ??= new ReaderOptions();
stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize);
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken);
return factory.OpenAsyncArchive(stream, readerOptions);
}
public static IWritableArchive CreateArchive(ArchiveType type)
{
var factory = Factory
@@ -52,16 +40,6 @@ public static class ArchiveFactory
return OpenArchive(new FileInfo(filePath), options);
}
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
string filePath,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken);
}
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
@@ -69,18 +47,6 @@ public static class ArchiveFactory
return FindFactory<IArchiveFactory>(fileInfo).OpenArchive(fileInfo, options);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken);
return factory.OpenAsyncArchive(fileInfo, options);
}
public static IArchive OpenArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? options = null
@@ -105,32 +71,6 @@ public static class ArchiveFactory
return FindFactory<IMultiArchiveFactory>(fileInfo).OpenArchive(filesArray, options);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
fileInfos.NotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
{
throw new InvalidOperationException("No files to open");
}
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
{
return await OpenAsyncArchive(fileInfo, options, cancellationToken);
}
fileInfo.NotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
var factory = await FindFactoryAsync<IMultiArchiveFactory>(fileInfo, cancellationToken);
return factory.OpenAsyncArchive(filesArray, options, cancellationToken);
}
public static IArchive OpenArchive(IEnumerable<Stream> streams, ReaderOptions? options = null)
{
streams.NotNull(nameof(streams));
@@ -152,33 +92,6 @@ public static class ArchiveFactory
return FindFactory<IMultiArchiveFactory>(firstStream).OpenArchive(streamsArray, options);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
IEnumerable<Stream> streams,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
streams.NotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
{
throw new InvalidOperationException("No streams");
}
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
{
return await OpenAsyncArchive(firstStream, options, cancellationToken);
}
firstStream.NotNull(nameof(firstStream));
options ??= new ReaderOptions();
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken);
return factory.OpenAsyncArchive(streamsArray, options);
}
public static void WriteToDirectory(
string sourceArchive,
string destinationDirectory,
@@ -197,16 +110,6 @@ public static class ArchiveFactory
return FindFactory<T>(stream);
}
public static ValueTask<T> FindFactoryAsync<T>(
string path,
CancellationToken cancellationToken = default
)
where T : IFactory
{
path.NotNullOrEmpty(nameof(path));
return FindFactoryAsync<T>(new FileInfo(path), cancellationToken);
}
public static T FindFactory<T>(FileInfo finfo)
where T : IFactory
{
@@ -247,51 +150,7 @@ public static class ArchiveFactory
);
}
private static async ValueTask<T> FindFactoryAsync<T>(
FileInfo finfo,
CancellationToken cancellationToken
)
where T : IFactory
{
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken);
}
private static async ValueTask<T> FindFactoryAsync<T>(
Stream stream,
CancellationToken cancellationToken
)
where T : IFactory
{
stream.NotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken))
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new InvalidOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
// Async methods moved to ArchiveFactory.Async.cs
public static bool IsArchive(
string filePath,

View File

@@ -88,7 +88,6 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
);
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;

View File

@@ -73,7 +73,6 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
}
}
private void LoadFactory(Stream stream)
{
if (_database is null)

View File

@@ -88,7 +88,6 @@ public partial class TarArchive
return new((IAsyncReader)TarReader.OpenReader(stream));
}
protected override async IAsyncEnumerable<TarArchiveEntry> LoadEntriesAsync(
IAsyncEnumerable<TarVolume> volumes
)

View File

@@ -91,7 +91,6 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
}
}
protected override TarArchiveEntry CreateEntryInternal(
string filePath,
Stream source,

View File

@@ -16,7 +16,6 @@ namespace SharpCompress.Archives.Zip;
public partial class ZipArchive
{
protected override async IAsyncEnumerable<ZipArchiveEntry> LoadEntriesAsync(
IAsyncEnumerable<ZipVolume> volumes
)
@@ -70,7 +69,6 @@ public partial class ZipArchive
}
}
protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.IO;
namespace SharpCompress.Common.GZip;
public partial class GZipEntry
{
internal static async IAsyncEnumerable<GZipEntry> GetEntriesAsync(
Stream stream,
OptionsBase options
)
{
yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding));
}
}

View File

@@ -4,7 +4,7 @@ using System.IO;
namespace SharpCompress.Common.GZip;
public class GZipEntry : Entry
public partial class GZipEntry : Entry
{
private readonly GZipFilePart? _filePart;
@@ -43,11 +43,5 @@ public class GZipEntry : Entry
yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding));
}
internal static async IAsyncEnumerable<GZipEntry> GetEntriesAsync(
Stream stream,
OptionsBase options
)
{
yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding));
}
// Async methods moved to GZipEntry.Async.cs
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.IO;
namespace SharpCompress.Common.Tar;
public partial class TarEntry
{
internal static async IAsyncEnumerable<TarEntry> GetEntriesAsync(
StreamingMode mode,
Stream stream,
CompressionType compressionType,
IArchiveEncoding archiveEncoding
)
{
await foreach (
var header in TarHeaderFactory.ReadHeaderAsync(mode, stream, archiveEncoding)
)
{
if (header != null)
{
if (mode == StreamingMode.Seekable)
{
yield return new TarEntry(new TarFilePart(header, stream), compressionType);
}
else
{
yield return new TarEntry(new TarFilePart(header, null), compressionType);
}
}
else
{
throw new IncompleteArchiveException("Unexpected EOF reading tar file");
}
}
}
}

View File

@@ -6,7 +6,7 @@ using SharpCompress.IO;
namespace SharpCompress.Common.Tar;
public class TarEntry : Entry
public partial class TarEntry : Entry
{
private readonly TarFilePart? _filePart;
@@ -77,32 +77,5 @@ public class TarEntry : Entry
}
}
internal static async IAsyncEnumerable<TarEntry> GetEntriesAsync(
StreamingMode mode,
Stream stream,
CompressionType compressionType,
IArchiveEncoding archiveEncoding
)
{
await foreach (
var header in TarHeaderFactory.ReadHeaderAsync(mode, stream, archiveEncoding)
)
{
if (header != null)
{
if (mode == StreamingMode.Seekable)
{
yield return new TarEntry(new TarFilePart(header, stream), compressionType);
}
else
{
yield return new TarEntry(new TarFilePart(header, null), compressionType);
}
}
else
{
throw new IncompleteArchiveException("Unexpected EOF reading tar file");
}
}
}
// Async methods moved to TarEntry.Async.cs
}

View File

@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.IO;
namespace SharpCompress.Common.Tar;
internal static partial class TarHeaderFactory
{
internal static async IAsyncEnumerable<TarHeader?> ReadHeaderAsync(
StreamingMode mode,
Stream stream,
IArchiveEncoding archiveEncoding
)
{
while (true)
{
TarHeader? header = null;
try
{
var reader = new AsyncBinaryReader(stream, false);
header = new TarHeader(archiveEncoding);
if (!await header.ReadAsync(reader))
{
yield break;
}
switch (mode)
{
case StreamingMode.Seekable:
{
header.DataStartPosition = stream.Position;
//skip to nearest 512
stream.Position += PadTo512(header.Size);
}
break;
case StreamingMode.Streaming:
{
header.PackedStream = new TarReadOnlySubStream(stream, header.Size);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
catch
{
header = null;
}
yield return header;
}
}
}

View File

@@ -5,7 +5,7 @@ using SharpCompress.IO;
namespace SharpCompress.Common.Tar;
internal static class TarHeaderFactory
internal static partial class TarHeaderFactory
{
internal static IEnumerable<TarHeader?> ReadHeader(
StreamingMode mode,
@@ -54,51 +54,7 @@ internal static class TarHeaderFactory
}
}
internal static async IAsyncEnumerable<TarHeader?> ReadHeaderAsync(
StreamingMode mode,
Stream stream,
IArchiveEncoding archiveEncoding
)
{
while (true)
{
TarHeader? header = null;
try
{
var reader = new AsyncBinaryReader(stream, false);
header = new TarHeader(archiveEncoding);
if (!await header.ReadAsync(reader))
{
yield break;
}
switch (mode)
{
case StreamingMode.Seekable:
{
header.DataStartPosition = stream.Position;
//skip to nearest 512
stream.Position += PadTo512(header.Size);
}
break;
case StreamingMode.Streaming:
{
header.PackedStream = new TarReadOnlySubStream(stream, header.Size);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
catch
{
header = null;
}
yield return header;
}
}
// Async methods moved to TarHeaderFactory.Async.cs
private static long PadTo512(long size)
{

View File

@@ -75,6 +75,7 @@ internal sealed partial class SeekableZipHeaderFactory
}
}
}
private static async ValueTask SeekBackToHeaderAsync(Stream stream, AsyncBinaryReader reader)
{
// Minimum EOCD length

View File

@@ -22,7 +22,6 @@ internal sealed partial class SeekableZipHeaderFactory : ZipHeaderFactory
internal SeekableZipHeaderFactory(string? password, IArchiveEncoding archiveEncoding)
: base(StreamingMode.Seekable, password, archiveEncoding) { }
internal IEnumerable<ZipHeader> ReadSeekableHeader(Stream stream)
{
var reader = new BinaryReader(stream);

View File

@@ -0,0 +1,177 @@
using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Compressors.ADC;
public static partial class ADCBase
{
/// <summary>
/// Decompresses a byte buffer asynchronously that's compressed with ADC
/// </summary>
/// <param name="input">Compressed buffer</param>
/// <param name="bufferSize">Max size for decompressed data</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result containing bytes read and decompressed data</returns>
public static async ValueTask<AdcDecompressResult> DecompressAsync(
byte[] input,
int bufferSize = 262144,
CancellationToken cancellationToken = default
) => await DecompressAsync(new MemoryStream(input), bufferSize, cancellationToken);
/// <summary>
/// Decompresses a stream asynchronously that's compressed with ADC
/// </summary>
/// <param name="input">Stream containing compressed data</param>
/// <param name="bufferSize">Max size for decompressed data</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result containing bytes read and decompressed data</returns>
public static async ValueTask<AdcDecompressResult> DecompressAsync(
Stream input,
int bufferSize = 262144,
CancellationToken cancellationToken = default
)
{
var result = new AdcDecompressResult();
if (input is null || input.Length == 0)
{
result.BytesRead = 0;
result.Output = null;
return result;
}
var start = (int)input.Position;
var position = (int)input.Position;
int chunkSize;
int offset;
int chunkType;
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
var outPosition = 0;
var full = false;
byte[] temp = ArrayPool<byte>.Shared.Rent(3);
try
{
while (position < input.Length)
{
cancellationToken.ThrowIfCancellationRequested();
var readByte = input.ReadByte();
if (readByte == -1)
{
break;
}
chunkType = GetChunkType((byte)readByte);
switch (chunkType)
{
case PLAIN:
chunkSize = GetChunkSize((byte)readByte);
if (outPosition + chunkSize > bufferSize)
{
full = true;
break;
}
var readCount = await input.ReadAsync(
buffer,
outPosition,
chunkSize,
cancellationToken
);
outPosition += readCount;
position += readCount + 1;
break;
case TWO_BYTE:
chunkSize = GetChunkSize((byte)readByte);
temp[0] = (byte)readByte;
temp[1] = (byte)input.ReadByte();
offset = GetOffset(temp.AsSpan(0, 2));
if (outPosition + chunkSize > bufferSize)
{
full = true;
break;
}
if (offset == 0)
{
var lastByte = buffer[outPosition - 1];
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = lastByte;
outPosition++;
}
position += 2;
}
else
{
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = buffer[outPosition - offset - 1];
outPosition++;
}
position += 2;
}
break;
case THREE_BYTE:
chunkSize = GetChunkSize((byte)readByte);
temp[0] = (byte)readByte;
temp[1] = (byte)input.ReadByte();
temp[2] = (byte)input.ReadByte();
offset = GetOffset(temp.AsSpan(0, 3));
if (outPosition + chunkSize > bufferSize)
{
full = true;
break;
}
if (offset == 0)
{
var lastByte = buffer[outPosition - 1];
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = lastByte;
outPosition++;
}
position += 3;
}
else
{
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = buffer[outPosition - offset - 1];
outPosition++;
}
position += 3;
}
break;
}
if (full)
{
break;
}
}
var output = new byte[outPosition];
Array.Copy(buffer, output, outPosition);
result.BytesRead = position - start;
result.Output = output;
return result;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
ArrayPool<byte>.Shared.Return(temp);
}
}
}

View File

@@ -50,7 +50,7 @@ public class AdcDecompressResult
/// <summary>
/// Provides static methods for decompressing Apple Data Compression data
/// </summary>
public static class ADCBase
public static partial class ADCBase
{
private const int PLAIN = 1;
private const int TWO_BYTE = 2;
@@ -97,172 +97,7 @@ public static class ADCBase
public static int Decompress(byte[] input, out byte[]? output, int bufferSize = 262144) =>
Decompress(new MemoryStream(input), out output, bufferSize);
/// <summary>
/// Decompresses a byte buffer asynchronously that's compressed with ADC
/// </summary>
/// <param name="input">Compressed buffer</param>
/// <param name="bufferSize">Max size for decompressed data</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result containing bytes read and decompressed data</returns>
public static async ValueTask<AdcDecompressResult> DecompressAsync(
byte[] input,
int bufferSize = 262144,
CancellationToken cancellationToken = default
) => await DecompressAsync(new MemoryStream(input), bufferSize, cancellationToken);
/// <summary>
/// Decompresses a stream asynchronously that's compressed with ADC
/// </summary>
/// <param name="input">Stream containing compressed data</param>
/// <param name="bufferSize">Max size for decompressed data</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Result containing bytes read and decompressed data</returns>
public static async ValueTask<AdcDecompressResult> DecompressAsync(
Stream input,
int bufferSize = 262144,
CancellationToken cancellationToken = default
)
{
var result = new AdcDecompressResult();
if (input is null || input.Length == 0)
{
result.BytesRead = 0;
result.Output = null;
return result;
}
var start = (int)input.Position;
var position = (int)input.Position;
int chunkSize;
int offset;
int chunkType;
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
var outPosition = 0;
var full = false;
byte[] temp = ArrayPool<byte>.Shared.Rent(3);
try
{
while (position < input.Length)
{
cancellationToken.ThrowIfCancellationRequested();
var readByte = input.ReadByte();
if (readByte == -1)
{
break;
}
chunkType = GetChunkType((byte)readByte);
switch (chunkType)
{
case PLAIN:
chunkSize = GetChunkSize((byte)readByte);
if (outPosition + chunkSize > bufferSize)
{
full = true;
break;
}
var readCount = await input.ReadAsync(
buffer,
outPosition,
chunkSize,
cancellationToken
);
outPosition += readCount;
position += readCount + 1;
break;
case TWO_BYTE:
chunkSize = GetChunkSize((byte)readByte);
temp[0] = (byte)readByte;
temp[1] = (byte)input.ReadByte();
offset = GetOffset(temp.AsSpan(0, 2));
if (outPosition + chunkSize > bufferSize)
{
full = true;
break;
}
if (offset == 0)
{
var lastByte = buffer[outPosition - 1];
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = lastByte;
outPosition++;
}
position += 2;
}
else
{
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = buffer[outPosition - offset - 1];
outPosition++;
}
position += 2;
}
break;
case THREE_BYTE:
chunkSize = GetChunkSize((byte)readByte);
temp[0] = (byte)readByte;
temp[1] = (byte)input.ReadByte();
temp[2] = (byte)input.ReadByte();
offset = GetOffset(temp.AsSpan(0, 3));
if (outPosition + chunkSize > bufferSize)
{
full = true;
break;
}
if (offset == 0)
{
var lastByte = buffer[outPosition - 1];
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = lastByte;
outPosition++;
}
position += 3;
}
else
{
for (var i = 0; i < chunkSize; i++)
{
buffer[outPosition] = buffer[outPosition - offset - 1];
outPosition++;
}
position += 3;
}
break;
}
if (full)
{
break;
}
}
var output = new byte[outPosition];
Array.Copy(buffer, output, outPosition);
result.BytesRead = position - start;
result.Output = output;
return result;
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
ArrayPool<byte>.Shared.Return(temp);
}
}
// Async methods moved to ADCBase.Async.cs
/// <summary>
/// Decompresses a stream that's compressed with ADC

View File

@@ -8,6 +8,64 @@ namespace SharpCompress.Compressors.BZip2;
public sealed partial class BZip2Stream
{
/// <summary>
/// Create a BZip2Stream asynchronously
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <param name="compressionMode">Compression Mode</param>
/// <param name="decompressConcatenated">Decompress Concatenated</param>
/// <param name="cancellationToken">Cancellation Token</param>
public static async ValueTask<BZip2Stream> CreateAsync(
Stream stream,
CompressionMode compressionMode,
bool decompressConcatenated,
bool leaveOpen = false,
CancellationToken cancellationToken = default
)
{
var bZip2Stream = new BZip2Stream();
bZip2Stream.leaveOpen = leaveOpen;
#if DEBUG_STREAMS
bZip2Stream.DebugConstruct(typeof(BZip2Stream));
#endif
bZip2Stream.Mode = compressionMode;
if (bZip2Stream.Mode == CompressionMode.Compress)
{
bZip2Stream.stream = new CBZip2OutputStream(stream);
}
else
{
bZip2Stream.stream = await CBZip2InputStream.CreateAsync(
stream,
decompressConcatenated,
cancellationToken
);
}
return bZip2Stream;
}
/// <summary>
/// Asynchronously consumes two bytes to test if there is a BZip2 header
/// </summary>
/// <param name="stream"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async ValueTask<bool> IsBZip2Async(
Stream stream,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
var buffer = new byte[2];
var bytesRead = await stream.ReadAsync(buffer, 0, 2, cancellationToken);
if (bytesRead < 2 || buffer[0] != 'B' || buffer[1] != 'Z')
{
return false;
}
return true;
}
#if !LEGACY_DOTNET
public override async ValueTask<int> ReadAsync(
Memory<byte> buffer,

View File

@@ -69,43 +69,6 @@ public sealed partial class BZip2Stream : Stream, IStreamStack
return bZip2Stream;
}
/// <summary>
/// Create a BZip2Stream asynchronously
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <param name="compressionMode">Compression Mode</param>
/// <param name="decompressConcatenated">Decompress Concatenated</param>
/// <param name="cancellationToken">Cancellation Token</param>
public static async ValueTask<BZip2Stream> CreateAsync(
Stream stream,
CompressionMode compressionMode,
bool decompressConcatenated,
bool leaveOpen = false,
CancellationToken cancellationToken = default
)
{
var bZip2Stream = new BZip2Stream();
bZip2Stream.leaveOpen = leaveOpen;
#if DEBUG_STREAMS
bZip2Stream.DebugConstruct(typeof(BZip2Stream));
#endif
bZip2Stream.Mode = compressionMode;
if (bZip2Stream.Mode == CompressionMode.Compress)
{
bZip2Stream.stream = new CBZip2OutputStream(stream);
}
else
{
bZip2Stream.stream = await CBZip2InputStream.CreateAsync(
stream,
decompressConcatenated,
cancellationToken
);
}
return bZip2Stream;
}
public void Finish() => (stream as CBZip2OutputStream)?.Finish();
protected override void Dispose(bool disposing)
@@ -178,24 +141,5 @@ public sealed partial class BZip2Stream : Stream, IStreamStack
return true;
}
/// <summary>
/// Asynchronously consumes two bytes to test if there is a BZip2 header
/// </summary>
/// <param name="stream"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async ValueTask<bool> IsBZip2Async(
Stream stream,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
var buffer = new byte[2];
var bytesRead = await stream.ReadAsync(buffer, 0, 2, cancellationToken);
if (bytesRead < 2 || buffer[0] != 'B' || buffer[1] != 'Z')
{
return false;
}
return true;
}
// Async methods moved to BZip2Stream.Async.cs
}

View File

@@ -9,6 +9,57 @@ namespace SharpCompress.Compressors.LZMA;
public sealed partial class LZipStream
{
/// <summary>
/// Asynchronously determines if the given stream is positioned at the start of a v1 LZip
/// file, as indicated by the ASCII characters "LZIP" and a version byte
/// of 1, followed by at least one byte.
/// </summary>
/// <param name="stream">The stream to read from. Must not be null.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns><c>true</c> if the given stream is an LZip file, <c>false</c> otherwise.</returns>
public static async ValueTask<bool> IsLZipFileAsync(
Stream stream,
CancellationToken cancellationToken = default
) => await ValidateAndReadSizeAsync(stream, cancellationToken) != 0;
/// <summary>
/// Asynchronously reads the 6-byte header of the stream, and returns 0 if either the header
/// couldn't be read or it isn't a validate LZIP header, or the dictionary
/// size if it *is* a valid LZIP file.
/// </summary>
public static async ValueTask<int> ValidateAndReadSizeAsync(
Stream stream,
CancellationToken cancellationToken
)
{
// Read the header
byte[] header = new byte[6];
var n = await stream
.ReadAsync(header, 0, header.Length, cancellationToken)
.ConfigureAwait(false);
// TODO: Handle reading only part of the header?
if (n != 6)
{
return 0;
}
if (
header[0] != 'L'
|| header[1] != 'Z'
|| header[2] != 'I'
|| header[3] != 'P'
|| header[4] != 1 /* version 1 */
)
{
return 0;
}
var basePower = header[5] & 0x1F;
var subtractionNumerator = (header[5] & 0xE0) >> 5;
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
}
#if !LEGACY_DOTNET
/// <summary>
/// Asynchronously reads bytes from the current stream into a buffer.

View File

@@ -202,19 +202,6 @@ public sealed partial class LZipStream : Stream, IStreamStack
/// <returns><c>true</c> if the given stream is an LZip file, <c>false</c> otherwise.</returns>
public static bool IsLZipFile(Stream stream) => ValidateAndReadSize(stream) != 0;
/// <summary>
/// Asynchronously determines if the given stream is positioned at the start of a v1 LZip
/// file, as indicated by the ASCII characters "LZIP" and a version byte
/// of 1, followed by at least one byte.
/// </summary>
/// <param name="stream">The stream to read from. Must not be null.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns><c>true</c> if the given stream is an LZip file, <c>false</c> otherwise.</returns>
public static async ValueTask<bool> IsLZipFileAsync(
Stream stream,
CancellationToken cancellationToken = default
) => await ValidateAndReadSizeAsync(stream, cancellationToken) != 0;
/// <summary>
/// Reads the 6-byte header of the stream, and returns 0 if either the header
/// couldn't be read or it isn't a validate LZIP header, or the dictionary
@@ -248,43 +235,7 @@ public sealed partial class LZipStream : Stream, IStreamStack
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
}
/// <summary>
/// Asynchronously reads the 6-byte header of the stream, and returns 0 if either the header
/// couldn't be read or it isn't a validate LZIP header, or the dictionary
/// size if it *is* a valid LZIP file.
/// </summary>
public static async ValueTask<int> ValidateAndReadSizeAsync(
Stream stream,
CancellationToken cancellationToken
)
{
// Read the header
byte[] header = new byte[6];
var n = await stream
.ReadAsync(header, 0, header.Length, cancellationToken)
.ConfigureAwait(false);
// TODO: Handle reading only part of the header?
if (n != 6)
{
return 0;
}
if (
header[0] != 'L'
|| header[1] != 'Z'
|| header[2] != 'I'
|| header[3] != 'P'
|| header[4] != 1 /* version 1 */
)
{
return 0;
}
var basePower = header[5] & 0x1F;
var subtractionNumerator = (header[5] & 0xE0) >> 5;
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
}
// Async methods moved to LZipStream.Async.cs
private static readonly byte[] headerBytes =
[

View File

@@ -13,6 +13,16 @@ internal sealed partial class MultiVolumeReadOnlyAsyncStream
: MultiVolumeReadOnlyStreamBase,
IStreamStack
{
internal static async ValueTask<MultiVolumeReadOnlyAsyncStream> Create(
IAsyncEnumerable<RarFilePart> parts
)
{
var stream = new MultiVolumeReadOnlyAsyncStream(parts);
await stream.filePartEnumerator.MoveNextAsync();
stream.InitializeNextFilePart();
return stream;
}
#if NET8_0_OR_GREATER
public override async ValueTask DisposeAsync()
{

View File

@@ -44,15 +44,7 @@ internal sealed partial class MultiVolumeReadOnlyAsyncStream
filePartEnumerator = parts.GetAsyncEnumerator();
}
internal static async ValueTask<MultiVolumeReadOnlyAsyncStream> Create(
IAsyncEnumerable<RarFilePart> parts
)
{
var stream = new MultiVolumeReadOnlyAsyncStream(parts);
await stream.filePartEnumerator.MoveNextAsync();
stream.InitializeNextFilePart();
return stream;
}
// Async methods moved to MultiVolumeReadOnlyAsyncStream.Async.cs
private void InitializeNextFilePart()
{

View File

@@ -10,6 +10,18 @@ namespace SharpCompress.Compressors.Rar;
internal partial class RarBLAKE2spStream : RarStream, IStreamStack
{
public static async ValueTask<RarBLAKE2spStream> CreateAsync(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyAsyncStream readStream,
CancellationToken cancellationToken = default
)
{
var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream);
await stream.InitializeAsync(cancellationToken);
return stream;
}
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -134,17 +134,7 @@ internal partial class RarBLAKE2spStream : RarStream, IStreamStack
return stream;
}
public static async ValueTask<RarBLAKE2spStream> CreateAsync(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyAsyncStream readStream,
CancellationToken cancellationToken = default
)
{
var stream = new RarBLAKE2spStream(unpack, fileHeader, readStream);
await stream.InitializeAsync(cancellationToken);
return stream;
}
// Async methods moved to RarBLAKE2spStream.Async.cs
protected override void Dispose(bool disposing)
{

View File

@@ -10,6 +10,18 @@ namespace SharpCompress.Compressors.Rar;
internal partial class RarCrcStream : RarStream, IStreamStack
{
public static async ValueTask<RarCrcStream> CreateAsync(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyStreamBase readStream,
CancellationToken cancellationToken = default
)
{
var stream = new RarCrcStream(unpack, fileHeader, readStream);
await stream.InitializeAsync(cancellationToken);
return stream;
}
public override async Task<int> ReadAsync(
byte[] buffer,
int offset,

View File

@@ -59,17 +59,7 @@ internal partial class RarCrcStream : RarStream, IStreamStack
return stream;
}
public static async ValueTask<RarCrcStream> CreateAsync(
IRarUnpack unpack,
FileHeader fileHeader,
MultiVolumeReadOnlyStreamBase readStream,
CancellationToken cancellationToken = default
)
{
var stream = new RarCrcStream(unpack, fileHeader, readStream);
await stream.InitializeAsync(cancellationToken);
return stream;
}
// Async methods moved to RarCrcStream.Async.cs
protected override void Dispose(bool disposing)
{

View File

@@ -0,0 +1,107 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Factories;
using SharpCompress.IO;
namespace SharpCompress.Readers;
public static partial class ReaderFactory
{
/// <summary>
/// Opens a Reader from a filepath asynchronously
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static ValueTask<IAsyncReader> OpenAsyncReader(
string filePath,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken);
}
/// <summary>
/// Opens a Reader from a FileInfo asynchronously
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static ValueTask<IAsyncReader> OpenAsyncReader(
FileInfo fileInfo,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken);
}
public static async ValueTask<IAsyncReader> OpenAsyncReader(
Stream stream,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
stream.NotNull(nameof(stream));
options ??= new ReaderOptions() { LeaveStreamOpen = false };
var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize);
long pos = bStream.GetPosition();
var factories = Factory.Factories.OfType<Factory>();
Factory? testedFactory = null;
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
{
testedFactory = factories.FirstOrDefault(a =>
a.GetSupportedExtensions()
.Contains(options.ExtensionHint, StringComparer.CurrentCultureIgnoreCase)
);
if (testedFactory is IReaderFactory readerFactory)
{
bStream.StackSeek(pos);
if (
await testedFactory.IsArchiveAsync(
bStream,
cancellationToken: cancellationToken
)
)
{
bStream.StackSeek(pos);
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
}
}
bStream.StackSeek(pos);
}
foreach (var factory in factories)
{
if (testedFactory == factory)
{
continue; // Already tested above
}
bStream.StackSeek(pos);
if (
factory is IReaderFactory readerFactory
&& await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken)
)
{
bStream.StackSeek(pos);
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
}
}
throw new InvalidFormatException(
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
);
}
}

View File

@@ -9,7 +9,7 @@ using SharpCompress.IO;
namespace SharpCompress.Readers;
public static class ReaderFactory
public static partial class ReaderFactory
{
public static IReader OpenReader(string filePath, ReaderOptions? options = null)
{
@@ -17,46 +17,12 @@ public static class ReaderFactory
return OpenReader(new FileInfo(filePath), options);
}
/// <summary>
/// Opens a Reader from a filepath asynchronously
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static ValueTask<IAsyncReader> OpenAsyncReader(
string filePath,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken);
}
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
return OpenReader(fileInfo.OpenRead(), options);
}
/// <summary>
/// Opens a Reader from a FileInfo asynchronously
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static ValueTask<IAsyncReader> OpenAsyncReader(
FileInfo fileInfo,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken);
}
/// <summary>
/// Opens a Reader for Non-seeking usage
/// </summary>
@@ -110,63 +76,5 @@ public static class ReaderFactory
);
}
public static async ValueTask<IAsyncReader> OpenAsyncReader(
Stream stream,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
stream.NotNull(nameof(stream));
options ??= new ReaderOptions() { LeaveStreamOpen = false };
var bStream = new SharpCompressStream(stream, bufferSize: options.BufferSize);
long pos = bStream.GetPosition();
var factories = Factory.Factories.OfType<Factory>();
Factory? testedFactory = null;
if (!string.IsNullOrWhiteSpace(options.ExtensionHint))
{
testedFactory = factories.FirstOrDefault(a =>
a.GetSupportedExtensions()
.Contains(options.ExtensionHint, StringComparer.CurrentCultureIgnoreCase)
);
if (testedFactory is IReaderFactory readerFactory)
{
bStream.StackSeek(pos);
if (
await testedFactory.IsArchiveAsync(
bStream,
cancellationToken: cancellationToken
)
)
{
bStream.StackSeek(pos);
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
}
}
bStream.StackSeek(pos);
}
foreach (var factory in factories)
{
if (testedFactory == factory)
{
continue; // Already tested above
}
bStream.StackSeek(pos);
if (
factory is IReaderFactory readerFactory
&& await factory.IsArchiveAsync(bStream, cancellationToken: cancellationToken)
)
{
bStream.StackSeek(pos);
return await readerFactory.OpenAsyncReader(bStream, options, cancellationToken);
}
}
throw new InvalidFormatException(
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
);
}
// Async methods moved to ReaderFactory.Async.cs
}

View File

@@ -0,0 +1,55 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress;
internal static partial class Utility
{
/// <summary>
/// Read exactly the requested number of bytes from a stream asynchronously. Throws EndOfStreamException if not enough data is available.
/// </summary>
public static async ValueTask ReadExactAsync(
this Stream stream,
byte[] buffer,
int offset,
int length,
CancellationToken cancellationToken = default
)
{
if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}
if (buffer is null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (length < 0 || length > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
while (length > 0)
{
var fetched = await stream
.ReadAsync(buffer, offset, length, cancellationToken)
.ConfigureAwait(false);
if (fetched <= 0)
{
throw new EndOfStreamException();
}
offset += fetched;
length -= fetched;
}
}
}

View File

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace SharpCompress;
internal static class Utility
internal static partial class Utility
{
//80kb is a good industry standard temporary buffer size
internal const int TEMP_BUFFER_SIZE = 81920;
@@ -336,51 +336,7 @@ internal static class Utility
}
}
/// <summary>
/// Read exactly the requested number of bytes from a stream asynchronously. Throws EndOfStreamException if not enough data is available.
/// </summary>
public static async ValueTask ReadExactAsync(
this Stream stream,
byte[] buffer,
int offset,
int length,
CancellationToken cancellationToken = default
)
{
if (stream is null)
{
throw new ArgumentNullException(nameof(stream));
}
if (buffer is null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if (length < 0 || length > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
while (length > 0)
{
var fetched = await stream
.ReadAsync(buffer, offset, length, cancellationToken)
.ConfigureAwait(false);
if (fetched <= 0)
{
throw new EndOfStreamException();
}
offset += fetched;
length -= fetched;
}
}
// Async methods moved to Utility.Async.cs
public static string TrimNulls(this string source) => source.Replace('\0', ' ').Trim();