move some files around to make more sense

This commit is contained in:
Adam Hathcock
2026-04-03 13:23:06 +01:00
parent ea6946c48d
commit e84c1ca007
5 changed files with 374 additions and 372 deletions

View File

@@ -0,0 +1,108 @@
using System.IO;
using System.Threading;
namespace SharpCompress;
public static class FileInfoExtensions
{
/// <summary>
/// Opens a file stream for asynchronous writing.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="path">The file path to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncWriteStream(string path, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET8_0_OR_GREATER
// Use File.OpenHandle with async options for .NET 8.0+
var handle = File.OpenHandle(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
FileOptions.Asynchronous
);
return new FileStream(handle, FileAccess.Write);
#else
// For older target frameworks, use FileStream constructor with async options
return new FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096, //default
FileOptions.Asynchronous
);
#endif
}
/// <summary>
/// Opens a file stream for asynchronous reading.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="path">The file path to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncReadStream(string path, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET8_0_OR_GREATER
// Use File.OpenHandle with async options for .NET 8.0+
var handle = File.OpenHandle(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
FileOptions.Asynchronous
);
return new FileStream(handle, FileAccess.Read);
#else
// For older target frameworks, use FileStream constructor with async options
return new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
FileOptions.Asynchronous
);
#endif
}
/// <param name="fileInfo">The FileInfo to open.</param>
extension(FileInfo fileInfo)
{
/// <summary>
/// Opens a file stream for asynchronous reading from a FileInfo.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public Stream OpenAsyncReadStream(CancellationToken cancellationToken)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncReadStream(fileInfo.FullName, cancellationToken);
}
/// <summary>
/// Opens a file stream for asynchronous writing from a FileInfo.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="fileInfo">The FileInfo to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public Stream OpenAsyncWriteStream(CancellationToken cancellationToken)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncWriteStream(fileInfo.FullName, cancellationToken);
}
}
}

View File

@@ -0,0 +1,123 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress;
public static partial class StreamExtensions
{
extension(Stream source)
{
/// <summary>
/// Read exactly the requested number of bytes from a stream asynchronously. Throws EndOfStreamException if not enough data is available.
/// </summary>
public async ValueTask ReadExactAsync(
byte[] buffer,
int offset,
int length,
CancellationToken cancellationToken = default
)
{
#if LEGACY_DOTNET
if (source is null)
{
throw new ArgumentNullException();
}
#else
ThrowHelper.ThrowIfNull(source);
#endif
ThrowHelper.ThrowIfNull(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 source
.ReadAsync(buffer, offset, length, cancellationToken)
.ConfigureAwait(false);
if (fetched <= 0)
{
throw new IncompleteArchiveException("Unexpected end of stream.");
}
offset += fetched;
length -= fetched;
}
}
public async ValueTask<long> TransferToAsync(
Stream destination,
long maxLength,
CancellationToken cancellationToken = default
)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyToAsync
using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
await limitedStream
.CopyToAsync(destination, Constants.BufferSize, cancellationToken)
.ConfigureAwait(false);
return limitedStream.Position;
}
public async ValueTask<bool> ReadFullyAsync(
byte[] buffer,
CancellationToken cancellationToken = default
)
{
var total = 0;
int read;
while (
(
read = await source
.ReadAsync(buffer, total, buffer.Length - total, cancellationToken)
.ConfigureAwait(false)
) > 0
)
{
total += read;
if (total >= buffer.Length)
{
return true;
}
}
return (total >= buffer.Length);
}
public async ValueTask<bool> ReadFullyAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken = default
)
{
var total = 0;
int read;
while (
(
read = await source
.ReadAsync(buffer, offset + total, count - total, cancellationToken)
.ConfigureAwait(false)
) > 0
)
{
total += read;
if (total >= count)
{
return true;
}
}
return (total >= count);
}
}
}

View File

