Files
sharpcompress/src/SharpCompress/IO/SharpCompressStream.cs
2026-01-23 10:25:41 +00:00

311 lines
8.7 KiB
C#

using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.IO;
public partial class SharpCompressStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => Stream;
// Buffering fields
private int _bufferSize;
private byte[]? _buffer;
private int _bufferPosition;
private int _bufferedLength;
private bool _bufferingEnabled;
private long _baseInitialPos;
private void ValidateBufferState()
{
if (_bufferPosition < 0 || _bufferPosition > _bufferedLength)
{
throw new InvalidOperationException(
"Buffer state is inconsistent: _bufferPosition is out of range."
);
}
}
int IStreamStack.BufferSize
{
get => _bufferingEnabled ? _bufferSize : 0;
set //need to adjust an already existing buffer
{
if (_bufferSize != value)
{
_bufferSize = value;
_bufferingEnabled = _bufferSize > 0;
if (_bufferingEnabled)
{
if (_buffer is not null)
{
ArrayPool<byte>.Shared.Return(_buffer);
}
_buffer = ArrayPool<byte>.Shared.Rent(_bufferSize);
_bufferPosition = 0;
_bufferedLength = 0;
if (_bufferingEnabled)
{
ValidateBufferState(); // Add here
}
// Check CanSeek before accessing Position to avoid exception overhead on non-seekable streams.
_internalPosition = Stream.CanSeek ? Stream.Position : 0;
}
}
}
}
int IStreamStack.BufferPosition
{
get => _bufferingEnabled ? _bufferPosition : 0;
set
{
if (_bufferingEnabled)
{
if (value < 0 || value > _bufferedLength)
throw new ArgumentOutOfRangeException(nameof(value));
_internalPosition = _internalPosition - _bufferPosition + value;
_bufferPosition = value;
ValidateBufferState(); // Add here
}
}
}
void IStreamStack.SetPosition(long position) { }
public Stream Stream { get; }
private bool _readOnly; //some archive detection requires seek to be disabled to cause it to exception to try the next arc type
//private bool _isRewound;
private bool _isDisposed;
private long _internalPosition = 0;
public bool ThrowOnDispose { get; set; }
public bool LeaveOpen { get; set; }
public long InternalPosition => _internalPosition;
public static SharpCompressStream Create(
Stream stream,
bool leaveOpen = false,
bool throwOnDispose = false,
int bufferSize = 0,
bool forceBuffer = false
)
{
if (
stream is SharpCompressStream sc
&& sc.LeaveOpen == leaveOpen
&& sc.ThrowOnDispose == throwOnDispose
)
{
if (bufferSize != 0)
((IStreamStack)stream).SetBuffer(bufferSize, forceBuffer);
return sc;
}
return new SharpCompressStream(stream, leaveOpen, throwOnDispose, bufferSize, forceBuffer);
}
public SharpCompressStream(
Stream stream,
bool leaveOpen = false,
bool throwOnDispose = false,
int bufferSize = 0,
bool forceBuffer = false
)
{
Stream = stream;
LeaveOpen = leaveOpen;
ThrowOnDispose = throwOnDispose;
_readOnly = !Stream.CanSeek;
((IStreamStack)this).SetBuffer(bufferSize, forceBuffer);
// Check CanSeek before accessing Position to avoid exception overhead on non-seekable streams.
_baseInitialPos = Stream.CanSeek ? Stream.Position : 0;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(SharpCompressStream));
#endif
}
protected override void Dispose(bool disposing)
{
#if DEBUG_STREAMS
this.DebugDispose(typeof(SharpCompressStream));
#endif
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (LeaveOpen)
{
return;
}
if (ThrowOnDispose)
{
throw new InvalidOperationException(
$"Attempt to dispose of a {nameof(SharpCompressStream)} when {nameof(ThrowOnDispose)} is {ThrowOnDispose}"
);
}
base.Dispose(disposing);
if (disposing)
{
Stream.Dispose();
if (_buffer != null)
{
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null;
}
}
}
public override bool CanRead => Stream.CanRead;
public override bool CanSeek => !_readOnly && Stream.CanSeek;
public override bool CanWrite => !_readOnly && Stream.CanWrite;
public override void Flush()
{
Stream.Flush();
}
public override long Length
{
get { return Stream.Length; }
}
public override long Position
{
get
{
long pos = _internalPosition; // Stream.Position + _bufferStream.Position - _bufferStream.Length;
return pos;
}
set { Seek(value, SeekOrigin.Begin); }
}
public override int Read(byte[] buffer, int offset, int count)
{
if (count == 0)
return 0;
if (_bufferingEnabled)
{
ValidateBufferState();
// Fill buffer if needed
if (_bufferedLength == 0)
{
_bufferedLength = Stream.Read(_buffer!, 0, _bufferSize);
_bufferPosition = 0;
}
int available = _bufferedLength - _bufferPosition;
int toRead = Math.Min(count, available);
if (toRead > 0)
{
Array.Copy(_buffer!, _bufferPosition, buffer, offset, toRead);
_bufferPosition += toRead;
_internalPosition += toRead;
return toRead;
}
// If buffer exhausted, refill
int r = Stream.Read(_buffer!, 0, _bufferSize);
if (r == 0)
return 0;
_bufferedLength = r;
_bufferPosition = 0;
if (_bufferedLength == 0)
{
return 0;
}
toRead = Math.Min(count, _bufferedLength);
Array.Copy(_buffer!, 0, buffer, offset, toRead);
_bufferPosition = toRead;
_internalPosition += toRead;
return toRead;
}
else
{
if (count == 0)
{
return 0;
}
int read;
read = Stream.Read(buffer, offset, count);
_internalPosition += read;
return read;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
if (_bufferingEnabled)
{
ValidateBufferState();
}
long orig = _internalPosition;
long targetPos;
// Calculate the absolute target position based on origin
switch (origin)
{
case SeekOrigin.Begin:
targetPos = offset;
break;
case SeekOrigin.Current:
targetPos = _internalPosition + offset;
break;
case SeekOrigin.End:
targetPos = Length + offset;
break;
default:
throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
}
long bufferPos = _internalPosition - _bufferPosition;
if (targetPos >= bufferPos && targetPos <= bufferPos + _bufferedLength)
{
_bufferPosition = (int)(targetPos - bufferPos); //repoint within the buffer
_internalPosition = targetPos;
}
else
{
long newStreamPos =
Stream.Seek(targetPos + _baseInitialPos, SeekOrigin.Begin) - _baseInitialPos;
_internalPosition = newStreamPos;
_bufferPosition = 0;
_bufferedLength = 0;
}
return _internalPosition;
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void WriteByte(byte value)
{
Stream.WriteByte(value);
++_internalPosition;
}
public override void Write(byte[] buffer, int offset, int count)
{
Stream.Write(buffer, offset, count);
_internalPosition += count;
}
}