Skip to content

Clean up again#1187

Merged
adamhathcock merged 9 commits intomasterfrom
adam/cleanup
Feb 6, 2026
Merged

Clean up again#1187
adamhathcock merged 9 commits intomasterfrom
adam/cleanup

Conversation

@adamhathcock
Copy link
Owner

This pull request refactors how SharpCompressStream wrappers are created and managed, replacing the old EnsureSeekable method with a new, more flexible Create method. It also standardizes the internal stream field naming and updates all usages to leverage the new creation logic. These changes improve clarity, consistency, and future extensibility for stream handling in the library.

Stream creation and management refactor:

  • Added the new SharpCompressStream.Create method, which replaces EnsureSeekable and provides a unified way to wrap streams, handling passthrough, seekable, and buffered cases. The old EnsureSeekable logic was removed. (src/SharpCompress/IO/SharpCompressStream.Create.cs, src/SharpCompress/IO/SharpCompressStream.cs) [1] [2]
  • Updated all usages of EnsureSeekable to use the new Create method, including in ZIP and TAR header factories and reader factories. (src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs, src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs, src/SharpCompress/Readers/ReaderFactory.Async.cs, src/SharpCompress/Readers/ReaderFactory.cs, src/SharpCompress/Readers/Tar/TarReader.cs) [1] [2] [3] [4] [5]

Internal field and property standardization:

  • Renamed internal stream field from _underlyingStream to _stream across all relevant files, and updated all property and method references accordingly for consistency. (src/SharpCompress/IO/SeekableSharpCompressStream.cs, src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs) [1] [2] [3] [4] [5]
  • Updated constructors and base class calls to use the new field and property structure, and removed the obsolete NullStream class. (src/SharpCompress/IO/SeekableSharpCompressStream.cs) [1] [2]

Buffering and recording improvements:

  • Standardized buffer size constants and logic, replacing DefaultRollingBufferSize with Constants.BufferSize and ensuring consistent buffer allocation for rolling buffers. (src/SharpCompress/IO/SharpCompressStream.cs)
  • Updated exception messages to reference the new Create method instead of the deprecated EnsureSeekable. (src/SharpCompress/IO/SharpCompressStream.cs) [1] [2] [3]

Minor logic and property fixes:

  • Improved the logic for CanSeek and CanWrite properties to better handle passthrough and seekable scenarios. (src/SharpCompress/IO/SharpCompressStream.cs)
  • Fixed property initialization for LeaveStreamOpen to be read-only and set in the constructor. (src/SharpCompress/IO/SharpCompressStream.cs)

Let me know if you want a deeper dive into how the new Create method works or why these changes improve the library!

Copilot AI review requested due to automatic review settings February 5, 2026 09:07
return new SeekableSharpCompressStream(underlying);
}
// Non-seekable underlying stream - wrap with rolling buffer
return new SharpCompressStream(underlying, true, false, rewindableBufferSize);
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: Inconsistent LeaveStreamOpen behavior

When unwrapping a passthrough SharpCompressStream with a non-seekable underlying stream, the new stream is created with leaveStreamOpen: true. However, the original passthrough stream's LeaveStreamOpen property is not being respected. If the original stream had LeaveStreamOpen = false, this change in behavior could cause the underlying stream to be left open when it should have been disposed.

Consider preserving the original stream's LeaveStreamOpen value from sharpCompressStream.LeaveStreamOpen.