@@ -3,11 +3,12 @@ using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress;
public static class StreamExtensions
public static partial class StreamExtensions
{
extension(Stream stream)
{
@@ -68,5 +69,146 @@ public static class StreamExtensions
ArrayPool<byte>.Shared.Return(temp);
}
}
public long TransferTo(Stream destination, long maxLength)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyTo
using var limitedStream = new IO.ReadOnlySubStream(stream, maxLength);
limitedStream.CopyTo(destination, Constants.BufferSize);
return limitedStream.Position;
}
public async ValueTask SkipAsync(
long advanceAmount,
CancellationToken cancellationToken = default
)
{
if (stream.CanSeek && stream is not SharpCompressStream)
{
stream.Position += advanceAmount;
return;
}
var array = ArrayPool<byte>.Shared.Rent(Constants.BufferSize);
try
{
while (advanceAmount > 0)
{
var toRead = (int)Math.Min(array.Length, advanceAmount);
var read = await stream
.ReadAsync(array, 0, toRead, cancellationToken)
.ConfigureAwait(false);
if (read <= 0)
{
break;
}
advanceAmount -= read;
}
}
finally
{
ArrayPool<byte>.Shared.Return(array);
}
}
#if NET8_0_OR_GREATER
public bool ReadFully(byte[] buffer)
{
try
{
stream.ReadExactly(buffer);
return true;
}
catch (EndOfStreamException)
{
return false;
}
}
public bool ReadFully(Span<byte> buffer)
{
try
{
stream.ReadExactly(buffer);
return true;
}
catch (EndOfStreamException)
{
return false;
}
}
#else
public bool ReadFully(byte[] buffer)
{
var total = 0;
int read;
while ((read = stream.Read(buffer, total, buffer.Length - total)) > 0)
{
total += read;
if (total >= buffer.Length)
{
return true;
}
}
return (total >= buffer.Length);
}
public bool ReadFully(Span<byte> buffer)
{
var total = 0;
int read;
while ((read = stream.Read(buffer.Slice(total, buffer.Length - total))) > 0)
{
total += read;
if (total >= buffer.Length)
{
return true;
}
}
return (total >= buffer.Length);
}
#endif
/// <summary>
/// Read exactly the requested number of bytes from a stream. Throws EndOfStreamException if not enough data is available.
/// </summary>
public void ReadExact(byte[] buffer, int offset, int length)
{
#if LEGACY_DOTNET
if (stream is null)
{
throw new ArgumentNullException();
}
#else
ThrowHelper.ThrowIfNull(stream);
#endif
ThrowHelper.ThrowIfNull(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 = stream.Read(buffer, offset, length);
if (fetched <= 0)
{
throw new IncompleteArchiveException("Unexpected end of stream.");
}
offset += fetched;
length -= fetched;
}
}
}
}

View File

@@ -1,227 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress;
internal static partial class Utility
{
extension(Stream source)
{
/// <summary>
/// Read exactly the requested number of bytes from a stream asynchronously. Throws EndOfStreamException if not enough data is available.
/// </summary>
public async ValueTask ReadExactAsync(
byte[] buffer,
int offset,
int length,
CancellationToken cancellationToken = default
)
{
#if LEGACY_DOTNET
if (source is null)
{
throw new ArgumentNullException();
}
#else
ThrowHelper.ThrowIfNull(source);
#endif
ThrowHelper.ThrowIfNull(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 source
.ReadAsync(buffer, offset, length, cancellationToken)
.ConfigureAwait(false);
if (fetched <= 0)
{
throw new IncompleteArchiveException("Unexpected end of stream.");
}
offset += fetched;
length -= fetched;
}
}
public async ValueTask<long> TransferToAsync(
Stream destination,
long maxLength,
CancellationToken cancellationToken = default
)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyToAsync
using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
await limitedStream
.CopyToAsync(destination, Constants.BufferSize, cancellationToken)
.ConfigureAwait(false);
return limitedStream.Position;
}
public async ValueTask<bool> ReadFullyAsync(
byte[] buffer,
CancellationToken cancellationToken = default
)
{
var total = 0;
int read;
while (
(
read = await source
.ReadAsync(buffer, total, buffer.Length - total, cancellationToken)
.ConfigureAwait(false)
) > 0
)
{
total += read;
if (total >= buffer.Length)
{
return true;
}
}
return (total >= buffer.Length);
}
public async ValueTask<bool> ReadFullyAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken = default
)
{
var total = 0;
int read;
while (
(
read = await source
.ReadAsync(buffer, offset + total, count - total, cancellationToken)
.ConfigureAwait(false)
) > 0
)
{
total += read;
if (total >= count)
{
return true;
}
}
return (total >= count);
}
}
/// <summary>
/// Opens a file stream for asynchronous writing.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="path">The file path to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncWriteStream(string path, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET8_0_OR_GREATER
// Use File.OpenHandle with async options for .NET 8.0+
var handle = File.OpenHandle(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
FileOptions.Asynchronous
);
return new FileStream(handle, FileAccess.Write);
#else
// For older target frameworks, use FileStream constructor with async options
return new FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 4096, //default
FileOptions.Asynchronous
);
#endif
}
/// <summary>
/// Opens a file stream for asynchronous writing from a FileInfo.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="fileInfo">The FileInfo to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncWriteStream(
this FileInfo fileInfo,
CancellationToken cancellationToken
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncWriteStream(fileInfo.FullName, cancellationToken);
}
/// <summary>
/// Opens a file stream for asynchronous reading.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="path">The file path to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncReadStream(string path, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
#if NET8_0_OR_GREATER
// Use File.OpenHandle with async options for .NET 8.0+
var handle = File.OpenHandle(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
FileOptions.Asynchronous
);
return new FileStream(handle, FileAccess.Read);
#else
// For older target frameworks, use FileStream constructor with async options
return new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
FileOptions.Asynchronous
);
#endif
}
/// <summary>
/// Opens a file stream for asynchronous reading from a FileInfo.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="fileInfo">The FileInfo to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncReadStream(
this FileInfo fileInfo,
CancellationToken cancellationToken
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncReadStream(fileInfo.FullName, cancellationToken);
}
}

