diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs index b1ddb576..febd59b5 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs @@ -72,9 +72,9 @@ internal sealed partial class StreamingZipHeaderFactory ) { _headerFactory = headerFactory; - // Use EnsureSeekable to avoid double-wrapping if stream is already a SharpCompressStream, + // Use Create to avoid double-wrapping if stream is already a SharpCompressStream, // and to preserve seekability for DataDescriptorStream which needs to seek backward - _sharpCompressStream = SharpCompressStream.EnsureSeekable(stream); + _sharpCompressStream = SharpCompressStream.Create(stream); _reader = new AsyncBinaryReader(_sharpCompressStream, leaveOpen: true); _cancellationToken = cancellationToken; } diff --git a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs index cc91e184..d68e7177 100644 --- a/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs +++ b/src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs @@ -20,9 +20,9 @@ internal partial class StreamingZipHeaderFactory : ZipHeaderFactory internal IEnumerable ReadStreamHeader(Stream stream) { - // Use EnsureSeekable to avoid double-wrapping if stream is already a SharpCompressStream, + // Use Create to avoid double-wrapping if stream is already a SharpCompressStream, // and to preserve seekability for DataDescriptorStream which needs to seek backward - var sharpCompressStream = SharpCompressStream.EnsureSeekable(stream); + var sharpCompressStream = SharpCompressStream.Create(stream); var reader = new BinaryReader( sharpCompressStream, System.Text.Encoding.Default, diff --git a/src/SharpCompress/IO/SharpCompressStream.Create.cs b/src/SharpCompress/IO/SharpCompressStream.Create.cs new file mode 100644 index 00000000..5f30a585 --- /dev/null +++ b/src/SharpCompress/IO/SharpCompressStream.Create.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using SharpCompress.Common; + +namespace SharpCompress.IO; + +internal partial class SharpCompressStream +{ + /// + /// Creates a SharpCompressStream that acts as a passthrough wrapper. + /// No buffering is performed; CanSeek delegates to the underlying stream. + /// The underlying stream will not be disposed when this stream is disposed. + /// + public static SharpCompressStream CreateNonDisposing(Stream stream) => + new(stream, leaveStreamOpen: true, passthrough: true); + + public static SharpCompressStream Create( + Stream stream, + int? rewindableBufferSize = null + ) + { + int bufferSize = rewindableBufferSize ?? Constants.RewindableBufferSize; + + // If it's a passthrough SharpCompressStream, unwrap it and create proper seekable wrapper + if (stream is SharpCompressStream sharpCompressStream) + { + if (sharpCompressStream._isPassthrough) + { + // Unwrap the passthrough and create appropriate wrapper + var underlying = sharpCompressStream.stream; + if (underlying.CanSeek) + { + // Create SeekableSharpCompressStream that preserves LeaveStreamOpen + return new SeekableSharpCompressStream(underlying) + { + LeaveStreamOpen = true, // Preserve non-disposing behavior + }; + } + // Non-seekable underlying stream - wrap with rolling buffer + return new SharpCompressStream(underlying, bufferSize) { LeaveStreamOpen = true }; + } + // Not passthrough - return as-is + return sharpCompressStream; + } + + // Check if stream is wrapping a SharpCompressStream (e.g., via IStreamStack) + if (stream is IStreamStack streamStack) + { + var underlying = streamStack.GetStream(); + if (underlying is not null) + { + return underlying; + } + } + + if (stream.CanSeek) + { + return new SeekableSharpCompressStream(stream); + } + + // For non-seekable streams, create a SharpCompressStream with rolling buffer + // to allow limited backward seeking (required by decompressors that over-read) + return new SharpCompressStream(stream, bufferSize); + } +} diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs index eae6217f..adeca170 100644 --- a/src/SharpCompress/IO/SharpCompressStream.cs +++ b/src/SharpCompress/IO/SharpCompressStream.cs @@ -76,14 +76,6 @@ internal partial class SharpCompressStream : Stream, IStreamStack _logicalPosition = 0; } - /// - /// Creates a SharpCompressStream that acts as a passthrough wrapper. - /// No buffering is performed; CanSeek delegates to the underlying stream. - /// The underlying stream will not be disposed when this stream is disposed. - /// - public static SharpCompressStream CreateNonDisposing(Stream stream) => - new(stream, leaveStreamOpen: true, passthrough: true); - /// /// Gets whether the stream is actively recording reads to the ring buffer. /// @@ -121,7 +113,7 @@ internal partial class SharpCompressStream : Stream, IStreamStack if (_isPassthrough) { throw new InvalidOperationException( - "Rewind cannot be called on a passthrough stream. Use EnsureSeekable() first." + "Rewind cannot be called on a passthrough stream. Use Create() first." ); } @@ -160,7 +152,7 @@ internal partial class SharpCompressStream : Stream, IStreamStack if (_isPassthrough) { throw new InvalidOperationException( - "StopRecording cannot be called on a passthrough stream. Use EnsureSeekable() first." + "StopRecording cannot be called on a passthrough stream. Use Create() first." ); } if (!IsRecording) @@ -180,61 +172,12 @@ internal partial class SharpCompressStream : Stream, IStreamStack // (frozen recording mode) until Rewind(stopRecording: true) is called } - public static SharpCompressStream EnsureSeekable( - Stream stream, - int? rewindableBufferSize = null - ) - { - int bufferSize = rewindableBufferSize ?? Constants.RewindableBufferSize; - - // If it's a passthrough SharpCompressStream, unwrap it and create proper seekable wrapper - if (stream is SharpCompressStream sharpCompressStream) - { - if (sharpCompressStream._isPassthrough) - { - // Unwrap the passthrough and create appropriate wrapper - var underlying = sharpCompressStream.stream; - if (underlying.CanSeek) - { - // Create SeekableSharpCompressStream that preserves LeaveStreamOpen - return new SeekableSharpCompressStream(underlying) - { - LeaveStreamOpen = true, // Preserve non-disposing behavior - }; - } - // Non-seekable underlying stream - wrap with rolling buffer - return new SharpCompressStream(underlying, bufferSize) { LeaveStreamOpen = true }; - } - // Not passthrough - return as-is - return sharpCompressStream; - } - - // Check if stream is wrapping a SharpCompressStream (e.g., via IStreamStack) - if (stream is IStreamStack streamStack) - { - var underlying = streamStack.GetStream(); - if (underlying is not null) - { - return underlying; - } - } - - if (stream.CanSeek) - { - return new SeekableSharpCompressStream(stream); - } - - // For non-seekable streams, create a SharpCompressStream with rolling buffer - // to allow limited backward seeking (required by decompressors that over-read) - return new SharpCompressStream(stream, bufferSize); - } - public virtual void StartRecording() { if (_isPassthrough) { throw new InvalidOperationException( - "StartRecording cannot be called on a passthrough stream. Use EnsureSeekable() first." + "StartRecording cannot be called on a passthrough stream. Use Create() first." ); } if (IsRecording) diff --git a/src/SharpCompress/Readers/ReaderFactory.Async.cs b/src/SharpCompress/Readers/ReaderFactory.Async.cs index 2293ea07..a1302fc0 100644 --- a/src/SharpCompress/Readers/ReaderFactory.Async.cs +++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs @@ -54,7 +54,7 @@ public static partial class ReaderFactory stream.NotNull(nameof(stream)); options ??= new ReaderOptions() { LeaveStreamOpen = false }; - var sharpCompressStream = SharpCompressStream.EnsureSeekable( + var sharpCompressStream = SharpCompressStream.Create( stream, options.RewindableBufferSize ); diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 180c2809..7aad0c00 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -34,7 +34,7 @@ public static partial class ReaderFactory stream.NotNull(nameof(stream)); options ??= new ReaderOptions() { LeaveStreamOpen = false }; - var sharpCompressStream = SharpCompressStream.EnsureSeekable( + var sharpCompressStream = SharpCompressStream.Create( stream, options.RewindableBufferSize ); diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index c2068789..562ec362 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -57,7 +57,7 @@ public partial class TarReader : AbstractReader { stream.NotNull(nameof(stream)); options = options ?? new ReaderOptions(); - var sharpCompressStream = SharpCompressStream.EnsureSeekable( + var sharpCompressStream = SharpCompressStream.Create( stream, options.RewindableBufferSize );