@@ -8,13 +8,94 @@ namespace Compression.Registry;
88/// defragment, shrink, input constraints) are separate opt-in interfaces so callers can
99/// discover them at the type level.
1010/// </summary>
11+ /// <remarks>
12+ /// <para>
13+ /// The historical <see cref="List(Stream,string?)"/> / <c>Extract(Stream, ...)</c>
14+ /// methods remain the descriptor's native compatibility surface. The explicit input-mode methods
15+ /// below make the three archive-reading models available uniformly to every archive and
16+ /// pseudo-archive: forward-only stream input, required-seek input, and in-memory
17+ /// <see cref="ReadOnlySpan{T}"/> input.
18+ /// </para>
19+ /// <para>
20+ /// Existing descriptors automatically gain all three modes. A descriptor can override the
21+ /// default interface implementations when it has a genuinely streaming parser, a native
22+ /// random-access reader, or a zero-copy span parser. Until then, forward-only streams are
23+ /// spooled to a temporary seekable file and spans are copied once into an owned memory stream;
24+ /// neither fallback imposes a whole-archive managed-array limit on stream input.
25+ /// </para>
26+ /// </remarks>
1127public interface IArchiveFormatOperations {
12- /// <summary>List all entries in the archive.</summary>
28+ /// <summary>List all entries in the archive using the descriptor's native stream path .</summary>
1329 List < ArchiveEntryInfo > List ( Stream stream , string ? password ) ;
1430
15- /// <summary>Extract entries from the archive to an output directory.</summary>
31+ /// <summary>Extract entries from the archive to an output directory using the descriptor's native stream path .</summary>
1632 void Extract ( Stream stream , string outputDir , string ? password , string [ ] ? files ) ;
1733
34+ /// <summary>
35+ /// Lists entries from a forward-only or seekable stream. This is the libarchive-style
36+ /// streaming entry point: callers do not need to provide seek capability.
37+ /// </summary>
38+ public virtual List < ArchiveEntryInfo > ListStreaming ( Stream archive , string ? password ) {
39+ using var lease = SeekableArchiveInputLease . Open ( archive ) ;
40+ return this . ListSeekable ( lease . Stream , password ) ;
41+ }
42+
43+ /// <summary>
44+ /// Extracts entries from a forward-only or seekable stream. Descriptors with a native
45+ /// one-pass parser should override this method; the default spools only when necessary.
46+ /// </summary>
47+ public virtual void ExtractStreaming ( Stream archive , string outputDir , string ? password , string [ ] ? files ) {
48+ using var lease = SeekableArchiveInputLease . Open ( archive ) ;
49+ this . ExtractSeekable ( lease . Stream , outputDir , password , files ) ;
50+ }
51+
52+ /// <summary>
53+ /// Lists entries through the explicit seek-based path. The supplied stream must support
54+ /// seeking; it is rewound before the descriptor's native reader is invoked.
55+ /// </summary>
56+ public virtual List < ArchiveEntryInfo > ListSeekable ( Stream archive , string ? password ) {
57+ ArgumentNullException . ThrowIfNull ( archive ) ;
58+ if ( ! archive . CanRead )
59+ throw new ArgumentException ( "Archive input must be readable." , nameof ( archive ) ) ;
60+ if ( ! archive . CanSeek )
61+ throw new ArgumentException ( "Seek-based archive input must support seeking." , nameof ( archive ) ) ;
62+ archive . Position = 0 ;
63+ return this . List ( archive , password ) ;
64+ }
65+
66+ /// <summary>
67+ /// Extracts entries through the explicit seek-based path. The supplied stream must support
68+ /// seeking; it is rewound before the descriptor's native reader is invoked.
69+ /// </summary>
70+ public virtual void ExtractSeekable ( Stream archive , string outputDir , string ? password , string [ ] ? files ) {
71+ ArgumentNullException . ThrowIfNull ( archive ) ;
72+ if ( ! archive . CanRead )
73+ throw new ArgumentException ( "Archive input must be readable." , nameof ( archive ) ) ;
74+ if ( ! archive . CanSeek )
75+ throw new ArgumentException ( "Seek-based archive input must support seeking." , nameof ( archive ) ) ;
76+ archive . Position = 0 ;
77+ this . Extract ( archive , outputDir , password , files ) ;
78+ }
79+
80+ /// <summary>
81+ /// Lists entries from an in-memory archive image. The default compatibility bridge copies
82+ /// the span once because a <see cref="Stream"/> cannot safely retain a borrowed span; native
83+ /// span parsers should override this method to remain allocation-free.
84+ /// </summary>
85+ public virtual List < ArchiveEntryInfo > ListSpan ( ReadOnlySpan < byte > archive , string ? password ) {
86+ using var stream = new MemoryStream ( archive . ToArray ( ) , writable : false ) ;
87+ return this . ListSeekable ( stream , password ) ;
88+ }
89+
90+ /// <summary>
91+ /// Extracts entries from an in-memory archive image. Native span parsers can override this
92+ /// method to avoid the compatibility copy used by the default implementation.
93+ /// </summary>
94+ public virtual void ExtractSpan ( ReadOnlySpan < byte > archive , string outputDir , string ? password , string [ ] ? files ) {
95+ using var stream = new MemoryStream ( archive . ToArray ( ) , writable : false ) ;
96+ this . ExtractSeekable ( stream , outputDir , password , files ) ;
97+ }
98+
1899 /// <summary>
19100 /// Opens a single entry as a read-only <see cref="Stream"/> bounded to that
20101 /// entry's logical bytes — physically incapable of reading slack space,
@@ -29,7 +110,7 @@ public interface IArchiveFormatOperations {
29110 /// </para>
30111 /// <para>
31112 /// The default implementation intentionally does <b>not</b> materialize a
32- /// <c>byte[]</c>. It asks <see cref=" Extract"/ > for the selected entry in an
113+ /// <c>byte[]</c>. It asks <c> Extract(Stream, ...)</c > for the selected entry in an
33114 /// isolated temporary directory, opens the resulting file as a seekable
34115 /// stream, and deletes that tree on dispose. This gives every descriptor a
35116 /// large-file-safe streaming fallback even before it grows a native per-entry
@@ -44,13 +125,61 @@ public virtual Stream OpenEntry(Stream archive, string entryName, string? passwo
44125 return new BoundedEntryStream ( extracted , extracted . Length , leaveOpen : false ) ;
45126 }
46127
128+ /// <summary>
129+ /// Opens one entry from a forward-only or seekable archive source. A temporary spool, when
130+ /// required, stays alive until the returned entry stream is disposed.
131+ /// </summary>
132+ public virtual Stream OpenEntryStreaming ( Stream archive , string entryName , string ? password ) {
133+ ArgumentException . ThrowIfNullOrWhiteSpace ( entryName ) ;
134+ var lease = SeekableArchiveInputLease . Open ( archive ) ;
135+ try {
136+ var entry = this . OpenEntrySeekable ( lease . Stream , entryName , password ) ;
137+ return new OwnedArchiveEntryStream ( entry , lease ) ;
138+ } catch {
139+ lease . Dispose ( ) ;
140+ throw ;
141+ }
142+ }
143+
144+ /// <summary>
145+ /// Opens one entry through the explicit seek-based path. The archive is rewound before the
146+ /// descriptor-specific entry reader is invoked.
147+ /// </summary>
148+ public virtual Stream OpenEntrySeekable ( Stream archive , string entryName , string ? password ) {
149+ ArgumentNullException . ThrowIfNull ( archive ) ;
150+ ArgumentException . ThrowIfNullOrWhiteSpace ( entryName ) ;
151+ if ( ! archive . CanRead )
152+ throw new ArgumentException ( "Archive input must be readable." , nameof ( archive ) ) ;
153+ if ( ! archive . CanSeek )
154+ throw new ArgumentException ( "Seek-based archive input must support seeking." , nameof ( archive ) ) ;
155+ archive . Position = 0 ;
156+ return this . OpenEntry ( archive , entryName , password ) ;
157+ }
158+
159+ /// <summary>
160+ /// Opens one entry from an in-memory archive image. Because the returned stream may outlive
161+ /// this call, the default bridge owns one copy of the supplied span until that stream is disposed.
162+ /// Native span readers can override this method when they can return independently owned output.
163+ /// </summary>
164+ public virtual Stream OpenEntrySpan ( ReadOnlySpan < byte > archive , string entryName , string ? password ) {
165+ ArgumentException . ThrowIfNullOrWhiteSpace ( entryName ) ;
166+ var source = new MemoryStream ( archive . ToArray ( ) , writable : false ) ;
167+ try {
168+ var entry = this . OpenEntrySeekable ( source , entryName , password ) ;
169+ return new OwnedArchiveEntryStream ( entry , source ) ;
170+ } catch {
171+ source . Dispose ( ) ;
172+ throw ;
173+ }
174+ }
175+
47176 /// <summary>
48177 /// Extracts a single entry to a byte array. This is the explicitly buffered
49178 /// convenience API; callers working with large entries should use
50- /// <see cref="OpenEntry"/> instead.
179+ /// <see cref="OpenEntry(Stream,string,string?) "/> instead.
51180 /// </summary>
52181 /// <remarks>
53- /// The default routes through <see cref="OpenEntry"/>, so descriptor-specific
182+ /// The default routes through <see cref="OpenEntry(Stream,string,string?) "/>, so descriptor-specific
54183 /// isolation/decoding semantics are preserved. A result past the runtime array
55184 /// limit naturally fails here rather than imposing that limit on the streaming
56185 /// API or filesystem-driver layer.
@@ -64,4 +193,20 @@ public virtual byte[] ExtractEntryToMemory(Stream archive, string entryName, str
64193 entry . CopyTo ( memory ) ;
65194 return memory . ToArray ( ) ;
66195 }
196+
197+ /// <summary>Extracts one entry from a forward-only or seekable archive source into memory.</summary>
198+ public virtual byte [ ] ExtractEntryToMemoryStreaming ( Stream archive , string entryName , string ? password ) {
199+ using var entry = this . OpenEntryStreaming ( archive , entryName , password ) ;
200+ using var memory = new MemoryStream ( ) ;
201+ entry . CopyTo ( memory ) ;
202+ return memory . ToArray ( ) ;
203+ }
204+
205+ /// <summary>Extracts one entry from an in-memory archive image into a new byte array.</summary>
206+ public virtual byte [ ] ExtractEntryToMemorySpan ( ReadOnlySpan < byte > archive , string entryName , string ? password ) {
207+ using var entry = this . OpenEntrySpan ( archive , entryName , password ) ;
208+ using var memory = new MemoryStream ( ) ;
209+ entry . CopyTo ( memory ) ;
210+ return memory . ToArray ( ) ;
211+ }
67212}
0 commit comments