Files
sharpcompress/src/SharpCompress/IO/SharpCompressStream.Async.cs

388 lines
12 KiB
C#
Raw Normal View History

2026-01-28 16:50:35 +00:00
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.IO;
internal partial class SharpCompressStream
2026-01-28 16:50:35 +00:00
{
public override Task<int> ReadAsync(
2026-01-28 16:50:35 +00:00
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
if (count == 0)
{
return Task.FromResult(0);
2026-01-28 16:50:35 +00:00
}
2026-01-29 15:23:53 +00:00
// In passthrough mode, delegate directly to underlying stream
if (_isPassthrough)
{
return stream.ReadAsync(buffer, offset, count, cancellationToken);
}
return ReadAsyncCore(buffer, offset, count, cancellationToken);
}
private async Task<int> ReadAsyncCore(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
2026-02-04 08:20:05 +00:00
// If recording is active or we're reading from the recording buffer, use legacy behavior
if (IsRecording || (isRewound && bufferStream.Position != bufferStream.Length))
{
return await ReadWithRecordingAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
}
2026-02-04 08:30:02 +00:00
// If ring buffer is enabled (and not recording), use ring buffer logic
if (_ringBuffer is not null)
2026-02-04 08:20:05 +00:00
{
2026-02-04 08:30:02 +00:00
return await ReadWithRingBufferAsync(buffer, offset, count, cancellationToken)
2026-02-04 08:20:05 +00:00
.ConfigureAwait(false);
}
// No buffering - read directly from stream
int read = await stream
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
streamPosition += read;
_logicalPosition = streamPosition;
return read;
}
/// <summary>
/// Async version of ReadWithRecording (legacy behavior for format detection).
/// </summary>
private async Task<int> ReadWithRecordingAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
2026-01-28 16:50:35 +00:00
int read;
2026-01-29 15:23:53 +00:00
if (isRewound && bufferStream.Position != bufferStream.Length)
2026-01-28 16:50:35 +00:00
{
2026-01-29 15:23:53 +00:00
var readCount = Math.Min(count, (int)(bufferStream.Length - bufferStream.Position));
read = await bufferStream
.ReadAsync(buffer, offset, readCount, cancellationToken)
.ConfigureAwait(false);
if (read < count)
2026-01-28 16:50:35 +00:00
{
2026-01-29 15:23:53 +00:00
var tempRead = await stream
2026-01-28 16:50:35 +00:00
.ReadAsync(buffer, offset + read, count - read, cancellationToken)
.ConfigureAwait(false);
if (IsRecording)
{
2026-01-29 15:23:53 +00:00
await bufferStream
.WriteAsync(buffer, offset + read, tempRead, cancellationToken)
.ConfigureAwait(false);
2026-01-28 16:50:35 +00:00
}
2026-02-04 08:30:02 +00:00
else if (_ringBuffer is not null && tempRead > 0)
2026-02-04 08:20:05 +00:00
{
2026-02-04 08:30:02 +00:00
// When transitioning out of recording mode, add to ring buffer
2026-02-04 08:20:05 +00:00
// so that future rewinds will work
2026-02-04 08:30:02 +00:00
_ringBuffer.Write(buffer, offset + read, tempRead);
2026-02-04 08:20:05 +00:00
}
2026-01-29 15:23:53 +00:00
streamPosition += tempRead;
2026-02-04 08:20:05 +00:00
_logicalPosition = streamPosition;
2026-01-28 16:50:35 +00:00
read += tempRead;
}
2026-01-29 15:23:53 +00:00
if (bufferStream.Position == bufferStream.Length)
2026-01-28 16:50:35 +00:00
{
2026-01-29 15:23:53 +00:00
isRewound = false;
2026-01-28 16:50:35 +00:00
}
return read;
}
read = await stream
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
if (IsRecording)
{
2026-01-29 15:23:53 +00:00
await bufferStream
.WriteAsync(buffer, offset, read, cancellationToken)
.ConfigureAwait(false);
2026-01-28 16:50:35 +00:00
}
2026-01-29 15:23:53 +00:00
streamPosition += read;
2026-02-04 08:20:05 +00:00
_logicalPosition = streamPosition;
2026-01-28 16:50:35 +00:00
return read;
}
2026-02-04 08:20:05 +00:00
/// <summary>
2026-02-04 08:30:02 +00:00
/// Async version of ReadWithRingBuffer.
2026-02-04 08:20:05 +00:00
/// </summary>
2026-02-04 08:30:02 +00:00
private async Task<int> ReadWithRingBufferAsync(
2026-02-04 08:20:05 +00:00
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
int totalRead = 0;
2026-02-04 08:30:02 +00:00
// If logical position is behind stream position, read from ring buffer first
2026-02-04 08:20:05 +00:00
while (count > 0 && _logicalPosition < streamPosition)
{
long bytesFromEnd = streamPosition - _logicalPosition;
2026-02-04 08:30:02 +00:00
int available = _ringBuffer!.ReadFromEnd(bytesFromEnd, buffer, offset, count);
totalRead += available;
offset += available;
count -= available;
_logicalPosition += available;
2026-02-04 08:20:05 +00:00
}
// If more data needed, read from underlying stream
if (count > 0)
{
int read = await stream
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
if (read > 0)
{
2026-02-04 08:30:02 +00:00
_ringBuffer!.Write(buffer, offset, read);
2026-02-04 08:20:05 +00:00
streamPosition += read;
_logicalPosition += read;
totalRead += read;
}
}
return totalRead;
}
2026-01-28 16:50:35 +00:00
#if !LEGACY_DOTNET
public override ValueTask<int> ReadAsync(
2026-01-28 16:50:35 +00:00
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
if (buffer.Length == 0)
{
return ValueTask.FromResult(0);
2026-01-28 16:50:35 +00:00
}
2026-01-29 15:23:53 +00:00
// In passthrough mode, delegate directly to underlying stream
if (_isPassthrough)
{
return stream.ReadAsync(buffer, cancellationToken);
}
return ReadAsyncCore(buffer, cancellationToken);
}
private async ValueTask<int> ReadAsyncCore(
Memory<byte> buffer,
CancellationToken cancellationToken
)
{
2026-02-04 08:20:05 +00:00
// If recording is active or we're reading from the recording buffer, use legacy behavior
if (IsRecording || (isRewound && bufferStream.Position != bufferStream.Length))
{
return await ReadWithRecordingAsync(buffer, cancellationToken).ConfigureAwait(false);
}
2026-02-04 08:30:02 +00:00
// If ring buffer is enabled (and not recording), use ring buffer logic
if (_ringBuffer is not null)
2026-02-04 08:20:05 +00:00
{
2026-02-04 08:30:02 +00:00
return await ReadWithRingBufferAsync(buffer, cancellationToken).ConfigureAwait(false);
2026-02-04 08:20:05 +00:00
}
// No buffering - read directly from stream
int read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
streamPosition += read;
_logicalPosition = streamPosition;
return read;
}
/// <summary>
/// Async version of ReadWithRecording for Memory&lt;byte&gt; (legacy behavior for format detection).
/// </summary>
private async ValueTask<int> ReadWithRecordingAsync(
Memory<byte> buffer,
CancellationToken cancellationToken
)
{
2026-01-28 16:50:35 +00:00
int read;
2026-01-29 15:23:53 +00:00
if (isRewound && bufferStream.Position != bufferStream.Length)
2026-01-28 16:50:35 +00:00
{
2026-01-29 15:23:53 +00:00
var readCount = (int)
Math.Min(buffer.Length, bufferStream.Length - bufferStream.Position);
read = await bufferStream
.ReadAsync(buffer.Slice(0, readCount), cancellationToken)
.ConfigureAwait(false);
if (read < buffer.Length)
2026-01-28 16:50:35 +00:00
{
2026-01-29 15:23:53 +00:00
var tempRead = await stream
2026-01-28 16:50:35 +00:00
.ReadAsync(buffer.Slice(read), cancellationToken)
.ConfigureAwait(false);
if (IsRecording)
{
2026-01-29 15:23:53 +00:00
await bufferStream
.WriteAsync(buffer.Slice(read, tempRead), cancellationToken)
.ConfigureAwait(false);
2026-01-28 16:50:35 +00:00
}
2026-02-04 08:30:02 +00:00
else if (_ringBuffer is not null && tempRead > 0)
2026-02-04 08:20:05 +00:00
{
2026-02-04 08:30:02 +00:00
// When transitioning out of recording mode, add to ring buffer
2026-02-04 08:20:05 +00:00
// so that future rewinds will work
var tempBuffer = buffer.Slice(read, tempRead).ToArray();
2026-02-04 08:30:02 +00:00
_ringBuffer.Write(tempBuffer, 0, tempRead);
2026-02-04 08:20:05 +00:00
}
streamPosition += tempRead;
2026-02-04 08:20:05 +00:00
_logicalPosition = streamPosition;
2026-01-28 16:50:35 +00:00
read += tempRead;
}
2026-01-29 15:23:53 +00:00
if (bufferStream.Position == bufferStream.Length)
2026-01-28 16:50:35 +00:00
{
2026-01-29 15:23:53 +00:00
isRewound = false;
2026-01-28 16:50:35 +00:00
}
return read;
}
read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (IsRecording)
{
2026-01-29 15:23:53 +00:00
await bufferStream
.WriteAsync(buffer.Slice(0, read), cancellationToken)
.ConfigureAwait(false);
2026-01-28 16:50:35 +00:00
}
streamPosition += read;
2026-02-04 08:20:05 +00:00
_logicalPosition = streamPosition;
2026-01-28 16:50:35 +00:00
return read;
}
2026-02-04 08:20:05 +00:00
/// <summary>
2026-02-04 08:30:02 +00:00
/// Async version of ReadWithRingBuffer for Memory&lt;byte&gt;.
2026-02-04 08:20:05 +00:00
/// </summary>
2026-02-04 08:30:02 +00:00
private async ValueTask<int> ReadWithRingBufferAsync(
2026-02-04 08:20:05 +00:00
Memory<byte> buffer,
CancellationToken cancellationToken
)
{
int totalRead = 0;
int count = buffer.Length;
int offset = 0;
2026-02-04 08:30:02 +00:00
// If logical position is behind stream position, read from ring buffer first
// Note: We need to use a temporary byte array because RingBuffer.ReadFromEnd expects byte[]
2026-02-04 08:20:05 +00:00
while (count > 0 && _logicalPosition < streamPosition)
{
long bytesFromEnd = streamPosition - _logicalPosition;
2026-02-04 08:30:02 +00:00
var tempBuffer = new byte[Math.Min(count, (int)bytesFromEnd)];
int available = _ringBuffer!.ReadFromEnd(
bytesFromEnd,
tempBuffer,
0,
tempBuffer.Length
2026-02-04 08:20:05 +00:00
);
2026-02-04 08:30:02 +00:00
tempBuffer.AsSpan(0, available).CopyTo(buffer.Span.Slice(offset));
2026-02-04 08:20:05 +00:00
2026-02-04 08:30:02 +00:00
totalRead += available;
offset += available;
count -= available;
_logicalPosition += available;
2026-02-04 08:20:05 +00:00
}
// If more data needed, read from underlying stream
if (count > 0)
{
int read = await stream
.ReadAsync(buffer.Slice(offset, count), cancellationToken)
.ConfigureAwait(false);
if (read > 0)
{
2026-02-04 08:30:02 +00:00
// RingBuffer.Write expects byte[], so we need to copy
2026-02-04 08:20:05 +00:00
var tempBuffer = buffer.Slice(offset, read).ToArray();
2026-02-04 08:30:02 +00:00
_ringBuffer!.Write(tempBuffer, 0, read);
2026-02-04 08:20:05 +00:00
streamPosition += read;
_logicalPosition += read;
totalRead += read;
}
}
return totalRead;
}
2026-01-28 16:50:35 +00:00
#endif
2026-01-29 15:23:53 +00:00
public override Task WriteAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
if (_isPassthrough)
{
return stream.WriteAsync(buffer, offset, count, cancellationToken);
}
throw new NotSupportedException();
}
2026-01-29 15:23:53 +00:00
#if !LEGACY_DOTNET
public override ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default
)
{
if (_isPassthrough)
{
return stream.WriteAsync(buffer, cancellationToken);
}
throw new NotSupportedException();
}
2026-01-29 15:23:53 +00:00
#endif
public override Task FlushAsync(CancellationToken cancellationToken)
{
if (_isPassthrough)
{
return stream.FlushAsync(cancellationToken);
}
2026-01-29 15:23:53 +00:00
throw new NotSupportedException();
}
2026-01-29 15:23:53 +00:00
public override async Task CopyToAsync(
Stream destination,
int bufferSize,
CancellationToken cancellationToken
)
{
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = await ReadAsync(buffer, 0, buffer.Length, cancellationToken)) != 0)
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken);
}
}
#if !LEGACY_DOTNET
public override async ValueTask DisposeAsync()
{
if (!isDisposed)
{
if (ThrowOnDispose)
{
throw new InvalidOperationException(
$"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is true"
);
}
2026-01-29 15:23:53 +00:00
isDisposed = true;
if (!LeaveStreamOpen)
{
await stream.DisposeAsync();
}
2026-02-04 08:30:02 +00:00
_ringBuffer?.Dispose();
_ringBuffer = null;
2026-01-29 15:23:53 +00:00
}
}
#endif
2026-01-28 16:50:35 +00:00
}