Skip to content
Draft
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
32 changes: 32 additions & 0 deletions FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,38 @@
* Reader classes allow forward-only reading on a stream.
* Writer classes allow forward-only Writing on a stream.

## Stream and Volume Architecture

SharpCompress uses a layered architecture to handle the complexity of multi-part and multi-volume archives:

### Concepts

1. **ByteSource** (`IByteSource`): The lowest-level abstraction representing a source of raw bytes. This could be a file, a stream, or part of a multi-part archive. ByteSources don't have any archive-specific logic.

2. **SourceStream**: Combines multiple byte sources into a unified stream. It operates in two modes:
- **Split Mode** (`IsVolumes=false`): Multiple files/streams are treated as one contiguous byte sequence. Used for split archives (e.g., `.z01`, `.z02`, `.zip`).
- **Volume Mode** (`IsVolumes=true`): Each file/stream is treated as an independent unit. Used for multi-volume archives (e.g., `.rar`, `.r00`, `.r01`).

3. **Volume** (`IVolume`): Represents a physical archive container with its own headers and metadata. Each format has its own Volume implementation (ZipVolume, RarVolume, SevenZipVolume, TarVolume).

### Format-Specific Behaviors

| Format | Split Archives | Multi-Volume | SOLID Support | Notes |
| ------ | -------------- | ------------ | ------------- | ----- |
| Zip | Yes | Yes | No | Split: `.z01`, `.z02`, `.zip`. Multi-volume uses separate headers per volume. |
| Rar | Yes | Yes | Yes | Multi-volume: `.rar`, `.r00`, `.r01`. SOLID archives share decompression context. |
| 7Zip | Yes | No | Yes (Folders) | Uses internal "folders" as compression units. Files in same folder share context. |
| Tar | Yes | No | No | Split tar files are concatenated. |

### SOLID Archives

Some formats support "SOLID" archives where multiple files share a contiguous stream of compressed bytes:

- **RAR SOLID**: Files are compressed together, and decompressing a file requires decompressing all files before it.
- **7Zip Folders**: Files grouped in the same folder share a decompression context.

This is why `ExtractAllEntries()` exists for SOLID archives - it allows sequential extraction which is much more efficient than random access.

## Supported Format Table

| Archive Format | Compression Format(s) | Compress/Decompress | Archive API | Reader API | Writer API |
Expand Down
52 changes: 52 additions & 0 deletions src/SharpCompress/Common/IVolume.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,61 @@

namespace SharpCompress.Common;

/// <summary>
/// Represents a physical archive volume (a single archive file or stream).
///
/// <para>
/// This interface is distinct from <see cref="IO.IByteSource"/> in that:
/// </para>
/// <list type="bullet">
/// <item>
/// <description>
/// <b>IVolume</b> represents a physical archive container with its own
/// headers, metadata, and structure. Each volume is a complete or partial
/// archive unit.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>IByteSource</b> represents a raw source of bytes without archive-specific
/// semantics. Multiple byte sources can form a contiguous stream, or each can
/// be independent.
/// </description>
/// </item>
/// </list>
///
/// <para>
/// Archive formats use volumes differently:
/// </para>
/// <list type="bullet">
/// <item>
/// <description>
/// <b>Multi-volume RAR</b>: Each volume is an independent archive unit with
/// its own headers. Files can span volumes.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>Split ZIP</b>: Data is split across multiple files but logically forms
/// one archive. The central directory is typically in the last part.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>7Zip/TAR</b>: Typically single volume, though can be split.
/// </description>
/// </item>
/// </list>
/// </summary>
public interface IVolume : IDisposable
{
/// <summary>
/// Gets the zero-based index of this volume within a multi-volume archive.
/// </summary>
int Index { get; }

/// <summary>
/// Gets the file name of this volume, if it was loaded from a file.
/// </summary>
string? FileName { get; }
}
31 changes: 31 additions & 0 deletions src/SharpCompress/Common/Volume.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@