if (_ringBuffer is null)
{
_ringBuffer = new RingBuffer(DefaultRollingBufferSize);
_ringBuffer = new RingBuffer(Constants.BufferSize);
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: Inconsistent buffer size usage

StartRecording() uses Constants.BufferSize (81920) when creating the ring buffer, but the Create() method uses Constants.RewindableBufferSize (also 81920 by default) for the same purpose. While these happen to have the same default value, they are semantically different constants.

If a user sets ReaderOptions.RewindableBufferSize to a custom value, StartRecording() will still use Constants.BufferSize instead of respecting the configured buffer size. This could lead to unexpected behavior where the recording buffer size differs from what the user configured.

@kiloconnect
Copy link
Contributor

kiloconnect bot commented Feb 5, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

This PR refactors the SharpCompress stream handling with the following improvements:

Changes Overview

  1. Renamed EnsureSeekable to Create - Better reflects the method's purpose of creating appropriate stream wrappers
  2. Refactored constructor hierarchy - Introduced a protected constructor with bufferSize parameter for better flexibility
  3. Renamed _underlyingStream to _stream in SeekableSharpCompressStream for consistency
  4. Made LeaveStreamOpen read-only - Changed from mutable property to init-only via constructor
  5. Removed redundant NullStream class - The inner class was not being used
  6. Updated all call sites - Consistent use of named parameter bufferSize: across ReaderFactory and TarReader

Code Quality

  • The refactoring maintains backward compatibility
  • Proper null checking is preserved
  • Stream disposal logic is correctly maintained
  • Documentation comments are updated appropriately
Files Reviewed (9 files)
  • src/SharpCompress/IO/SharpCompressStream.Create.cs - New factory methods
  • src/SharpCompress/IO/SharpCompressStream.cs - Refactored base class
  • src/SharpCompress/IO/SeekableSharpCompressStream.cs - Renamed field, removed NullStream
  • src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs - Updated field references
  • src/SharpCompress/Readers/ReaderFactory.cs - Updated API call
  • src/SharpCompress/Readers/ReaderFactory.Async.cs - Updated API call
  • src/SharpCompress/Readers/Tar/TarReader.cs - Updated API call
  • src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs - Updated API call
  • src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs - Updated API call

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request refactors the stream creation and management logic in SharpCompress, replacing the EnsureSeekable method with a new Create method and standardizing internal field naming from _underlyingStream to _stream.

Changes:

  • Introduced SharpCompressStream.Create method to replace EnsureSeekable, providing unified stream wrapping logic
  • Renamed internal stream field from _underlyingStream to _stream in SeekableSharpCompressStream classes
  • Made LeaveStreamOpen property read-only in SharpCompressStream base class
  • Updated all call sites to use the new Create method with named parameter syntax

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/SharpCompress/IO/SharpCompressStream.Create.cs New file containing the Create and CreateNonDisposing factory methods
src/SharpCompress/IO/SharpCompressStream.cs Removed EnsureSeekable method, made LeaveStreamOpen read-only, updated exception messages
src/SharpCompress/IO/SeekableSharpCompressStream.cs Renamed _underlyingStream to _stream, removed NullStream class, updated constructor
src/SharpCompress/IO/SeekableSharpCompressStream.Async.cs Updated all references from _underlyingStream to _stream
src/SharpCompress/Readers/Tar/TarReader.cs Changed from EnsureSeekable to Create with named parameter
src/SharpCompress/Readers/ReaderFactory.cs Changed from EnsureSeekable to Create with named parameter
src/SharpCompress/Readers/ReaderFactory.Async.cs Changed from EnsureSeekable to Create with named parameter
src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.cs Changed from EnsureSeekable to Create, updated comment
src/SharpCompress/Common/Zip/StreamingZipHeaderFactory.Async.cs Changed from EnsureSeekable to Create, updated comment
Comments suppressed due to low confidence (2)

src/SharpCompress/IO/SharpCompressStream.Create.cs:60

  • There's a spacing issue in the code. The comma should be followed by a space: false,false should be false, false. This violates standard C# formatting conventions.
    src/SharpCompress/IO/SharpCompressStream.Create.cs:60
  • The parameter passed here is bufferSize (which can be null), but earlier in the method at line 22, rewindableBufferSize is calculated as the fallback value. This should be rewindableBufferSize to ensure consistency with how the buffer size is handled elsewhere in the Create method.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 31 to 34
return new SeekableSharpCompressStream(underlying);
}
// Non-seekable underlying stream - wrap with rolling buffer
return new SharpCompressStream(underlying, true, false, rewindableBufferSize);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The comment states "Create SeekableSharpCompressStream that preserves LeaveStreamOpen", but the code doesn't actually preserve the LeaveStreamOpen value from the passthrough stream. The newly created SeekableSharpCompressStream will have LeaveStreamOpen default to false. If preservation is intended, the code should set LeaveStreamOpen = sharpCompressStream.LeaveStreamOpen after creating the instance.

