diff --git a/AGENTS.md b/AGENTS.md index 32bbb942..a47121a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,10 +126,17 @@ SharpCompress supports multiple archive and compression formats: - See [docs/FORMATS.md](docs/FORMATS.md) for complete format support matrix ### Stream Handling Rules -- **Disposal**: As of version 0.21, SharpCompress closes wrapped streams by default -- Use `ReaderOptions` or `WriterOptions` with `LeaveStreamOpen = true` to control stream disposal +- **Disposal semantics**: The default `ReaderOptions.LeaveStreamOpen` value is `false`, but effective stream ownership depends on which API overload you call + - File-based overloads (e.g., `OpenArchive(string filePath)`) open the file internally and own that stream, so it is closed by default with the archive/reader + - Do **not** rely on a specific `ReaderOptions` preset being used internally; some implementations may use `ReaderOptions.ForFilePath`, while others may use default `ReaderOptions` with the same ownership semantics + - Several high-level overloads that accept a caller-provided `Stream` use external-stream semantics by default (for example, `ReaderFactory.OpenReader(Stream)` / `ArchiveFactory.OpenArchive(Stream)`), so the caller's stream is typically left open unless you opt into different ownership behavior + - Do **not** assume every stream-based overload behaves identically; some APIs require you to pass stream ownership options explicitly +- **For caller-provided streams**: When the overload accepts `ReaderOptions`, pass `ReaderOptions.ForExternalStream` or use `ReaderOptions` with `LeaveStreamOpen = true` whenever the caller must retain ownership of the stream + - Example: `var options = new ReaderOptions { LeaveStreamOpen = true };` + - Or: `var options = ReaderOptions.ForExternalStream;` +- **For file paths**: SharpCompress manages the stream lifecycle for the internally opened file stream; no manual disposal is needed beyond the archive/reader itself - Use `NonDisposingStream` wrapper when working with compression streams directly to prevent disposal -- Always dispose of readers, writers, and archives in `using` blocks +- Always dispose of readers, writers, and archives in `using` / `await using` blocks - For forward-only operations, use Reader/Writer APIs; for random access, use Archive APIs ### Async/Await Patterns diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs index db0b8489..bdd18669 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs @@ -41,7 +41,7 @@ public partial class ZipArchive new SourceStream( fileInfo, i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } + readerOptions ?? ReaderOptions.ForFilePath ) ); } @@ -57,7 +57,7 @@ public partial class ZipArchive new SourceStream( files[0], i => i < files.Count ? files[i] : null, - readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false } + readerOptions ?? ReaderOptions.ForFilePath ) ); } diff --git a/src/SharpCompress/Readers/ReaderOptions.cs b/src/SharpCompress/Readers/ReaderOptions.cs index 6a063611..cc90224a 100644 --- a/src/SharpCompress/Readers/ReaderOptions.cs +++ b/src/SharpCompress/Readers/ReaderOptions.cs @@ -24,9 +24,36 @@ namespace SharpCompress.Readers; public sealed record ReaderOptions : IReaderOptions { /// - /// SharpCompress will keep the supplied streams open. Default is true. + /// Whether SharpCompress leaves the supplied streams open when the reader/archive is disposed. + /// As of v0.21, the library is documented to close streams by default; this option now defaults to false. + /// Set to true when passing caller-owned streams that should not be disposed. /// - public bool LeaveStreamOpen { get; init; } = true; + /// + /// + /// Default behavior (LeaveStreamOpen = false): + /// When you open an archive from a file path (e.g., GZipArchive.OpenArchive(filePath)), + /// SharpCompress manages the stream lifetime and closes it on Dispose. + /// + /// + /// Caller-provided streams (LeaveStreamOpen = true): + /// When you pass a stream you created (FileStream, MemoryStream, NetworkStream, etc.), + /// set LeaveStreamOpen = true to prevent SharpCompress from disposing it. + /// Use preset for convenience. + /// + /// + /// Example: + /// + /// // File-based: stream managed by library + /// using var archive = GZipArchive.OpenArchive(filePath); // LeaveStreamOpen = false + /// + /// // Caller-provided stream: caller manages lifetime + /// using var stream = File.OpenRead(filePath); + /// var options = new ReaderOptions { LeaveStreamOpen = true }; + /// using var archive = GZipArchive.OpenArchive(stream, options); + /// + /// + /// + public bool LeaveStreamOpen { get; init; } = false; /// /// Encoding to use for archive entry names.