View File

@@ -138,150 +138,6 @@ internal static partial class Utility
return sTime.AddSeconds(unixtime);
}
extension(Stream source)
{
public long TransferTo(Stream destination, long maxLength)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyTo
using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
limitedStream.CopyTo(destination, Constants.BufferSize);
return limitedStream.Position;
}
public async ValueTask SkipAsync(
long advanceAmount,
CancellationToken cancellationToken = default
)
{
if (source.CanSeek && source is not SharpCompressStream)
{
source.Position += advanceAmount;
return;
}
var array = ArrayPool<byte>.Shared.Rent(Constants.BufferSize);
try
{
while (advanceAmount > 0)
{
var toRead = (int)Math.Min(array.Length, advanceAmount);
var read = await source
.ReadAsync(array, 0, toRead, cancellationToken)
.ConfigureAwait(false);
if (read <= 0)
{
break;
}
advanceAmount -= read;
}
}
finally
{
ArrayPool<byte>.Shared.Return(array);
}
}
#if NET8_0_OR_GREATER
public bool ReadFully(byte[] buffer)
{
try
{
source.ReadExactly(buffer);
return true;
}
catch (EndOfStreamException)
{
return false;
}
}
public bool ReadFully(Span<byte> buffer)
{
try
{
source.ReadExactly(buffer);
return true;
}
catch (EndOfStreamException)
{
return false;
}
}
#else
public bool ReadFully(byte[] buffer)
{
var total = 0;
int read;
while ((read = source.Read(buffer, total, buffer.Length - total)) > 0)
{
total += read;
if (total >= buffer.Length)
{
return true;
}
}
return (total >= buffer.Length);
}
public bool ReadFully(Span<byte> buffer)
{
var total = 0;
int read;
while ((read = source.Read(buffer.Slice(total, buffer.Length - total))) > 0)
{
total += read;
if (total >= buffer.Length)
{
return true;
}
}
return (total >= buffer.Length);
}
#endif
/// <summary>
/// Read exactly the requested number of bytes from a stream. Throws EndOfStreamException if not enough data is available.
/// </summary>
public void ReadExact(byte[] buffer, int offset, int length)
{
#if LEGACY_DOTNET
if (source is null)
{
throw new ArgumentNullException();
}
#else
ThrowHelper.ThrowIfNull(source);
#endif
ThrowHelper.ThrowIfNull(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 = source.Read(buffer, offset, length);
if (fetched <= 0)
{
throw new IncompleteArchiveException("Unexpected end of stream.");
}
offset += fetched;
length -= fetched;
}
}
}
public static string TrimNulls(this string source) => source.Replace('\0', ' ').Trim();
/// <summary>