| File: src\runtime\src\libraries\Common\src\System\IO\SubReadStream.cs | Web Access |
| Project: System.Formats.Tar.csproj (System.Formats.Tar) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace System.IO { // Stream that wraps a super stream and exposes a window [startPosition, startPosition + maxLength). // Supports both seekable and unseekable super streams. When the super stream is seekable, this stream // is seekable too and will reposition the super stream before each read if needed. When the super // stream is unseekable, reads must be sequential and Seek/Position-set throw. // Does not support writing. internal sealed class SubReadStream : Stream { private const int MaxAdvanceBufferLength = 4096; private bool _hasReachedEnd; private readonly long _startInSuperStream; private long _positionInSuperStream; private readonly long _endInSuperStream; private readonly Stream _superStream; private bool _isDisposed; public SubReadStream(Stream superStream, long startPosition, long maxLength) { ArgumentNullException.ThrowIfNull(superStream); if (!superStream.CanRead) { throw new ArgumentException(SR.IO_NotSupported_UnreadableStream, nameof(superStream)); } ArgumentOutOfRangeException.ThrowIfNegative(startPosition); ArgumentOutOfRangeException.ThrowIfNegative(maxLength); ArgumentOutOfRangeException.ThrowIfGreaterThan(startPosition, long.MaxValue - maxLength); _startInSuperStream = startPosition; _positionInSuperStream = startPosition; _endInSuperStream = startPosition + maxLength; _superStream = superStream; } public override long Length { get { ThrowIfDisposed(); return _endInSuperStream - _startInSuperStream; } } public override long Position { get { ThrowIfDisposed(); return _positionInSuperStream - _startInSuperStream; } set { ThrowIfDisposed(); if (!CanSeek) { throw new NotSupportedException(SR.IO_NotSupported_UnseekableStream); } ArgumentOutOfRangeException.ThrowIfNegative(value); long newPositionInSuperStream = _startInSuperStream + value; _superStream.Position = newPositionInSuperStream; _positionInSuperStream = newPositionInSuperStream; } } public override bool CanRead => !_isDisposed && _superStream.CanRead; public override bool CanSeek => !_isDisposed && _superStream.CanSeek; public override bool CanWrite => false; private long Remaining => _endInSuperStream - _positionInSuperStream; private int LimitByRemaining(int bufferSize) => (int)Math.Max(0, Math.Min(Remaining, bufferSize)); // Positions the super stream past the end of this stream's window. After calling this method, // subsequent reads on this stream throw <see cref="EndOfStreamException"/>. internal void AdvanceToEnd() { _hasReachedEnd = true; long remaining = Remaining; _positionInSuperStream = _endInSuperStream; AdvanceSuperStream(remaining); } internal ValueTask AdvanceToEndAsync(CancellationToken cancellationToken) { _hasReachedEnd = true; long remaining = Remaining; _positionInSuperStream = _endInSuperStream; return AdvanceSuperStreamAsync(remaining, cancellationToken); } private void AdvanceSuperStream(long bytesToDiscard) { if (_superStream.CanSeek) { _superStream.Position += bytesToDiscard; } else if (bytesToDiscard > 0) { byte[] buffer = ArrayPool<byte>.Shared.Rent((int)Math.Min(MaxAdvanceBufferLength, bytesToDiscard)); try { while (bytesToDiscard > 0) { int currentLengthToRead = (int)Math.Min(MaxAdvanceBufferLength, bytesToDiscard); _superStream.ReadExactly(buffer.AsSpan(0, currentLengthToRead)); bytesToDiscard -= currentLengthToRead; } } finally { ArrayPool<byte>.Shared.Return(buffer); } } } private async ValueTask AdvanceSuperStreamAsync(long bytesToDiscard, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (_superStream.CanSeek) { _superStream.Position += bytesToDiscard; } else if (bytesToDiscard > 0) { byte[] buffer = ArrayPool<byte>.Shared.Rent((int)Math.Min(MaxAdvanceBufferLength, bytesToDiscard)); try { while (bytesToDiscard > 0) { int currentLengthToRead = (int)Math.Min(MaxAdvanceBufferLength, bytesToDiscard); await _superStream.ReadExactlyAsync(buffer, 0, currentLengthToRead, cancellationToken).ConfigureAwait(false); bytesToDiscard -= currentLengthToRead; } } finally { ArrayPool<byte>.Shared.Return(buffer); } } } private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_isDisposed, this); } private void ThrowIfCantRead() { if (!_superStream.CanRead) { throw new NotSupportedException(SR.IO_NotSupported_UnreadableStream); } } private void ThrowIfBeyondEndOfStream() { if (_hasReachedEnd) { throw new EndOfStreamException(); } } public override int Read(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); return Read(buffer.AsSpan(offset, count)); } public override int Read(Span<byte> destination) { ThrowIfDisposed(); ThrowIfCantRead(); ThrowIfBeyondEndOfStream(); if (_superStream.CanSeek && _superStream.Position != _positionInSuperStream) { _superStream.Seek(_positionInSuperStream, SeekOrigin.Begin); } destination = destination[..LimitByRemaining(destination.Length)]; int ret = _superStream.Read(destination); _positionInSuperStream += ret; return ret; } public override int ReadByte() { byte b = default; return Read(new Span<byte>(ref b)) == 1 ? b : -1; } public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ValidateBufferArguments(buffer, offset, count); return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask(); } public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) { ThrowIfDisposed(); ThrowIfCantRead(); ThrowIfBeyondEndOfStream(); return ReadAsyncCore(buffer, cancellationToken); } private async ValueTask<int> ReadAsyncCore(Memory<byte> buffer, CancellationToken cancellationToken) { Debug.Assert(!_hasReachedEnd); cancellationToken.ThrowIfCancellationRequested(); if (_superStream.CanSeek && _superStream.Position != _positionInSuperStream) { _superStream.Seek(_positionInSuperStream, SeekOrigin.Begin); } buffer = buffer[..LimitByRemaining(buffer.Length)]; int ret = await _superStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); _positionInSuperStream += ret; return ret; } public override long Seek(long offset, SeekOrigin origin) { ThrowIfDisposed(); if (!CanSeek) { throw new NotSupportedException(SR.IO_NotSupported_UnseekableStream); } long newPositionInSuperStream = origin switch { SeekOrigin.Begin => _startInSuperStream + offset, SeekOrigin.Current => _positionInSuperStream + offset, SeekOrigin.End => _endInSuperStream + offset, _ => throw new ArgumentOutOfRangeException(nameof(origin)), }; if (newPositionInSuperStream < _startInSuperStream) { throw new IOException(SR.IO_SeekBeforeBegin); } long actualPositionInSuperStream = _superStream.Seek(newPositionInSuperStream, SeekOrigin.Begin); _positionInSuperStream = actualPositionInSuperStream; return _positionInSuperStream - _startInSuperStream; } public override void SetLength(long value) => throw new NotSupportedException(SR.IO_NotSupported_UnwritableStream); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(SR.IO_NotSupported_UnwritableStream); public override void Flush() { } public override Task FlushAsync(CancellationToken cancellationToken) => cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : Task.CompletedTask; // Close the stream for reading. Note that this does NOT close the super stream (since // this stream is just a 'chunk' of the super stream). protected override void Dispose(bool disposing) { _isDisposed = true; base.Dispose(disposing); } } }