Skip to content

Commit c4acf17

Browse files
authored
Merge pull request #1233 from adamhathcock/adam/expose-sc-stream
Make SharpCompressStream public
2 parents 268ee19 + 28319f1 commit c4acf17

6 files changed

Lines changed: 66 additions & 47 deletions

File tree

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@
2525
"csharpier.enableDebugLogs": false,
2626
"omnisharp.enableRoslynAnalyzers": true,
2727
"omnisharp.enableEditorConfigSupport": true,
28-
"dotnet-test-explorer.testProjectPath": "tests/**/*.csproj"
28+
"dotnet-test-explorer.testProjectPath": "tests/**/*.csproj",
29+
"chat.tools.terminal.autoApprove": {
30+
"dotnet csharpier": true
31+
}
2932
}

src/SharpCompress/IO/SeekableSharpCompressStream.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,6 @@ internal sealed partial class SeekableSharpCompressStream : SharpCompressStream
1616
/// </summary>
1717
public override bool LeaveStreamOpen { get; }
1818

19-
/// <summary>
20-
/// Gets or sets whether to throw an exception when Dispose is called.
21-
/// Useful for testing to ensure streams are not disposed prematurely.
22-
/// </summary>
23-
public override bool ThrowOnDispose { get; set; }
24-
2519
public SeekableSharpCompressStream(Stream stream, bool leaveStreamOpen = false)
2620
: base(Null, true, false, null)
2721
{

src/SharpCompress/IO/SharpCompressStream.Async.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace SharpCompress.IO;
77

8-
internal partial class SharpCompressStream
8+
public partial class SharpCompressStream
99
{
1010
public override Task<int> ReadAsync(
1111
byte[] buffer,

src/SharpCompress/IO/SharpCompressStream.Create.cs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,69 @@
44

55
namespace SharpCompress.IO;
66

7-
internal partial class SharpCompressStream
7+
public partial class SharpCompressStream
88
{
99
/// <summary>
10-
/// Creates a SharpCompressStream that acts as a passthrough wrapper.
11-
/// No buffering is performed; CanSeek delegates to the underlying stream.
12-
/// The underlying stream will not be disposed when this stream is disposed.
10+
/// Creates a <see cref="SharpCompressStream"/> that acts as a zero-overhead passthrough wrapper
11+
/// around <paramref name="stream"/> without taking ownership of it.
1312
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// This is a thin wrapper: all reads, writes, and seeks are forwarded directly to the underlying
16+
/// stream with no ring-buffer overhead. <see cref="Stream.CanSeek"/> delegates to the underlying
17+
/// stream's own value.
18+
/// </para>
19+
/// <para>
20+
/// The resulting stream does <b>not</b> support <see cref="StartRecording"/>, <see cref="Rewind()"/>,
21+
/// or <see cref="StopRecording"/>. Call <see cref="Create"/> on the passthrough stream to obtain
22+
/// a recording-capable wrapper when needed.
23+
/// </para>
24+
/// <para>
25+
/// Because the stream does not take ownership, the underlying stream is <b>never</b> disposed when
26+
/// this wrapper is disposed. Use this when you need to satisfy an API that expects a
27+
/// <see cref="SharpCompressStream"/> without transferring lifetime responsibility.
28+
/// </para>
29+
/// </remarks>
30+
/// <param name="stream">The underlying stream to wrap. Must not be <see langword="null"/>.</param>
31+
/// <returns>
32+
/// A passthrough <see cref="SharpCompressStream"/> that does not dispose <paramref name="stream"/>.
33+
/// </returns>
1434
public static SharpCompressStream CreateNonDisposing(Stream stream) =>
1535
new(stream, leaveStreamOpen: true, passthrough: true, bufferSize: null);
1636

37+
/// <summary>
38+
/// Creates a <see cref="SharpCompressStream"/> that supports recording and rewinding over
39+
/// <paramref name="stream"/>, choosing the most efficient strategy based on the stream's
40+
/// capabilities.
41+
/// </summary>
42+
/// <remarks>
43+
/// <para><b>Seekable streams</b> — wraps in a thin delegate that calls the underlying
44+
/// stream's native <see cref="Stream.Seek"/> directly. No ring buffer is allocated.
45+
/// <see cref="StartRecording"/> stores the current position; <see cref="Rewind()"/> seeks
46+
/// back to it.</para>
47+
/// <para><b>Non-seekable streams</b> (network streams, compressed streams, pipes) — allocates
48+
/// a ring buffer of <paramref name="bufferSize"/> bytes. All bytes read from the underlying
49+
/// stream are kept in the ring buffer so that <see cref="Rewind()"/> can replay them without
50+
/// re-reading the underlying stream. If more bytes have been read than the ring buffer can hold,
51+
/// a subsequent rewind will throw <see cref="InvalidOperationException"/>; increase
52+
/// <paramref name="bufferSize"/> or <see cref="Common.Constants.RewindableBufferSize"/> to
53+
/// avoid this.</para>
54+
/// <para><b>Already-wrapped streams</b> — if <paramref name="stream"/> is already a
55+
/// <see cref="SharpCompressStream"/> (or a stack that contains one), it is returned as-is to
56+
/// prevent double-wrapping and double-buffering.</para>
57+
/// </remarks>
58+
/// <param name="stream">The underlying stream to wrap. Must not be <see langword="null"/>.</param>
59+
/// <param name="bufferSize">
60+
/// Size in bytes of the ring buffer allocated for non-seekable streams.
61+
/// Defaults to <see cref="Common.Constants.RewindableBufferSize"/> (81 920 bytes) when
62+
/// <see langword="null"/>. Has no effect when <paramref name="stream"/> is seekable, because
63+
/// no ring buffer is needed in that case.
64+
/// </param>
65+
/// <returns>
66+
/// A <see cref="SharpCompressStream"/> wrapping <paramref name="stream"/>. The returned instance
67+
/// owns the stream and will dispose it unless the original source was a non-disposing passthrough
68+
/// wrapper.
69+
/// </returns>
1770
public static SharpCompressStream Create(Stream stream, int? bufferSize = null)
1871
{
1972
var rewindableBufferSize = bufferSize ?? Constants.RewindableBufferSize;
@@ -54,6 +107,6 @@ public static SharpCompressStream Create(Stream stream, int? bufferSize = null)
54107

55108
// For non-seekable streams, create a SharpCompressStream with rolling buffer
56109
// to allow limited backward seeking (required by decompressors that over-read)
57-
return new SharpCompressStream(stream, false, false, bufferSize);
110+
return new SharpCompressStream(stream, false, false, rewindableBufferSize);
58111
}
59112
}

src/SharpCompress/IO/SharpCompressStream.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace SharpCompress.IO;
66

7-
internal partial class SharpCompressStream : Stream, IStreamStack
7+
public partial class SharpCompressStream : Stream, IStreamStack
88
{
99
public virtual Stream BaseStream() => stream;
1010

@@ -38,7 +38,7 @@ internal partial class SharpCompressStream : Stream, IStreamStack
3838
/// Gets or sets whether to throw an exception when Dispose is called.
3939
/// Useful for testing to ensure streams are not disposed prematurely.
4040
/// </summary>
41-
public virtual bool ThrowOnDispose { get; set; }
41+
internal bool ThrowOnDispose { get; set; }
4242

4343
public SharpCompressStream(Stream stream)
4444
{
@@ -181,7 +181,7 @@ public virtual void StartRecording()
181181
// Ensure ring buffer exists
182182
if (_ringBuffer is null)
183183
{
184-
_ringBuffer = new RingBuffer(Constants.BufferSize);
184+
_ringBuffer = new RingBuffer(Constants.RewindableBufferSize);
185185
}
186186

187187
// Mark current position as recording anchor

tests/SharpCompress.Test/packages.lock.json

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -309,30 +309,6 @@
309309
}
310310
}
311311
},
312-
".NETFramework,Version=v4.8/win-x86": {
313-
"Microsoft.Win32.Registry": {
314-
"type": "Transitive",
315-
"resolved": "5.0.0",
316-
"contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
317-
"dependencies": {
318-
"System.Security.AccessControl": "5.0.0",
319-
"System.Security.Principal.Windows": "5.0.0"
320-
}
321-
},
322-
"System.Security.AccessControl": {
323-
"type": "Transitive",
324-
"resolved": "5.0.0",
325-
"contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
326-
"dependencies": {
327-
"System.Security.Principal.Windows": "5.0.0"
328-
}
329-
},
330-
"System.Security.Principal.Windows": {
331-
"type": "Transitive",
332-
"resolved": "5.0.0",
333-
"contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
334-
}
335-
},
336312
"net10.0": {
337313
"AwesomeAssertions": {
338314
"type": "Direct",
@@ -545,13 +521,6 @@
545521
"resolved": "8.0.0",
546522
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
547523
}
548-
},
549-
"net10.0/win-x86": {
550-
"Microsoft.Win32.Registry": {
551-
"type": "Transitive",
552-
"resolved": "5.0.0",
553-
"contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg=="
554-
}
555524
}
556525
}
557526
}

0 commit comments

Comments
 (0)