From a2a1b2e8fd436719c13d6467e9bbfe4572cfe331 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Sat, 14 Feb 2026 10:16:01 +0000 Subject: [PATCH] Expose SharpCompressStream to allow ringbuffer to be used on non-seekable streams --- .../IO/SharpCompressStream.Async.cs | 2 +- .../IO/SharpCompressStream.Create.cs | 2 +- src/SharpCompress/IO/SharpCompressStream.cs | 2 +- .../Xz/XZStreamAsyncTests.cs | 21 ++++++++++++++++++ tests/SharpCompress.Test/Xz/XZStreamTests.cs | 22 +++++++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) 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..d5a7dfcc 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; diff --git a/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs b/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs index a2276602..9402e7c4 100644 --- a/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs +++ b/tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading.Tasks; using SharpCompress.Compressors.Xz; +using SharpCompress.IO; using SharpCompress.Test.Mocks; using Xunit; @@ -34,4 +35,24 @@ public class XzStreamAsyncTests : XzTestsBase var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false); Assert.Equal(OriginalIndexed, uncompressed); } + + [Fact] + public async ValueTask CanReadNonSeekableStreamAsync() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(Compressed)); + var xz = new XZStream(SharpCompressStream.Create(nonSeekable)); + using var sr = new StreamReader(new AsyncOnlyStream(xz)); + var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false); + Assert.Equal(Original, uncompressed); + } + + [Fact] + public async ValueTask CanReadNonSeekableEmptyStreamAsync() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(CompressedEmpty)); + var xz = new XZStream(SharpCompressStream.Create(nonSeekable)); + using var sr = new StreamReader(new AsyncOnlyStream(xz)); + var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false); + Assert.Equal(OriginalEmpty, uncompressed); + } } diff --git a/tests/SharpCompress.Test/Xz/XZStreamTests.cs b/tests/SharpCompress.Test/Xz/XZStreamTests.cs index 02c5020c..80ad5dc3 100644 --- a/tests/SharpCompress.Test/Xz/XZStreamTests.cs +++ b/tests/SharpCompress.Test/Xz/XZStreamTests.cs @@ -1,5 +1,7 @@ using System.IO; using SharpCompress.Compressors.Xz; +using SharpCompress.IO; +using SharpCompress.Test.Mocks; using Xunit; namespace SharpCompress.Test.Xz; @@ -32,4 +34,24 @@ public class XzStreamTests : XzTestsBase var uncompressed = sr.ReadToEnd(); Assert.Equal(OriginalIndexed, uncompressed); } + + [Fact] + public void CanReadNonSeekableStream() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(Compressed)); + var xz = new XZStream(SharpCompressStream.Create(nonSeekable)); + using var sr = new StreamReader(xz); + var uncompressed = sr.ReadToEnd(); + Assert.Equal(Original, uncompressed); + } + + [Fact] + public void CanReadNonSeekableEmptyStream() + { + var nonSeekable = new ForwardOnlyStream(new MemoryStream(CompressedEmpty)); + var xz = new XZStream(SharpCompressStream.Create(nonSeekable)); + using var sr = new StreamReader(xz); + var uncompressed = sr.ReadToEnd(); + Assert.Equal(OriginalEmpty, uncompressed); + } }