From a53a88b9dc6bea052ae18b83424596ba9609e4e3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Feb 2026 09:10:21 +0000 Subject: [PATCH 1/4] Make SharpCompressStream public --- .vscode/settings.json | 5 ++++- src/SharpCompress/IO/SeekableSharpCompressStream.cs | 6 ------ src/SharpCompress/IO/SharpCompressStream.Async.cs | 2 +- src/SharpCompress/IO/SharpCompressStream.Create.cs | 2 +- src/SharpCompress/IO/SharpCompressStream.cs | 4 ++-- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 07998539..1d13071f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,5 +25,8 @@ "csharpier.enableDebugLogs": false, "omnisharp.enableRoslynAnalyzers": true, "omnisharp.enableEditorConfigSupport": true, - "dotnet-test-explorer.testProjectPath": "tests/**/*.csproj" + "dotnet-test-explorer.testProjectPath": "tests/**/*.csproj", + "chat.tools.terminal.autoApprove": { + "dotnet csharpier": true + } } diff --git a/src/SharpCompress/IO/SeekableSharpCompressStream.cs b/src/SharpCompress/IO/SeekableSharpCompressStream.cs index 28b67c4e..c49e5be5 100644 --- a/src/SharpCompress/IO/SeekableSharpCompressStream.cs +++ b/src/SharpCompress/IO/SeekableSharpCompressStream.cs @@ -16,12 +16,6 @@ internal sealed partial class SeekableSharpCompressStream : SharpCompressStream /// public override bool LeaveStreamOpen { get; } - /// - /// Gets or sets whether to throw an exception when Dispose is called. - /// Useful for testing to ensure streams are not disposed prematurely. - /// - public override bool ThrowOnDispose { get; set; } - public SeekableSharpCompressStream(Stream stream, bool leaveStreamOpen = false) : base(Null, true, false, null) { diff --git a/src/SharpCompress/IO/SharpCompressStream.Async.cs b/src/SharpCompress/IO/SharpCompressStream.Async.cs index e18a47a1..a36a9862 100644 --- a/src/SharpCompress/IO/SharpCompressStream.Async.cs +++ b/src/SharpCompress/IO/SharpCompressStream.Async.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace SharpCompress.IO; -internal partial class SharpCompressStream +public partial class SharpCompressStream { public override Task ReadAsync( byte[] buffer, diff --git a/src/SharpCompress/IO/SharpCompressStream.Create.cs b/src/SharpCompress/IO/SharpCompressStream.Create.cs index 3a886a80..8011641b 100644 --- a/src/SharpCompress/IO/SharpCompressStream.Create.cs +++ b/src/SharpCompress/IO/SharpCompressStream.Create.cs @@ -4,7 +4,7 @@ using SharpCompress.Common; namespace SharpCompress.IO; -internal partial class SharpCompressStream +public partial class SharpCompressStream { /// /// Creates a SharpCompressStream that acts as a passthrough wrapper. diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs index a7c09072..c04c06a3 100644 --- a/src/SharpCompress/IO/SharpCompressStream.cs +++ b/src/SharpCompress/IO/SharpCompressStream.cs @@ -4,7 +4,7 @@ using SharpCompress.Common; namespace SharpCompress.IO; -internal partial class SharpCompressStream : Stream, IStreamStack +public partial class SharpCompressStream : Stream, IStreamStack { public virtual Stream BaseStream() => stream; @@ -38,7 +38,7 @@ internal partial class SharpCompressStream : Stream, IStreamStack /// Gets or sets whether to throw an exception when Dispose is called. /// Useful for testing to ensure streams are not disposed prematurely. /// - public virtual bool ThrowOnDispose { get; set; } + internal bool ThrowOnDispose { get; set; } public SharpCompressStream(Stream stream) { From 0778b1308645d8b0fdda6565e36065aa86d14977 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Feb 2026 09:15:40 +0000 Subject: [PATCH 2/4] add more docs --- .../IO/SharpCompressStream.Create.cs | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/SharpCompress/IO/SharpCompressStream.Create.cs b/src/SharpCompress/IO/SharpCompressStream.Create.cs index 8011641b..ad4ca7ab 100644 --- a/src/SharpCompress/IO/SharpCompressStream.Create.cs +++ b/src/SharpCompress/IO/SharpCompressStream.Create.cs @@ -7,13 +7,66 @@ namespace SharpCompress.IO; public 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. + /// Creates a that acts as a zero-overhead passthrough wrapper + /// around without taking ownership of it. /// + /// + /// + /// This is a thin wrapper: all reads, writes, and seeks are forwarded directly to the underlying + /// stream with no ring-buffer overhead. delegates to the underlying + /// stream's own value. + /// + /// + /// The resulting stream does not support , , + /// or . Call on the passthrough stream to obtain + /// a recording-capable wrapper when needed. + /// + /// + /// Because the stream does not take ownership, the underlying stream is never disposed when + /// this wrapper is disposed. Use this when you need to satisfy an API that expects a + /// without transferring lifetime responsibility. + /// + /// + /// The underlying stream to wrap. Must not be . + /// + /// A passthrough that does not dispose . + /// public static SharpCompressStream CreateNonDisposing(Stream stream) => new(stream, leaveStreamOpen: true, passthrough: true, bufferSize: null); + /// + /// Creates a that supports recording and rewinding over + /// , choosing the most efficient strategy based on the stream's + /// capabilities. + /// + /// + /// Seekable streams — wraps in a thin delegate that calls the underlying + /// stream's native directly. No ring buffer is allocated. + /// stores the current position; seeks + /// back to it. + /// Non-seekable streams (network streams, compressed streams, pipes) — allocates + /// a ring buffer of bytes. All bytes read from the underlying + /// stream are kept in the ring buffer so that can replay them without + /// re-reading the underlying stream. If more bytes have been read than the ring buffer can hold, + /// a subsequent rewind will throw ; increase + /// or to + /// avoid this. + /// Already-wrapped streams — if is already a + /// (or a stack that contains one), it is returned as-is to + /// prevent double-wrapping and double-buffering. + /// + /// The underlying stream to wrap. Must not be . + /// + /// Size in bytes of the ring buffer allocated for non-seekable streams. + /// Defaults to (81 920 bytes) when + /// . Has no effect when is seekable, because + /// no ring buffer is needed in that case. + /// + /// + /// A wrapping . The returned instance + /// owns the stream and will dispose it unless the original source was a non-disposing passthrough + /// wrapper. + /// public static SharpCompressStream Create(Stream stream, int? bufferSize = null) { var rewindableBufferSize = bufferSize ?? Constants.RewindableBufferSize; From 5a0d6dd333a8fc22eaf6b12d851812de02974f2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:38:41 +0000 Subject: [PATCH 3/4] Initial plan From d0bfdfd6abc012b0eaad5286db4d67cc36146081 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:41:26 +0000 Subject: [PATCH 4/4] Fix buffer size alignment: use RewindableBufferSize consistently in Create() and StartRecording() Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --- .../IO/SharpCompressStream.Create.cs | 2 +- src/SharpCompress/IO/SharpCompressStream.cs | 2 +- tests/SharpCompress.Test/packages.lock.json | 31 ------------------- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/SharpCompress/IO/SharpCompressStream.Create.cs b/src/SharpCompress/IO/SharpCompressStream.Create.cs index ad4ca7ab..37d91ebf 100644 --- a/src/SharpCompress/IO/SharpCompressStream.Create.cs +++ b/src/SharpCompress/IO/SharpCompressStream.Create.cs @@ -107,6 +107,6 @@ public partial class SharpCompressStream // 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, false, false, bufferSize); + return new SharpCompressStream(stream, false, false, rewindableBufferSize); } } diff --git a/src/SharpCompress/IO/SharpCompressStream.cs b/src/SharpCompress/IO/SharpCompressStream.cs index c04c06a3..aef14790 100644 --- a/src/SharpCompress/IO/SharpCompressStream.cs +++ b/src/SharpCompress/IO/SharpCompressStream.cs @@ -181,7 +181,7 @@ public partial class SharpCompressStream : Stream, IStreamStack // Ensure ring buffer exists if (_ringBuffer is null) { - _ringBuffer = new RingBuffer(Constants.BufferSize); + _ringBuffer = new RingBuffer(Constants.RewindableBufferSize); } // Mark current position as recording anchor diff --git a/tests/SharpCompress.Test/packages.lock.json b/tests/SharpCompress.Test/packages.lock.json index 1f290aa4..972b9be4 100644 --- a/tests/SharpCompress.Test/packages.lock.json +++ b/tests/SharpCompress.Test/packages.lock.json @@ -309,30 +309,6 @@ } } }, - ".NETFramework,Version=v4.8/win-x86": { - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.AccessControl": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", - "dependencies": { - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - } - }, "net10.0": { "AwesomeAssertions": { "type": "Direct", @@ -545,13 +521,6 @@ "resolved": "8.0.0", "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==" } - }, - "net10.0/win-x86": { - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" - } } } } \ No newline at end of file