namespace SharpCompress.Common;

/// <summary>
/// Base class for archive volumes. A volume represents a single physical
/// archive file or stream that may contain entries or parts of entries.
///
/// <para>
/// The relationship between <see cref="Volume"/>, <see cref="SourceStream"/>,
/// and <see cref="IByteSource"/> is:
/// </para>
/// <list type="bullet">
/// <item>
/// <description>
/// <b>IByteSource</b> is the lowest level - it provides access to raw bytes
/// from a file or stream, with no archive-specific logic.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>SourceStream</b> combines multiple byte sources into a unified stream,
/// handling the distinction between split archives (contiguous bytes) and
/// multi-volume archives (independent units).
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>Volume</b> wraps a stream with archive-specific metadata and behavior.
/// Format-specific subclasses (ZipVolume, RarVolume, etc.) add format-specific
/// properties and methods.
/// </description>
/// </item>
/// </list>
/// </summary>
public abstract class Volume : IVolume
{
private readonly Stream _baseStream;
Expand Down
39 changes: 39 additions & 0 deletions src/SharpCompress/IO/FileByteSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.IO;

namespace SharpCompress.IO;

/// <summary>
/// A byte source backed by a file on the file system.
/// </summary>
public sealed class FileByteSource : IByteSource
{
private readonly FileInfo _fileInfo;

/// <summary>
/// Creates a new file-based byte source.
/// </summary>
/// <param name="fileInfo">The file to read from.</param>
/// <param name="index">The index of this source in a collection.</param>
/// <param name="isPartOfContiguousSequence">Whether this is part of a split archive.</param>
public FileByteSource(FileInfo fileInfo, int index = 0, bool isPartOfContiguousSequence = false)
{
_fileInfo = fileInfo;
Index = index;
IsPartOfContiguousSequence = isPartOfContiguousSequence;
}

/// <inheritdoc />
public int Index { get; }

/// <inheritdoc />
public long? Length => _fileInfo.Exists ? _fileInfo.Length : null;

/// <inheritdoc />
public string? FileName => _fileInfo.FullName;

/// <inheritdoc />
public Stream OpenRead() => _fileInfo.OpenRead();

/// <inheritdoc />
public bool IsPartOfContiguousSequence { get; }
}
100 changes: 100 additions & 0 deletions src/SharpCompress/IO/IByteSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.IO;

namespace SharpCompress.IO;

/// <summary>
/// Represents a source of bytes that can be read as a stream.
/// This abstraction distinguishes between the "stream of bytes" concept
/// and the physical container (file, stream, or volume) that holds those bytes.
///
/// <para>
/// The key insight is that in archive formats, there are three distinct concepts:
/// </para>
///
/// <list type="number">
/// <item>
/// <description>
/// <b>ByteSource</b>: A logical source of bytes. This could be:
/// <list type="bullet">
/// <item>A single file or stream</item>
/// <item>A contiguous sequence spanning multiple files (split archives)</item>
/// <item>A compressed data block that expands to multiple files (SOLID archives)</item>
/// </list>
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>Volume</b>: A physical container representing one archive file/stream.
/// In multi-volume archives (like RAR volumes), each volume is an independent
/// archive unit with its own headers and metadata.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>SourceStream</b>: Manages multiple byte sources and presents them as a
/// unified stream. Handles both split mode (contiguous bytes across files)
/// and volume mode (independent archive units).
/// </description>
/// </item>
/// </list>
///
/// <para>
/// Format-specific behaviors:
/// </para>
/// <list type="bullet">
/// <item>
/// <description>
/// <b>7Zip</b>: Can have a contiguous stream of bytes for a file or files
/// within a folder (compression unit).
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>RAR SOLID</b>: Uses a contiguous stream of bytes for files, where
/// decompression depends on previous files in the stream.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>ZIP Split</b>: File data can be split across multiple disk files,
/// treated as one contiguous byte stream.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>RAR Multi-volume</b>: Each volume is an independent archive that
/// can be opened and read separately.
/// </description>
/// </item>
/// </list>
/// </summary>
public interface IByteSource
{
/// <summary>
/// Gets the index of this byte source within a collection of sources.
/// </summary>
int Index { get; }

/// <summary>
/// Gets the length of bytes available from this source, if known.
/// Returns null if the length cannot be determined (e.g., for unseekable streams).
/// </summary>
long? Length { get; }

/// <summary>
/// Gets the file name associated with this byte source, if available.
/// </summary>
string? FileName { get; }

/// <summary>
/// Opens a stream to read bytes from this source.
/// </summary>
/// <returns>A readable stream positioned at the start of the byte source.</returns>
Stream OpenRead();

/// <summary>
/// Indicates whether this byte source represents part of a contiguous
/// byte sequence that spans multiple sources (e.g., split archive).
/// </summary>
bool IsPartOfContiguousSequence { get; }
}
64 changes: 64 additions & 0 deletions src/SharpCompress/IO/IStreamStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,70 @@

namespace SharpCompress.IO
{
/// <summary>
/// Provides a common interface for streams that wrap other streams, forming a hierarchical "stack"
/// of stream processing layers. This interface enables coordinated buffering, position tracking,
/// and seeking across multiple stream layers.
///
/// <para>
/// <b>Purpose in SharpCompress Architecture:</b>
/// </para>
/// <para>
/// SharpCompress uses stacked streams extensively: a raw file/network stream may be wrapped by
/// a buffering stream, then by a decompression stream, then by a CRC validation stream, etc.
/// <c>IStreamStack</c> provides a uniform way to:
/// </para>
/// <list type="bullet">
/// <item><description>Navigate the stream hierarchy via <see cref="BaseStream()"/></description></item>
/// <item><description>Coordinate buffering across layers via <see cref="BufferSize"/> and <see cref="BufferPosition"/></description></item>
/// <item><description>Synchronize position state when seeking via <see cref="SetPosition"/></description></item>
/// <item><description>Debug stream lifecycle with instance tracking (in DEBUG builds)</description></item>
/// </list>
///
/// <para>
/// <b>Relationship to Other Abstractions:</b>
/// </para>
/// <list type="bullet">
/// <item>
/// <description>
/// <b>IByteSource</b>: Represents where bytes originate (file, stream). An <c>IByteSource.OpenRead()</c>
/// returns a raw stream that may then be wrapped in <c>IStreamStack</c>-implementing streams.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>SourceStream</b>: Combines multiple <c>IByteSource</c> instances into a unified stream.
/// <c>SourceStream</c> itself implements <c>IStreamStack</c>.
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>SharpCompressStream</b>: A buffering wrapper that implements <c>IStreamStack</c> with actual
/// buffering support (BufferSize greater than 0).
/// </description>
/// </item>
/// <item>
/// <description>
/// <b>Compression Streams</b>: All decompression streams (DeflateStream, LzmaStream, etc.) implement
/// <c>IStreamStack</c> to participate in the stack hierarchy, even if they don't buffer themselves.
/// </description>
/// </item>
/// </list>
///
/// <para>
/// <b>Extension Methods:</b>
/// </para>
/// <para>
/// The <see cref="StackStreamExtensions"/> class provides extension methods that operate on the
/// entire stack:
/// </para>
/// <list type="bullet">
/// <item><description><c>SetBuffer()</c>: Configures buffering at the appropriate level</description></item>
/// <item><description><c>Rewind()</c>: Rewinds buffered data when over-read occurs</description></item>
/// <item><description><c>StackSeek()</c>: Seeks efficiently, preferring buffer adjustment</description></item>
/// <item><description><c>GetPosition()</c>: Gets position accounting for buffered reads</description></item>
/// </list>
/// </summary>
public interface IStreamStack
{
/// <summary>
Expand Down
Loading