Compare commits

...

4 Commits

Author SHA1 Message Date
Adam Hathcock
b7f41acd19 Merge pull request #1220 from adamhathcock/adam/issue-936
Expose SharpCompressStream to allow ringbuffer to be used on non-seekable streams
2026-02-14 10:35:19 +00:00
Adam Hathcock
71d993fab1 Update src/SharpCompress/IO/SharpCompressStream.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-14 10:35:02 +00:00
Adam Hathcock
4546a3acd2 add some docs 2026-02-14 10:26:33 +00:00
Adam Hathcock
a2a1b2e8fd Expose SharpCompressStream to allow ringbuffer to be used on non-seekable streams 2026-02-14 10:16:01 +00:00
6 changed files with 83 additions and 3 deletions

View File

@@ -485,6 +485,31 @@ using (var archive = ZipArchive.CreateArchive())
}
```
### Buffered Forward-Only Streams
`SharpCompressStream` can wrap streams with buffering for forward-only scenarios:
```csharp
// Wrap a non-seekable stream with buffering
using (var bufferedStream = new SharpCompressStream(rawStream))
{
// Provides ring buffer functionality for reading ahead
// and seeking within buffered data
using (var reader = ReaderFactory.OpenReader(bufferedStream))
{
while (reader.MoveToNextEntry())
{
reader.WriteEntryToDirectory(@"C:\output");
}
}
}
```
Useful for:
- Non-seekable streams (network streams, pipes)
- Forward-only reading with limited look-ahead
- Buffering unbuffered streams for better performance
### Extract Specific Files
```csharp

View File

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
namespace SharpCompress.IO;
internal partial class SharpCompressStream
public partial class SharpCompressStream
{
public override Task<int> ReadAsync(
byte[] buffer,

View File

@@ -4,7 +4,7 @@ using SharpCompress.Common;
namespace SharpCompress.IO;
internal partial class SharpCompressStream
public partial class SharpCompressStream
{
/// <summary>
/// Creates a SharpCompressStream that acts as a passthrough wrapper.

View File

@@ -4,7 +4,19 @@ using SharpCompress.Common;
namespace SharpCompress.IO;
internal partial class SharpCompressStream : Stream, IStreamStack
/// <summary>
/// Stream wrapper that provides optional ring-buffered reading for non-seekable
/// or forward-only streams, enabling limited backward seeking required by some
/// decompressors and archive formats.
/// </summary>
/// <remarks>
/// In most cases, callers should obtain an instance via the static
/// <c>SharpCompressStream.Create(...)</c> methods rather than constructing this
/// class directly. The <c>Create</c> methods select an appropriate configuration
/// (such as passthrough vs buffered mode and buffer size) for the underlying
/// stream and usage scenario.
/// </remarks>
public partial class SharpCompressStream : Stream, IStreamStack
{
public virtual Stream BaseStream() => stream;

View File

@@ -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);
}
}

View File

@@ -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);
}
}