Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Buffered example uses the direct constructor, which doesn't enable ring buffering

SharpCompressStream(Stream) doesn't allocate a ring buffer, so the example won't provide the buffering described in the text. Use SharpCompressStream.Create(...) (or pass a buffer size) to ensure the wrapper is configured for non-seekable buffering.

Suggested change
using (var bufferedStream = new SharpCompressStream(rawStream))
using (var bufferedStream = SharpCompressStream.Create(rawStream))

Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation example uses the SharpCompressStream constructor directly (new SharpCompressStream(rawStream)), but according to the class-level documentation in SharpCompressStream.cs, users should prefer using the static SharpCompressStream.Create(...) methods. Consider updating this example to use SharpCompressStream.Create(rawStream) instead to align with the recommended usage pattern and ensure proper configuration (seekable vs buffered mode) is selected automatically.

Suggested change
using (var bufferedStream = new SharpCompressStream(rawStream))
using (var bufferedStream = SharpCompressStream.Create(rawStream))

Copilot uses AI. Check for mistakes.
{
// 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
Expand Down
2 changes: 1 addition & 1 deletion src/SharpCompress/IO/SharpCompressStream.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace SharpCompress.IO;

internal partial class SharpCompressStream
public partial class SharpCompressStream
{
public override Task<int> ReadAsync(
byte[] buffer,
Expand Down
2 changes: 1 addition & 1 deletion src/SharpCompress/IO/SharpCompressStream.Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace SharpCompress.IO;

internal partial class SharpCompressStream
public partial class SharpCompressStream
{
/// <summary>
/// Creates a SharpCompressStream that acts as a passthrough wrapper.
Expand Down
14 changes: 13 additions & 1 deletion src/SharpCompress/IO/SharpCompressStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@

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
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public constructor at line 55 (public SharpCompressStream(Stream stream)) lacks XML documentation. According to the class-level remarks, users should prefer using SharpCompressStream.Create(...) methods instead of directly constructing instances. Consider adding XML documentation that explains this constructor's purpose and recommends using the Create methods for most use cases.

Copilot uses AI. Check for mistakes.
{
public virtual Stream BaseStream() => stream;

Expand Down
21 changes: 21 additions & 0 deletions tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.IO;
using System.Threading.Tasks;
using SharpCompress.Compressors.Xz;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;

Expand Down Expand Up @@ -34,4 +35,24 @@ public async ValueTask CanReadIndexedStreamAsync()
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);
}
}
22 changes: 22 additions & 0 deletions tests/SharpCompress.Test/Xz/XZStreamTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.IO;
using SharpCompress.Compressors.Xz;
using SharpCompress.IO;
using SharpCompress.Test.Mocks;
using Xunit;

namespace SharpCompress.Test.Xz;
Expand Down Expand Up @@ -32,4 +34,24 @@ public void CanReadIndexedStream()
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);
}
}