mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-04 05:25:00 +00:00
Compare commits
6 Commits
0.44.3
...
copilot/ra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb3d32622d | ||
|
|
b2b9e4ef7d | ||
|
|
f2cde3abdc | ||
|
|
3470f71f83 | ||
|
|
75db75a33a | ||
|
|
cef6d4603b |
32
FORMATS.md
32
FORMATS.md
@@ -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 |
|
||||
|
||||
@@ -2,9 +2,61 @@ using System;
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,37 @@ using SharpCompress.Readers;
|
||||
|
||||
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;
|
||||
|
||||
39
src/SharpCompress/IO/FileByteSource.cs
Normal file
39
src/SharpCompress/IO/FileByteSource.cs
Normal file
@@ -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
src/SharpCompress/IO/IByteSource.cs
Normal file
100
src/SharpCompress/IO/IByteSource.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -8,6 +8,70 @@ using System.Text;
|
||||
|
||||
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>
|
||||
|
||||
@@ -8,6 +8,55 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
/// <summary>
|
||||
/// A stream wrapper that provides buffering and position tracking for an underlying stream.
|
||||
///
|
||||
/// <para>
|
||||
/// SharpCompressStream is part of the stream handling architecture alongside:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>IByteSource</b>: Represents where bytes come from (file or stream).
|
||||
/// An IByteSource.OpenRead() returns a raw stream that may be wrapped by SharpCompressStream.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>SourceStream</b>: Combines multiple IByteSource instances into a unified stream,
|
||||
/// handling split archives and multi-volume scenarios.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>SharpCompressStream</b>: Wraps a single stream to provide buffering, position tracking,
|
||||
/// and implements IStreamStack for hierarchical stream operations.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Key features:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Optional buffering with configurable buffer size</description></item>
|
||||
/// <item><description>Position tracking independent of underlying stream</description></item>
|
||||
/// <item><description>Stream lifecycle management (LeaveOpen, ThrowOnDispose)</description></item>
|
||||
/// <item><description>IStreamStack implementation for stream stack operations</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Usage example:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// // Wrap a stream with buffering
|
||||
/// var bufferedStream = SharpCompressStream.Create(
|
||||
/// stream: rawStream,
|
||||
/// leaveOpen: true,
|
||||
/// throwOnDispose: false,
|
||||
/// bufferSize: 4096);
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class SharpCompressStream : Stream, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
|
||||
@@ -8,6 +8,62 @@ using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
/// <summary>
|
||||
/// A stream that unifies multiple byte sources (files or streams) into a single readable stream.
|
||||
///
|
||||
/// <para>
|
||||
/// SourceStream handles two distinct modes controlled by <see cref="IsVolumes"/>:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>Split Mode (IsVolumes=false)</b>: Multiple files/streams are treated as one
|
||||
/// contiguous byte sequence. Reading seamlessly transitions from one source to the
|
||||
/// next. Position and Length span all sources combined. This is used for split
|
||||
/// archives where file data is simply split across multiple physical files.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>Volume Mode (IsVolumes=true)</b>: Each file/stream is treated as an independent
|
||||
/// unit. Position and Length refer only to the current source. This is used for
|
||||
/// multi-volume archives where each volume has its own headers and structure.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Internally, SourceStream uses <see cref="IByteSource"/> to manage its underlying
|
||||
/// sources. This allows consistent handling of both file-based and stream-based sources,
|
||||
/// while the <see cref="Common.Volume"/> abstraction adds archive-specific semantics.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Format-specific behaviors:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>7Zip</b>: Uses split mode. The SourceStream presents the entire archive
|
||||
/// as one stream, and 7Zip handles internal "folders" (compression units)
|
||||
/// that may contain contiguous compressed data for multiple files.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>RAR</b>: Can use either mode. Multi-volume RAR uses volume mode where
|
||||
/// each .rar/.r00/.r01 file is a separate volume. SOLID RAR archives have
|
||||
/// internal contiguous byte streams for decompression.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>
|
||||
/// <b>ZIP</b>: Can use either mode. Split ZIP (.z01, .z02, .zip) uses split mode.
|
||||
/// Multi-volume ZIP uses volume mode.
|
||||
/// </description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class SourceStream : Stream, IStreamStack
|
||||
{
|
||||
#if DEBUG_STREAMS
|
||||
@@ -15,7 +71,7 @@ public class SourceStream : Stream, IStreamStack
|
||||
#endif
|
||||
int IStreamStack.DefaultBufferSize { get; set; }
|
||||
|
||||
Stream IStreamStack.BaseStream() => _streams[_stream];
|
||||
Stream IStreamStack.BaseStream() => _openStreams[_currentSourceIndex];
|
||||
|
||||
int IStreamStack.BufferSize
|
||||
{
|
||||
@@ -31,116 +87,153 @@ public class SourceStream : Stream, IStreamStack
|
||||
void IStreamStack.SetPosition(long position) { }
|
||||
|
||||
private long _prevSize;
|
||||
private readonly List<FileInfo> _files;
|
||||
private readonly List<Stream> _streams;
|
||||
private readonly Func<int, FileInfo?>? _getFilePart;
|
||||
private readonly Func<int, Stream?>? _getStreamPart;
|
||||
private int _stream;
|
||||
private readonly List<IByteSource> _sources;
|
||||
private readonly List<Stream> _openStreams;
|
||||
private readonly Func<int, IByteSource?>? _getNextSource;
|
||||
private int _currentSourceIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SourceStream from a file, with a function to get additional file parts.
|
||||
/// </summary>
|
||||
/// <param name="file">The initial file to read from.</param>
|
||||
/// <param name="getPart">Function that returns additional file parts by index, or null when no more parts.</param>
|
||||
/// <param name="options">Reader options.</param>
|
||||
public SourceStream(FileInfo file, Func<int, FileInfo?> getPart, ReaderOptions options)
|
||||
: this(null, null, file, getPart, options) { }
|
||||
: this(
|
||||
new FileByteSource(file, 0),
|
||||
index =>
|
||||
{
|
||||
var f = getPart(index);
|
||||
return f != null ? new FileByteSource(f, index) : null;
|
||||
},
|
||||
options
|
||||
) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SourceStream from a stream, with a function to get additional stream parts.
|
||||
/// </summary>
|
||||
/// <param name="stream">The initial stream to read from.</param>
|
||||
/// <param name="getPart">Function that returns additional stream parts by index, or null when no more parts.</param>
|
||||
/// <param name="options">Reader options.</param>
|
||||
public SourceStream(Stream stream, Func<int, Stream?> getPart, ReaderOptions options)
|
||||
: this(stream, getPart, null, null, options) { }
|
||||
: this(
|
||||
new StreamByteSource(stream, 0),
|
||||
index =>
|
||||
{
|
||||
var s = getPart(index);
|
||||
return s != null ? new StreamByteSource(s, index) : null;
|
||||
},
|
||||
options
|
||||
) { }
|
||||
|
||||
private SourceStream(
|
||||
Stream? stream,
|
||||
Func<int, Stream?>? getStreamPart,
|
||||
FileInfo? file,
|
||||
Func<int, FileInfo?>? getFilePart,
|
||||
/// <summary>
|
||||
/// Creates a SourceStream from an initial byte source with a function to get additional sources.
|
||||
/// </summary>
|
||||
/// <param name="initialSource">The initial byte source.</param>
|
||||
/// <param name="getNextSource">Function that returns additional byte sources by index, or null when no more sources.</param>
|
||||
/// <param name="options">Reader options.</param>
|
||||
public SourceStream(
|
||||
IByteSource initialSource,
|
||||
Func<int, IByteSource?>? getNextSource,
|
||||
ReaderOptions options
|
||||
)
|
||||
{
|
||||
ReaderOptions = options;
|
||||
_files = new List<FileInfo>();
|
||||
_streams = new List<Stream>();
|
||||
IsFileMode = file != null;
|
||||
IsVolumes = false;
|
||||
|
||||
if (!IsFileMode)
|
||||
{
|
||||
_streams.Add(stream!);
|
||||
_getStreamPart = getStreamPart;
|
||||
_getFilePart = _ => null;
|
||||
if (stream is FileStream fileStream)
|
||||
{
|
||||
_files.Add(new FileInfo(fileStream.Name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_files.Add(file!);
|
||||
_streams.Add(_files[0].OpenRead());
|
||||
_getFilePart = getFilePart;
|
||||
_getStreamPart = _ => null;
|
||||
}
|
||||
_stream = 0;
|
||||
_sources = new List<IByteSource> { initialSource };
|
||||
_openStreams = new List<Stream> { initialSource.OpenRead() };
|
||||
_getNextSource = getNextSource;
|
||||
_currentSourceIndex = 0;
|
||||
_prevSize = 0;
|
||||
IsVolumes = false;
|
||||
IsFileMode = initialSource is FileByteSource;
|
||||
|
||||
#if DEBUG_STREAMS
|
||||
this.DebugConstruct(typeof(SourceStream));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all available parts/volumes by calling the getNextSource function
|
||||
/// until it returns null. Resets to the first stream after loading.
|
||||
/// </summary>
|
||||
public void LoadAllParts()
|
||||
{
|
||||
for (var i = 1; SetStream(i); i++) { }
|
||||
SetStream(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this SourceStream operates in volume mode.
|
||||
/// When true, each stream is treated as an independent volume with its own
|
||||
/// position and length. When false (default), all streams are treated as
|
||||
/// one contiguous byte sequence.
|
||||
/// </summary>
|
||||
public bool IsVolumes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reader options associated with this source stream.
|
||||
/// </summary>
|
||||
public ReaderOptions ReaderOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this SourceStream was created from a FileInfo (true)
|
||||
/// or from a Stream (false).
|
||||
/// </summary>
|
||||
public bool IsFileMode { get; }
|
||||
|
||||
public IEnumerable<FileInfo> Files => _files;
|
||||
public IEnumerable<Stream> Streams => _streams;
|
||||
/// <summary>
|
||||
/// Gets the collection of loaded byte sources.
|
||||
/// </summary>
|
||||
internal IEnumerable<IByteSource> Sources => _sources;
|
||||
|
||||
private Stream Current => _streams[_stream];
|
||||
/// <summary>
|
||||
/// Gets the collection of FileInfo objects for each loaded source.
|
||||
/// May be empty if sources are streams without file associations.
|
||||
/// </summary>
|
||||
public IEnumerable<FileInfo> Files =>
|
||||
_sources.Where(s => s.FileName != null).Select(s => new FileInfo(s.FileName!));
|
||||
|
||||
public bool LoadStream(int index) //ensure all parts to id are loaded
|
||||
/// <summary>
|
||||
/// Gets the collection of underlying streams for each loaded source.
|
||||
/// </summary>
|
||||
public IEnumerable<Stream> Streams => _openStreams;
|
||||
|
||||
private Stream Current => _openStreams[_currentSourceIndex];
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that sources up to and including the specified index are loaded.
|
||||
/// </summary>
|
||||
/// <param name="index">The source index to load.</param>
|
||||
/// <returns>True if the source at the index was successfully loaded; false otherwise.</returns>
|
||||
public bool LoadStream(int index)
|
||||
{
|
||||
while (_streams.Count <= index)
|
||||
while (_sources.Count <= index)
|
||||
{
|
||||
if (IsFileMode)
|
||||
var nextSource = _getNextSource?.Invoke(_sources.Count);
|
||||
if (nextSource is null)
|
||||
{
|
||||
var f = _getFilePart.NotNull("GetFilePart is null")(_streams.Count);
|
||||
if (f == null)
|
||||
{
|
||||
_stream = _streams.Count - 1;
|
||||
return false;
|
||||
}
|
||||
//throw new Exception($"File part {idx} not available.");
|
||||
_files.Add(f);
|
||||
_streams.Add(_files.Last().OpenRead());
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = _getStreamPart.NotNull("GetStreamPart is null")(_streams.Count);
|
||||
if (s == null)
|
||||
{
|
||||
_stream = _streams.Count - 1;
|
||||
return false;
|
||||
}
|
||||
//throw new Exception($"Stream part {idx} not available.");
|
||||
_streams.Add(s);
|
||||
if (s is FileStream stream)
|
||||
{
|
||||
_files.Add(new FileInfo(stream.Name));
|
||||
}
|
||||
_currentSourceIndex = _sources.Count - 1;
|
||||
return false;
|
||||
}
|
||||
_sources.Add(nextSource);
|
||||
_openStreams.Add(nextSource.OpenRead());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetStream(int idx) //allow caller to switch part in multipart
|
||||
/// <summary>
|
||||
/// Switches to the specified source index.
|
||||
/// </summary>
|
||||
/// <param name="idx">The source index to switch to.</param>
|
||||
/// <returns>True if the switch was successful; false otherwise.</returns>
|
||||
public bool SetStream(int idx)
|
||||
{
|
||||
if (LoadStream(idx))
|
||||
{
|
||||
_stream = idx;
|
||||
_currentSourceIndex = idx;
|
||||
}
|
||||
|
||||
return _stream == idx;
|
||||
return _currentSourceIndex == idx;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
@@ -149,7 +242,7 @@ public class SourceStream : Stream, IStreamStack
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => !IsVolumes ? _streams.Sum(a => a.Length) : Current.Length;
|
||||
public override long Length => !IsVolumes ? _openStreams.Sum(a => a.Length) : Current.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
@@ -183,8 +276,8 @@ public class SourceStream : Stream, IStreamStack
|
||||
{
|
||||
var length = Current.Length;
|
||||
|
||||
// Load next file if present
|
||||
if (!SetStream(_stream + 1))
|
||||
// Load next source if present
|
||||
if (!SetStream(_currentSourceIndex + 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -223,7 +316,7 @@ public class SourceStream : Stream, IStreamStack
|
||||
while (_prevSize + Current.Length < pos)
|
||||
{
|
||||
_prevSize += Current.Length;
|
||||
SetStream(_stream + 1);
|
||||
SetStream(_currentSourceIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,8 +365,8 @@ public class SourceStream : Stream, IStreamStack
|
||||
{
|
||||
var length = Current.Length;
|
||||
|
||||
// Load next file if present
|
||||
if (!SetStream(_stream + 1))
|
||||
// Load next source if present
|
||||
if (!SetStream(_currentSourceIndex + 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -321,8 +414,8 @@ public class SourceStream : Stream, IStreamStack
|
||||
{
|
||||
var length = Current.Length;
|
||||
|
||||
// Load next file if present
|
||||
if (!SetStream(_stream + 1))
|
||||
// Load next source if present
|
||||
if (!SetStream(_currentSourceIndex + 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -343,7 +436,7 @@ public class SourceStream : Stream, IStreamStack
|
||||
{
|
||||
if (IsFileMode || !ReaderOptions.LeaveStreamOpen) //close if file mode or options specify it
|
||||
{
|
||||
foreach (var stream in _streams)
|
||||
foreach (var stream in _openStreams)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -354,8 +447,8 @@ public class SourceStream : Stream, IStreamStack
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
_streams.Clear();
|
||||
_files.Clear();
|
||||
_openStreams.Clear();
|
||||
_sources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
64
src/SharpCompress/IO/StreamByteSource.cs
Normal file
64
src/SharpCompress/IO/StreamByteSource.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
/// <summary>
|
||||
/// A byte source backed by an existing stream.
|
||||
/// </summary>
|
||||
public sealed class StreamByteSource : IByteSource
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new stream-based byte source.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream 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 StreamByteSource(Stream stream, int index = 0, bool isPartOfContiguousSequence = false)
|
||||
{
|
||||
_stream = stream;
|
||||
Index = index;
|
||||
IsPartOfContiguousSequence = isPartOfContiguousSequence;
|
||||
|
||||
if (stream is FileStream fileStream)
|
||||
{
|
||||
FileName = fileStream.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Index { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public long? Length
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return _stream.CanSeek ? _stream.Length : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? FileName { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream OpenRead()
|
||||
{
|
||||
if (_stream.CanSeek)
|
||||
{
|
||||
_stream.Position = 0;
|
||||
}
|
||||
return _stream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPartOfContiguousSequence { get; }
|
||||
}
|
||||
Reference in New Issue
Block a user