Suggested change
return new SeekableSharpCompressStream(underlying);
}
// Non-seekable underlying stream - wrap with rolling buffer
return new SharpCompressStream(underlying, true, false, rewindableBufferSize);
var seekable = new SeekableSharpCompressStream(underlying)
{
LeaveStreamOpen = sharpCompressStream.LeaveStreamOpen
};
return seekable;
}
// Non-seekable underlying stream - wrap with rolling buffer
return new SharpCompressStream(underlying, sharpCompressStream.LeaveStreamOpen, false, rewindableBufferSize);

Copilot uses AI. Check for mistakes.
adamhathcock and others added 2 commits February 5, 2026 09:16
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 5, 2026 09:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return underlying;
}
}

Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Inconsistent buffer size handling: Line 34 uses rewindableBufferSize (which is never null and defaults to Constants.RewindableBufferSize), but line 57 uses bufferSize which can be null. This means that unwrapping a passthrough stream will always create a buffer, while creating from a raw non-seekable stream might not create a buffer if bufferSize is null. This inconsistency could lead to unexpected behavior. Consider using rewindableBufferSize here for consistency.

Copilot uses AI. Check for mistakes.
if (underlying.CanSeek)
{
// Create SeekableSharpCompressStream that preserves LeaveStreamOpen
return new SeekableSharpCompressStream(
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Inconsistent LeaveStreamOpen behavior: When creating a SeekableSharpCompressStream from an unwrapped passthrough stream (line 31) or a raw seekable stream (line 52), the LeaveStreamOpen property is not explicitly set, so it defaults to false (the default value for bool). However, the comment on line 30 says "preserves LeaveStreamOpen", which is misleading since the original passthrough stream had LeaveStreamOpen=true. This means the underlying stream will be disposed when it shouldn't be, breaking the intended behavior. Consider passing leaveStreamOpen to the SeekableSharpCompressStream constructor.

Copilot uses AI. Check for mistakes.
)
{
this.stream = stream;
LeaveStreamOpen = leaveStreamOpen;
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Cannot assign to LeaveStreamOpen because it's declared as a get-only auto-property on line 35. Get-only auto-properties can only be set in a constructor body of the same class where they're declared, not in a different constructor. This code will not compile. The property needs either a setter (e.g., public virtual bool LeaveStreamOpen { get; protected set; }) or should be declared with init (e.g., public virtual bool LeaveStreamOpen { get; init; }).

Copilot uses AI. Check for mistakes.
private bool _isDisposed;

/// <summary>
/// Gets or sets whether to leave the underlying stream open when disposed.
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Documentation mismatch: The comment says "Gets or sets" but the property is get-only. Since the property cannot be set after construction, the documentation should say "Gets whether to leave the underlying stream open when disposed."

Suggested change
/// Gets or sets whether to leave the underlying stream open when disposed.
/// Gets whether to leave the underlying stream open when disposed.

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 37
@@ -33,44 +33,45 @@ public SeekableSharpCompressStream(Stream stream)
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
_underlyingStream = stream;

LeaveStreamOpen = leaveStreamOpen;
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The SeekableSharpCompressStream constructor should accept a leaveStreamOpen parameter to support the use case in Create() where LeaveStreamOpen behavior needs to be preserved when unwrapping passthrough streams. Currently, there's no way to control whether the underlying stream is disposed, which breaks the intended behavior described in the comment on line 30.

Copilot uses AI. Check for mistakes.
Comment on lines 40 to 46
false,
rewindableBufferSize
);
}
// Not passthrough - return as-is
return sharpCompressStream;
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

Inconsistent handling of passthrough streams: When the stream is directly a passthrough SharpCompressStream (line 24), it's unwrapped and properly wrapped. However, when the stream is an IStreamStack containing a passthrough SharpCompressStream (line 43-46), the passthrough stream is returned as-is without unwrapping. This creates inconsistent behavior depending on how the stream is wrapped. Consider checking if the underlying stream is a passthrough and handling it consistently with the direct case.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant