mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-07-09 02:26:47 +00:00
Merge remote-tracking branch 'origin/master' into adam/write-async
# Conflicts: # src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs # src/SharpCompress/Factories/TarFactory.cs # src/SharpCompress/Polyfills/StreamExtensions.cs # src/SharpCompress/Writers/IWriter.cs
This commit is contained in:
14
AGENTS.md
14
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
|
||||
@@ -183,6 +190,7 @@ SharpCompress supports multiple archive and compression formats:
|
||||
|
||||
### Validation Expectations
|
||||
- Run targeted tests for the changed area first.
|
||||
- On non-Windows machines, avoid net48 test runs unless Mono is installed; use framework-specific validation such as `--framework net10.0` instead.
|
||||
- Run `dotnet csharpier format .` after code edits.
|
||||
- Run `dotnet csharpier check .` before handing off changes.
|
||||
|
||||
|
||||
38
docs/API.md
38
docs/API.md
@@ -84,8 +84,8 @@ using (var archive = ZipArchive.OpenArchive("file.zip"))
|
||||
archive.WriteToDirectory(@"C:\output");
|
||||
|
||||
// Extract single entry
|
||||
var entry = archive.Entries.First();
|
||||
entry.WriteToFile(@"C:\output\file.txt");
|
||||
var firstEntry = archive.Entries.First();
|
||||
firstEntry.WriteToFile(@"C:\output\file.txt");
|
||||
|
||||
// Get entry stream
|
||||
using (var stream = entry.OpenEntryStream())
|
||||
@@ -97,14 +97,23 @@ using (var archive = ZipArchive.OpenArchive("file.zip"))
|
||||
// Async extraction (requires IAsyncArchive)
|
||||
await using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip"))
|
||||
{
|
||||
// Extract all entries asynchronously
|
||||
await asyncArchive.WriteToDirectoryAsync(
|
||||
@"C:\output",
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
using (var stream = await entry.OpenEntryStreamAsync(cancellationToken))
|
||||
|
||||
// Open a specific entry stream asynchronously
|
||||
await using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip"))
|
||||
{
|
||||
// ...
|
||||
await foreach (var entry in asyncArchive.EntriesAsync)
|
||||
{
|
||||
using (var stream = await entry.OpenEntryStreamAsync(cancellationToken))
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -214,15 +223,18 @@ using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, Compressio
|
||||
// Write directory
|
||||
writer.WriteAll("C:\\source", "*", SearchOption.AllDirectories);
|
||||
writer.WriteAll("C:\\source", "*.txt", SearchOption.TopDirectoryOnly);
|
||||
|
||||
// Async variants
|
||||
using (var fileStream = File.OpenRead("source.txt"))
|
||||
{
|
||||
await writer.WriteAsync("entry.txt", fileStream, DateTime.Now, cancellationToken);
|
||||
}
|
||||
|
||||
await writer.WriteAllAsync("C:\\source", "*", SearchOption.AllDirectories, cancellationToken);
|
||||
}
|
||||
|
||||
// Async variants: use OpenAsyncWriter to get IAsyncWriter
|
||||
await using var stream = File.Create("output.zip");
|
||||
await using var writer = await WriterFactory.OpenAsyncWriter(stream, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate), cancellationToken);
|
||||
|
||||
using (var fileStream = File.OpenRead("source.txt"))
|
||||
{
|
||||
await writer.WriteAsync("entry.txt", fileStream, DateTime.Now, cancellationToken);
|
||||
}
|
||||
|
||||
await writer.WriteAllAsync("C:\\source", "*", SearchOption.AllDirectories, cancellationToken);
|
||||
```
|
||||
|
||||
---
|
||||
@@ -390,7 +402,7 @@ ArchiveType.ZStandard
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
using (var archive = ZipArchive.Open("archive.zip",
|
||||
using (var archive = ZipArchive.OpenArchive("archive.zip",
|
||||
ReaderOptions.ForEncryptedArchive("password")))
|
||||
{
|
||||
archive.WriteToDirectory(@"C:\output");
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
| Tar.BZip2 | BZip2 | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.Zstandard | ZStandard | Decompress | TarArchive | TarReader | N/A |
|
||||
| Tar.LZip | LZMA | Both | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | TarWriter (3) |
|
||||
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | N/A |
|
||||
| GZip (single file) | DEFLATE | Both | GZipArchive | GZipReader | GZipWriter |
|
||||
| 7Zip (4) | LZMA, LZMA2, BZip2, PPMd, BCJ, BCJ2, Deflate | Both | SevenZipArchive | N/A | SevenZipWriter |
|
||||
|
||||
|
||||
340
docs/TAR_GAP_ANALYSIS.md
Normal file
340
docs/TAR_GAP_ANALYSIS.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Tar Gap Analysis
|
||||
|
||||
## Scope
|
||||
|
||||
This document compares the current Tar documentation, tests, and code paths in SharpCompress.
|
||||
|
||||
It is intentionally implementation-focused. The goal is to identify mismatches, omissions, and incomplete areas in the current SharpCompress Tar support.
|
||||
|
||||
Primary references:
|
||||
|
||||
- `docs/FORMATS.md`
|
||||
- `src/SharpCompress/Factories/TarFactory.cs`
|
||||
- `src/SharpCompress/Factories/TarWrapper.cs`
|
||||
- `src/SharpCompress/Archives/Tar/`
|
||||
- `src/SharpCompress/Readers/Tar/`
|
||||
- `src/SharpCompress/Writers/Tar/`
|
||||
- `src/SharpCompress/Common/Tar/`
|
||||
- `tests/SharpCompress.Test/Tar/`
|
||||
|
||||
## Claimed vs Actual Support
|
||||
|
||||
### `Tar.XZ` is read-only
|
||||
|
||||
`tar.xz` is supported for reading, but not for writing.
|
||||
|
||||
Actual implementation in `src/SharpCompress/Writers/Tar/TarWriter.cs` does not support `CompressionType.Xz`. The writer throws `InvalidFormatException` for any compression type outside:
|
||||
|
||||
- `None`
|
||||
- `GZip`
|
||||
- `BZip2`
|
||||
- `LZip`
|
||||
|
||||
Impact:
|
||||
|
||||
- Tar write support is narrower than Tar read support
|
||||
- `tar.xz` creation is not available through the built-in Tar writer
|
||||
|
||||
Recommended action:
|
||||
|
||||
- keep the format table marked `N/A` for Tar.XZ writer support
|
||||
|
||||
## Read-Path Gaps
|
||||
|
||||
### PAX headers are not implemented
|
||||
|
||||
There is no explicit support for POSIX PAX local extended headers.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `EntryType` does not define the usual local PAX header type value
|
||||
- `TarHeader.Read` handles `LongName` and `LongLink`, but does not implement PAX record parsing
|
||||
- there are no tests or test archives covering PAX behavior
|
||||
|
||||
Impact:
|
||||
|
||||
- archives relying on PAX for long names, metadata, or timestamps may not be interpreted correctly
|
||||
|
||||
Recommended action:
|
||||
|
||||
- decide whether PAX is intentionally unsupported or should be implemented
|
||||
- document that decision explicitly
|
||||
|
||||
### Sparse files are not semantically implemented
|
||||
|
||||
`EntryType` defines `SparseFile`, but the read path does not contain sparse map handling or sparse reconstruction logic.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `src/SharpCompress/Common/Tar/Headers/EntryType.cs`
|
||||
- no sparse-specific code in `TarHeader`, `TarEntry`, `TarFilePart`, or `TarArchive`
|
||||
- no sparse tests
|
||||
|
||||
Impact:
|
||||
|
||||
- sparse entries may be treated as ordinary entries rather than sparse files with holes
|
||||
|
||||
Recommended action:
|
||||
|
||||
- document sparse support as unsupported or partial
|
||||
- add explicit tests if future support is added
|
||||
|
||||
### Global extended headers are not semantically implemented
|
||||
|
||||
`EntryType` defines `GlobalExtendedHeader`, but no semantic handling exists in the read pipeline.
|
||||
|
||||
Evidence:
|
||||
|
||||
- `TarHeader.Read` does not special-case `GlobalExtendedHeader`
|
||||
- `TarEntry` does not surface a global-header model
|
||||
- no tests cover this case
|
||||
|
||||
Impact:
|
||||
|
||||
- global metadata records are not applied in a defined way
|
||||
|
||||
Recommended action:
|
||||
|
||||
- document as unsupported until explicit behavior exists
|
||||
|
||||
### Device and FIFO semantics are not surfaced
|
||||
|
||||
The entry type enum includes `CharDevice`, `BlockDevice`, and `Fifo`, but the public tar model does not expose device metadata semantics.
|
||||
|
||||
Impact:
|
||||
|
||||
- such entries may not round-trip meaningfully through the API
|
||||
- behavior is undocumented and untested
|
||||
|
||||
Recommended action:
|
||||
|
||||
- either document them as raw/unmodeled entry types or add dedicated support
|
||||
|
||||
## Write-Path Gaps
|
||||
|
||||
### `HeaderFormat` is not honored consistently
|
||||
|
||||
`TarWriterOptions.HeaderFormat` exists and defaults to `GNU_TAR_LONG_LINK`, but the configured value is not consistently applied.
|
||||
|
||||
### Sync directory write path
|
||||
|
||||
`TarWriter.WriteDirectory` creates headers using:
|
||||
|
||||
- `new TarHeader(WriterOptions.ArchiveEncoding)`
|
||||
|
||||
This uses the default tar header format rather than the writer's configured `headerFormat` field.
|
||||
|
||||
Impact:
|
||||
|
||||
- directory entries written through the sync path do not follow `TarWriterOptions.HeaderFormat`
|
||||
|
||||
### Async write path
|
||||
|
||||
`TarWriter.WriteAsync` and `WriteDirectoryAsync` also create headers using the default constructor rather than the configured header format.
|
||||
|
||||
Impact:
|
||||
|
||||
- async writes ignore `TarWriterOptions.HeaderFormat` for both file and directory entries
|
||||
|
||||
Recommended action:
|
||||
|
||||
- pass the configured header format to all `TarHeader` constructions in sync and async write paths
|
||||
- add tests for both `GNU_TAR_LONG_LINK` and `USTAR`
|
||||
|
||||
### No public link-writing support
|
||||
|
||||
The read path supports symbolic and hard link targets through `TarEntry.LinkTarget`, but the write API exposes only regular file and directory creation.
|
||||
|
||||
Impact:
|
||||
|
||||
- symlink and hardlink tar archives cannot be created through the current public Tar writer API
|
||||
|
||||
Recommended action:
|
||||
|
||||
- either document this as a deliberate limitation or add link-writing APIs
|
||||
|
||||
### Metadata round-trip support is incomplete
|
||||
|
||||
The writer does not round-trip rich tar metadata beyond the basic fields needed for file and directory entries.
|
||||
|
||||
Current write behavior sets fixed defaults for some fields such as mode, owner id, and group id.
|
||||
|
||||
Impact:
|
||||
|
||||
- modified or newly created tar archives may lose metadata fidelity relative to the original archive
|
||||
|
||||
Recommended action:
|
||||
|
||||
- document current metadata write behavior clearly
|
||||
- expand metadata support only if needed by consumers
|
||||
|
||||
### No write support for some detected wrappers
|
||||
|
||||
The detection and read path supports wrappers that the write path does not support.
|
||||
|
||||
| Wrapper | Read support | Write support |
|
||||
| ------- | ------------ | ------------- |
|
||||
| `tar.xz` | Yes | No |
|
||||
| `tar.zst` | Yes | No |
|
||||
| `tar.Z` | Yes | No |
|
||||
|
||||
This is not inherently wrong, but it should be clearly documented everywhere support is summarized.
|
||||
|
||||
## Sync and Async API Inconsistencies
|
||||
|
||||
### Seekability requirements differ at the API boundary
|
||||
|
||||
Synchronous `TarArchive.OpenArchive(Stream)` explicitly throws if the stream is not seekable.
|
||||
|
||||
Asynchronous `TarArchive.OpenAsyncArchive(Stream)` does not perform the same public guard.
|
||||
|
||||
Impact:
|
||||
|
||||
- callers do not see the same contract from sync and async overloads
|
||||
- behavior is harder to reason about from API docs alone
|
||||
|
||||
Recommended action:
|
||||
|
||||
- either align the contracts or document the difference explicitly
|
||||
|
||||
### Async and sync write behavior do not align on header format handling
|
||||
|
||||
This is the most visible sync/async inconsistency in the current Tar writer implementation.
|
||||
|
||||
Recommended action:
|
||||
|
||||
- fix the implementation first
|
||||
- add matching sync and async tests to keep the behavior aligned
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
### Symlink coverage exists in test data but not in assertions
|
||||
|
||||
There is a tar archive containing symlinks:
|
||||
|
||||
- `tests/TestArchives/Archives/TarWithSymlink.tar.gz`
|
||||
|
||||
Current Tar tests do not assert tar symlink behavior against that fixture.
|
||||
|
||||
Impact:
|
||||
|
||||
- the code claims practical read support for link targets, but coverage does not verify it
|
||||
|
||||
Recommended action:
|
||||
|
||||
- add reader and archive tests asserting `EntryType`-derived behavior and `LinkTarget`
|
||||
|
||||
### No tests for `HeaderFormat`
|
||||
|
||||
There are currently no tests covering:
|
||||
|
||||
- `TarWriterOptions.HeaderFormat = USTAR`
|
||||
- `TarWriterOptions.HeaderFormat = GNU_TAR_LONG_LINK`
|
||||
- long-name failures in USTAR mode
|
||||
- long-name success in GNU mode through the async writer path
|
||||
|
||||
Impact:
|
||||
|
||||
- the current header-format regressions were able to exist without test coverage
|
||||
|
||||
Recommended action:
|
||||
|
||||
- add dedicated sync and async writer tests for header format selection
|
||||
|
||||
### No tests for PAX, sparse, or global headers
|
||||
|
||||
There is no evidence of coverage for:
|
||||
|
||||
- PAX local headers
|
||||
- global extended headers
|
||||
- sparse tar entries
|
||||
|
||||
Impact:
|
||||
|
||||
- unsupported or partial behavior is neither documented by tests nor protected from regression
|
||||
|
||||
Recommended action:
|
||||
|
||||
- either add fixtures and tests or document these as unsupported with no test coverage
|
||||
|
||||
### No tests for unsupported write wrappers
|
||||
|
||||
There are negative tests for an invalid `Rar` compression type, but not for unsupported tar wrappers that a user might reasonably infer from read support.
|
||||
|
||||
Missing negative cases include:
|
||||
|
||||
- `CompressionType.Xz`
|
||||
- `CompressionType.ZStandard`
|
||||
- `CompressionType.Lzw`
|
||||
|
||||
Recommended action:
|
||||
|
||||
- add explicit negative tests so the supported write matrix stays intentional
|
||||
|
||||
## Documentation Gaps
|
||||
|
||||
### Current format documentation is too coarse for Tar
|
||||
|
||||
`docs/FORMATS.md` summarizes support at the wrapper level, but Tar behavior depends on more than wrapper compression.
|
||||
|
||||
Missing implementation-specific details include:
|
||||
|
||||
- GNU long-name and long-link support
|
||||
- USTAR prefix handling
|
||||
- oldgnu numeric quirk handling
|
||||
- missing PAX support
|
||||
- missing sparse support
|
||||
- reader vs archive behavior differences for compressed tar
|
||||
- file-size requirements for writing from non-seekable sources
|
||||
|
||||
Recommended action:
|
||||
|
||||
- keep `docs/FORMATS.md` high-level
|
||||
- add and maintain a dedicated Tar spec document for details
|
||||
|
||||
### The current docs do not call out partial support clearly
|
||||
|
||||
The codebase supports some tar dialect features and not others, but the docs do not separate:
|
||||
|
||||
- fully supported
|
||||
- partially supported
|
||||
- unsupported
|
||||
|
||||
Recommended action:
|
||||
|
||||
- use an explicit feature matrix in the Tar documentation
|
||||
|
||||
## Recommended Follow-Ups
|
||||
|
||||
### Priority 0
|
||||
|
||||
- Correct `docs/FORMATS.md` for `Tar.XZ` write support
|
||||
|
||||
### Priority 1
|
||||
|
||||
- Fix `TarWriterOptions.HeaderFormat` handling in sync and async writer paths
|
||||
- Add tests for header-format behavior
|
||||
- Add symlink coverage using `TarWithSymlink.tar.gz`
|
||||
|
||||
### Priority 2
|
||||
|
||||
- Decide and document the support position for PAX headers
|
||||
- Decide and document the support position for sparse files
|
||||
- Decide and document the support position for global extended headers
|
||||
|
||||
### Priority 3
|
||||
|
||||
- Add negative writer tests for unsupported wrapper compressions
|
||||
- Evaluate whether sync and async archive open contracts should match exactly
|
||||
- Improve metadata round-trip behavior only if there is a consumer need
|
||||
|
||||
## Summary
|
||||
|
||||
The SharpCompress Tar implementation is strong on common read scenarios and basic write scenarios, but the current gaps fall into four categories:
|
||||
|
||||
- documentation overstating or under-describing support
|
||||
- incomplete feature coverage for less common tar dialect features
|
||||
- sync/async and file/directory inconsistencies in writer header-format handling
|
||||
- test coverage holes around links and advanced tar metadata features
|
||||
|
||||
`docs/TAR_SPEC.md` should be treated as the implementation baseline. This document identifies where that baseline is incomplete, inconsistent, or incorrectly reflected elsewhere in the repository.
|
||||
430
docs/TAR_SPEC.md
Normal file
430
docs/TAR_SPEC.md
Normal file
@@ -0,0 +1,430 @@
|
||||
# Tar Spec
|
||||
|
||||
## Scope
|
||||
|
||||
This document describes the Tar implementation that exists in SharpCompress today.
|
||||
|
||||
It is intentionally SharpCompress-specific. It documents actual behavior in the current codebase, including partial support and limitations. It is not a general tar format reference.
|
||||
|
||||
Primary implementation files:
|
||||
|
||||
- `src/SharpCompress/Factories/TarFactory.cs`
|
||||
- `src/SharpCompress/Factories/TarWrapper.cs`
|
||||
- `src/SharpCompress/Archives/Tar/TarArchive.cs`
|
||||
- `src/SharpCompress/Archives/Tar/TarArchive.Async.cs`
|
||||
- `src/SharpCompress/Archives/Tar/TarArchive.Factory.cs`
|
||||
- `src/SharpCompress/Readers/Tar/TarReader.cs`
|
||||
- `src/SharpCompress/Readers/Tar/TarReader.Async.cs`
|
||||
- `src/SharpCompress/Writers/Tar/TarWriter.cs`
|
||||
- `src/SharpCompress/Writers/Tar/TarWriter.Async.cs`
|
||||
- `src/SharpCompress/Writers/Tar/TarWriterOptions.cs`
|
||||
- `src/SharpCompress/Common/Tar/Headers/TarHeader.cs`
|
||||
- `src/SharpCompress/Common/Tar/Headers/TarHeader.Async.cs`
|
||||
- `src/SharpCompress/Common/Tar/TarHeaderFactory.cs`
|
||||
- `src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs`
|
||||
|
||||
## API Surface
|
||||
|
||||
SharpCompress exposes Tar support through four main entry points.
|
||||
|
||||
| Type | Role |
|
||||
| ---- | ---- |
|
||||
| `TarFactory` | Format detection and factory entry point for archive, reader, and writer APIs |
|
||||
| `TarArchive` | Archive API for enumerating and rewriting tar archives |
|
||||
| `TarReader` | Forward-only reader API for streaming tar extraction |
|
||||
| `TarWriter` | Forward-only writer API for creating tar archives |
|
||||
|
||||
`TarWriterOptions` controls output compression, stream ownership, archive finalization, encoding, and header write format.
|
||||
|
||||
## Supported Wrapper Formats
|
||||
|
||||
Tar wrapper detection is defined by `TarWrapper.Wrappers` in `src/SharpCompress/Factories/TarWrapper.cs`.
|
||||
|
||||
### Supported Extensions
|
||||
|
||||
| Wrapper | Extensions |
|
||||
| ------- | ---------- |
|
||||
| Plain tar | `tar` |
|
||||
| Tar + BZip2 | `tar.bz2`, `tb2`, `tbz`, `tbz2`, `tz2` |
|
||||
| Tar + GZip | `tar.gz`, `taz`, `tgz` |
|
||||
| Tar + ZStandard | `tar.zst`, `tar.zstd`, `tzst`, `tzstd` |
|
||||
| Tar + LZip | `tar.lz` |
|
||||
| Tar + XZ | `tar.xz`, `txz` |
|
||||
| Tar + LZW compress | `tar.Z`, `tZ`, `taZ` |
|
||||
|
||||
### API Support Matrix
|
||||
|
||||
| Wrapper | Detection | `TarArchive` read | `TarReader` read | `TarWriter` write |
|
||||
| ------- | --------- | ----------------- | ---------------- | ----------------- |
|
||||
| Plain tar | Yes | Yes | Yes | Yes |
|
||||
| Tar + GZip | Yes | Yes | Yes | Yes |
|
||||
| Tar + BZip2 | Yes | Yes | Yes | Yes |
|
||||
| Tar + LZip | Yes | Yes | Yes | Yes |
|
||||
| Tar + XZ | Yes | Yes | Yes | No |
|
||||
| Tar + ZStandard | Yes | Yes | Yes | No |
|
||||
| Tar + LZW compress | Yes | Yes | Yes | No |
|
||||
|
||||
Write support is implemented in `src/SharpCompress/Writers/Tar/TarWriter.cs` and currently accepts only `CompressionType.None`, `CompressionType.GZip`, `CompressionType.BZip2`, and `CompressionType.LZip`.
|
||||
|
||||
## Detection Behavior
|
||||
|
||||
Tar detection is implemented in `TarFactory.IsArchive`, `TarFactory.IsArchiveAsync`, `TarFactory.GetCompressionType`, and `TarFactory.GetCompressionTypeAsync`.
|
||||
|
||||
Detection behavior is:
|
||||
|
||||
1. Wrap the incoming stream in `SharpCompressStream`.
|
||||
2. Start recording with a rewind buffer sized from `TarWrapper.MaximumRewindBufferSize`.
|
||||
3. Probe each registered wrapper in order.
|
||||
4. If a wrapper matches, create a decompression stream for that wrapper.
|
||||
5. Call `TarArchive.IsTarFile` or `TarArchive.IsTarFileAsync` on the decompressed stream.
|
||||
6. If the tar probe succeeds, treat the stream as tar with that wrapper compression.
|
||||
|
||||
Implications:
|
||||
|
||||
- Tar detection is content-based, not extension-based.
|
||||
- Wrapper detection is not sufficient by itself. The decompressed payload must also parse as tar.
|
||||
- Non-seekable detection is supported through the recording and rewind mechanism.
|
||||
- The largest rewind requirement currently comes from BZip2, which declares a larger minimum probe buffer in `TarWrapper`.
|
||||
|
||||
`TarArchive.IsTarFile` and `TarArchive.IsTarFileAsync` attempt to read a single tar header and return `false` on any exception. They also treat an all-zero empty archive block as a valid empty tar archive when the entry type is defined.
|
||||
|
||||
## Reader Behavior
|
||||
|
||||
`TarReader` is the forward-only streaming API.
|
||||
|
||||
Implementation files:
|
||||
|
||||
- `src/SharpCompress/Readers/Tar/TarReader.cs`
|
||||
- `src/SharpCompress/Readers/Tar/TarReader.Async.cs`
|
||||
- `src/SharpCompress/Common/Tar/TarEntry.cs`
|
||||
- `src/SharpCompress/Common/Tar/TarEntry.Async.cs`
|
||||
- `src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs`
|
||||
|
||||
Reader behavior:
|
||||
|
||||
- The reader always enumerates entries in streaming mode.
|
||||
- It works with non-seekable input streams.
|
||||
- It applies decompression based on the detected wrapper compression type before parsing tar headers.
|
||||
- Entry streams are backed by `TarReadOnlySubStream`.
|
||||
|
||||
`TarReadOnlySubStream` has an important behavior: disposing an entry stream consumes any unread entry bytes and any required 512-byte padding so that the next header can be read correctly. This is what makes skipping entries work in streaming mode.
|
||||
|
||||
### Reader Compression Mapping
|
||||
|
||||
`TarReader.RequestInitialStream` and `RequestInitialStreamAsync` map the detected wrapper to the corresponding decompression stream:
|
||||
|
||||
- `None`
|
||||
- `BZip2`
|
||||
- `GZip`
|
||||
- `ZStandard`
|
||||
- `LZip`
|
||||
- `Xz`
|
||||
- `Lzw`
|
||||
|
||||
### Reader Entry Semantics
|
||||
|
||||
For each entry, SharpCompress exposes:
|
||||
|
||||
- `Key` from the parsed tar name
|
||||
- `LinkTarget` for symbolic and hard links
|
||||
- `Size`
|
||||
- `CompressedSize`
|
||||
- `LastModifiedTime`
|
||||
- `IsDirectory`
|
||||
- `Mode`
|
||||
- `UserID`
|
||||
- `GroupId`
|
||||
|
||||
Tar entries are always reported as unencrypted and CRC is always `0`.
|
||||
|
||||
## Archive Behavior
|
||||
|
||||
`TarArchive` is the archive API.
|
||||
|
||||
Implementation files:
|
||||
|
||||
- `src/SharpCompress/Archives/Tar/TarArchive.cs`
|
||||
- `src/SharpCompress/Archives/Tar/TarArchive.Async.cs`
|
||||
- `src/SharpCompress/Archives/Tar/TarArchive.Factory.cs`
|
||||
|
||||
### Open Behavior
|
||||
|
||||
Synchronous `TarArchive.OpenArchive(Stream)` requires a seekable stream and throws `ArgumentException` when `CanSeek` is `false`.
|
||||
|
||||
`TarArchive.OpenArchive(FileInfo)` and the list-based overloads use `SourceStream` and determine wrapper compression by calling `TarFactory.GetCompressionType`.
|
||||
|
||||
Asynchronous `OpenAsyncArchive` overloads use `TarFactory.GetCompressionTypeAsync` and do not enforce the same explicit seekability check at the public API boundary.
|
||||
|
||||
### Entry Loading
|
||||
|
||||
`TarArchive.LoadEntries` and `LoadEntriesAsync` parse entries differently depending on wrapper compression:
|
||||
|
||||
- Uncompressed tar uses `StreamingMode.Seekable`.
|
||||
- Wrapped tar uses `StreamingMode.Streaming` because the decompressed stream is not treated as random-access.
|
||||
|
||||
When seekable mode is used, the header stores `DataStartPosition`, and entries reopen data through `TarFilePart` by seeking back to the data position.
|
||||
|
||||
When streaming mode is used, the header stores a `PackedStream`, and entry access follows streaming semantics over the decompressed stream.
|
||||
|
||||
### Archive Rewrite Behavior
|
||||
|
||||
`TarArchive` supports creating and modifying archives through `AbstractWritableArchive`:
|
||||
|
||||
- add file entries
|
||||
- add directory entries
|
||||
- remove entries
|
||||
- save to a new stream or path
|
||||
|
||||
Archive rewrite is implemented by enumerating the existing and new entries and writing them back out through `TarWriter`.
|
||||
|
||||
## Writer Behavior
|
||||
|
||||
`TarWriter` is the forward-only tar writer.
|
||||
|
||||
Implementation files:
|
||||
|
||||
- `src/SharpCompress/Writers/Tar/TarWriter.cs`
|
||||
- `src/SharpCompress/Writers/Tar/TarWriter.Async.cs`
|
||||
- `src/SharpCompress/Writers/Tar/TarWriterOptions.cs`
|
||||
|
||||
### Supported Output Compression
|
||||
|
||||
The writer supports these output compression types:
|
||||
|
||||
- `CompressionType.None`
|
||||
- `CompressionType.GZip`
|
||||
- `CompressionType.BZip2`
|
||||
- `CompressionType.LZip`
|
||||
|
||||
Any other compression type causes `InvalidFormatException`.
|
||||
|
||||
### Stream Ownership
|
||||
|
||||
If `LeaveStreamOpen` is `true`, `TarWriter` wraps the destination in a non-disposing stream.
|
||||
|
||||
### File Writing
|
||||
|
||||
`TarWriter.Write` and `WriteAsync` write a tar header followed by file contents, then pad the payload to the next 512-byte boundary.
|
||||
|
||||
If the source stream is non-seekable and the caller does not supply `size`, the writer throws `ArgumentException` because tar requires the file size in the header.
|
||||
|
||||
### Directory Writing
|
||||
|
||||
`WriteDirectory` and `WriteDirectoryAsync` normalize the directory name to use forward slashes and ensure the key ends with `/`.
|
||||
|
||||
Empty or root-equivalent directory names are skipped.
|
||||
|
||||
### Archive Finalization
|
||||
|
||||
If `FinalizeArchiveOnClose` is `true`, disposing the writer writes two 512-byte zero blocks to terminate the archive.
|
||||
|
||||
If the output stream implements `IFinishable`, dispose also calls `Finish()`.
|
||||
|
||||
## Header Write Formats
|
||||
|
||||
`TarHeaderWriteFormat` is defined in `src/SharpCompress/Common/Tar/Headers/TarHeaderWriteFormat.cs`.
|
||||
|
||||
Supported write formats:
|
||||
|
||||
- `GNU_TAR_LONG_LINK`
|
||||
- `USTAR`
|
||||
|
||||
`TarWriterOptions.HeaderFormat` defaults to `GNU_TAR_LONG_LINK`.
|
||||
|
||||
Current implementation behavior is narrower than the option surface suggests:
|
||||
|
||||
- sync file writes use the configured `HeaderFormat`
|
||||
- sync directory writes currently construct the default tar header format
|
||||
- async file writes currently construct the default tar header format
|
||||
- async directory writes currently construct the default tar header format
|
||||
|
||||
In practice, this means the configured `HeaderFormat` is currently honored only by the synchronous file write path.
|
||||
|
||||
### GNU Long Name Write Behavior
|
||||
|
||||
In GNU mode, when a file name exceeds the 100-byte field, `TarHeader.WriteGnuTarLongLink` writes a synthetic long-name header using `././@LongLink` and `EntryType.LongName`, then writes the long name payload, and finally writes the actual file entry.
|
||||
|
||||
GNU mode also writes large file sizes using binary size encoding when the size does not fit the standard octal field.
|
||||
|
||||
### USTAR Write Behavior
|
||||
|
||||
When the synchronous file write path is configured for `USTAR`, `TarHeader.WriteUstar` attempts to split a long path into:
|
||||
|
||||
- the main `name` field
|
||||
- the `prefix` field
|
||||
|
||||
If the name cannot be represented in USTAR field limits, the writer throws `InvalidFormatException` and instructs the caller to use GNU Tar format instead.
|
||||
|
||||
## Header Read Behavior
|
||||
|
||||
Tar header parsing is implemented in `TarHeader.Read` and `TarHeader.ReadAsync`.
|
||||
|
||||
### Implemented Read Features
|
||||
|
||||
| Feature | Read support |
|
||||
| ------- | ------------ |
|
||||
| Regular file entries | Yes |
|
||||
| Directory entries | Yes |
|
||||
| Symbolic link target reading | Yes |
|
||||
| Hard link target reading | Yes |
|
||||
| GNU long name (`L`) | Yes |
|
||||
| GNU long link (`K`) | Yes |
|
||||
| USTAR prefix reconstruction | Yes |
|
||||
| Binary size field parsing | Yes |
|
||||
| oldgnu uid/gid numeric quirk parsing | Yes |
|
||||
| POSIX and signed checksum validation | Yes |
|
||||
|
||||
### Entry Types Recognized by the Code
|
||||
|
||||
`EntryType` currently declares these values in `src/SharpCompress/Common/Tar/Headers/EntryType.cs`:
|
||||
|
||||
- `File`
|
||||
- `OldFile`
|
||||
- `HardLink`
|
||||
- `SymLink`
|
||||
- `CharDevice`
|
||||
- `BlockDevice`
|
||||
- `Directory`
|
||||
- `Fifo`
|
||||
- `LongLink`
|
||||
- `LongName`
|
||||
- `SparseFile`
|
||||
- `VolumeHeader`
|
||||
- `GlobalExtendedHeader`
|
||||
|
||||
SharpCompress currently has explicit handling for only a subset of those values during read and write.
|
||||
|
||||
### Long Name and Long Link Reads
|
||||
|
||||
When `TarHeader.Read` encounters `EntryType.LongName` or `EntryType.LongLink`, it reads the payload and applies it to the next real header.
|
||||
|
||||
Long-name payload reads are capped at `32768` bytes to avoid memory exhaustion from malformed archives.
|
||||
|
||||
### Name Reconstruction
|
||||
|
||||
For USTAR headers, if the magic field is `ustar` and the prefix field is populated, SharpCompress reconstructs the entry name as `prefix + "/" + name`.
|
||||
|
||||
## Name and Metadata Handling
|
||||
|
||||
### Path Normalization
|
||||
|
||||
Writer path normalization is implemented in `TarWriter.NormalizeFilename` and `NormalizeDirectoryName`.
|
||||
|
||||
Behavior:
|
||||
|
||||
- backslashes are converted to `/`
|
||||
- drive prefixes before `:` are removed
|
||||
- leading and trailing `/` are trimmed for file entries
|
||||
- directory entries are normalized to end with `/`
|
||||
|
||||
### Encoding
|
||||
|
||||
Tar name encoding and decoding is controlled by `IArchiveEncoding`.
|
||||
|
||||
- reader APIs decode names with `ReaderOptions.ArchiveEncoding`
|
||||
- writer APIs encode names with `TarWriterOptions.ArchiveEncoding`
|
||||
|
||||
The tests include UTF-8 and code page coverage for tar name handling.
|
||||
|
||||
### Metadata Surface
|
||||
|
||||
Tar metadata currently surfaced through `TarEntry` includes:
|
||||
|
||||
- name
|
||||
- link target
|
||||
- mode
|
||||
- uid
|
||||
- gid
|
||||
- size
|
||||
- last modified time
|
||||
|
||||
Writer metadata is narrower. The writer sets:
|
||||
|
||||
- `LastModifiedTime`
|
||||
- `Name`
|
||||
- `Size`
|
||||
- entry type for file or directory
|
||||
|
||||
The current writer writes fixed mode, owner id, and group id defaults rather than round-tripping full metadata.
|
||||
|
||||
## Async Behavior
|
||||
|
||||
Async tar support is provided by:
|
||||
|
||||
- `TarArchive.OpenAsyncArchive`
|
||||
- `TarReader.OpenAsyncReader`
|
||||
- `TarWriter.WriteAsync`
|
||||
- `TarWriter.WriteDirectoryAsync`
|
||||
- `TarHeader.ReadAsync`
|
||||
- `TarHeader.WriteAsync`
|
||||
|
||||
The async implementations generally mirror the sync implementations while using async header parsing, decompression, and stream copy paths. The most important current exception is `TarWriterOptions.HeaderFormat`, which is not consistently honored outside the synchronous file write path.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
This section documents current implementation limits, not desired future behavior.
|
||||
|
||||
### Write limitations
|
||||
|
||||
- No write support for `tar.xz`
|
||||
- No write support for `tar.zst`
|
||||
- No write support for `tar.Z`
|
||||
- No public API for writing symbolic links or hard links
|
||||
- No PAX write support
|
||||
- No sparse file write support
|
||||
- No device or FIFO write support
|
||||
|
||||
### Read limitations or partial support
|
||||
|
||||
- No explicit PAX local header support
|
||||
- No semantic sparse file handling beyond recognizing the entry type enum value
|
||||
- No semantic global extended header handling beyond recognizing the entry type enum value
|
||||
- No special device or FIFO object model beyond the raw entry type information available internally
|
||||
|
||||
### Archive behavior limitations
|
||||
|
||||
- Sync archive open requires a seekable input stream
|
||||
- Compressed tar archive access is not full random-access in the same sense as uncompressed seekable tar
|
||||
|
||||
## Test Coverage Map
|
||||
|
||||
Tar tests live in `tests/SharpCompress.Test/Tar/`.
|
||||
|
||||
Representative coverage:
|
||||
|
||||
| Area | Tests |
|
||||
| ---- | ----- |
|
||||
| Wrapper detection and reading | `TarReaderTests.cs`, `TarReaderAsyncTests.cs` |
|
||||
| Archive open and rewrite | `TarArchiveTests.cs`, `TarArchiveAsyncTests.cs` |
|
||||
| Writer behavior | `TarWriterTests.cs`, `TarWriterAsyncTests.cs` |
|
||||
| Directory entry behavior | `TarWriterDirectoryTests.cs`, `TarArchiveDirectoryTests.cs` |
|
||||
| Long-name behavior | `TarArchiveTests.cs`, `TarReaderTests.cs` |
|
||||
| Corruption and broken stream handling | `TarReaderTests.cs`, `TarReaderAsyncTests.cs` |
|
||||
|
||||
Representative tar test archives in `tests/TestArchives/Archives/`:
|
||||
|
||||
- `Tar.tar`
|
||||
- `Tar.tar.gz`
|
||||
- `Tar.tar.bz2`
|
||||
- `Tar.tar.lz`
|
||||
- `Tar.tar.xz`
|
||||
- `Tar.tar.zst`
|
||||
- `Tar.tar.Z`
|
||||
- `Tar.oldgnu.tar.gz`
|
||||
- `very long filename.tar`
|
||||
- `ustar with long names.tar`
|
||||
- `Tar.LongPathsWithLongNameExtension.tar`
|
||||
- `Tar.Empty.tar`
|
||||
- `TarCorrupted.tar`
|
||||
- `TarWithSymlink.tar.gz`
|
||||
|
||||
## Summary
|
||||
|
||||
SharpCompress Tar support is centered around:
|
||||
|
||||
- broad read support for common tar wrappers
|
||||
- forward-only reader behavior for streamed extraction
|
||||
- seekable archive support for uncompressed tar and archive rewrite workflows
|
||||
- narrower write support than read support
|
||||
- GNU long-name and USTAR write support
|
||||
- partial coverage for less common tar dialect features
|
||||
108
docs/USAGE.md
108
docs/USAGE.md
@@ -236,48 +236,38 @@ The registry also exposes `GetCompressingProvider` (now returning `ICompressionP
|
||||
|
||||
**Extract single entry asynchronously:**
|
||||
```C#
|
||||
using (Stream stream = File.OpenRead("archive.zip"))
|
||||
using (var reader = ReaderFactory.OpenReader(stream))
|
||||
using Stream stream = File.OpenRead("archive.zip");
|
||||
await using var reader = await ReaderFactory.OpenAsyncReader(stream, cancellationToken: cancellationToken);
|
||||
while (await reader.MoveToNextEntryAsync(cancellationToken))
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
using (var entryStream = reader.OpenEntryStream())
|
||||
{
|
||||
using (var outputStream = File.Create("output.bin"))
|
||||
{
|
||||
await reader.WriteEntryToAsync(outputStream, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
using var outputStream = File.Create("output.bin");
|
||||
await reader.WriteEntryToAsync(outputStream, cancellationToken);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Extract all entries asynchronously:**
|
||||
```C#
|
||||
using (Stream stream = File.OpenRead("archive.tar.gz"))
|
||||
using (var reader = ReaderFactory.OpenReader(stream))
|
||||
{
|
||||
await reader.WriteAllToDirectoryAsync(
|
||||
@"D:\temp",
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
using Stream stream = File.OpenRead("archive.tar.gz");
|
||||
await using var reader = await ReaderFactory.OpenAsyncReader(stream, cancellationToken: cancellationToken);
|
||||
await reader.WriteAllToDirectoryAsync(
|
||||
@"D:\temp",
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
```
|
||||
|
||||
**Open and process entry stream asynchronously:**
|
||||
```C#
|
||||
using (var archive = ZipArchive.OpenArchive("archive.zip"))
|
||||
await using var archive = await ZipArchive.OpenAsyncArchive("archive.zip", cancellationToken: cancellationToken);
|
||||
await foreach (var entry in archive.EntriesAsync)
|
||||
{
|
||||
foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
|
||||
if (!entry.IsDirectory)
|
||||
{
|
||||
using (var entryStream = await entry.OpenEntryStreamAsync(cancellationToken))
|
||||
{
|
||||
// Process the decompressed stream asynchronously
|
||||
await ProcessStreamAsync(entryStream, cancellationToken);
|
||||
}
|
||||
using var entryStream = await entry.OpenEntryStreamAsync(cancellationToken);
|
||||
// Process the decompressed stream asynchronously
|
||||
await ProcessStreamAsync(entryStream, cancellationToken);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -286,28 +276,22 @@ using (var archive = ZipArchive.OpenArchive("archive.zip"))
|
||||
|
||||
**Write single file asynchronously:**
|
||||
```C#
|
||||
using (Stream archiveStream = File.OpenWrite("output.zip"))
|
||||
using (var writer = WriterFactory.OpenWriter(archiveStream, ArchiveType.Zip, CompressionType.Deflate))
|
||||
{
|
||||
using (Stream fileStream = File.OpenRead("input.txt"))
|
||||
{
|
||||
await writer.WriteAsync("entry.txt", fileStream, DateTime.Now, cancellationToken);
|
||||
}
|
||||
}
|
||||
using Stream archiveStream = File.OpenWrite("output.zip");
|
||||
await using var writer = await WriterFactory.OpenAsyncWriter(archiveStream, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate), cancellationToken);
|
||||
using Stream fileStream = File.OpenRead("input.txt");
|
||||
await writer.WriteAsync("entry.txt", fileStream, DateTime.Now, cancellationToken);
|
||||
```
|
||||
|
||||
**Write entire directory asynchronously:**
|
||||
```C#
|
||||
using (Stream stream = File.OpenWrite("backup.tar.gz"))
|
||||
using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip)))
|
||||
{
|
||||
await writer.WriteAllAsync(
|
||||
@"D:\files",
|
||||
"*",
|
||||
SearchOption.AllDirectories,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
using Stream stream = File.OpenWrite("backup.tar.gz");
|
||||
await using var writer = await WriterFactory.OpenAsyncWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip), cancellationToken);
|
||||
await writer.WriteAllAsync(
|
||||
@"D:\files",
|
||||
"*",
|
||||
SearchOption.AllDirectories,
|
||||
cancellationToken
|
||||
);
|
||||
```
|
||||
|
||||
**Write with progress tracking and cancellation:**
|
||||
@@ -317,17 +301,15 @@ var cts = new CancellationTokenSource();
|
||||
// Set timeout or cancel from UI
|
||||
cts.CancelAfter(TimeSpan.FromMinutes(5));
|
||||
|
||||
using (Stream stream = File.OpenWrite("archive.zip"))
|
||||
using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate))
|
||||
using Stream stream = File.OpenWrite("archive.zip");
|
||||
await using var writer = await WriterFactory.OpenAsyncWriter(stream, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate), cts.Token);
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await writer.WriteAllAsync(@"D:\data", "*", SearchOption.AllDirectories, cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Operation was cancelled");
|
||||
}
|
||||
await writer.WriteAllAsync(@"D:\data", "*", SearchOption.AllDirectories, cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Operation was cancelled");
|
||||
}
|
||||
```
|
||||
|
||||
@@ -335,14 +317,12 @@ using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, Compressio
|
||||
|
||||
**Extract from archive asynchronously:**
|
||||
```C#
|
||||
using (var archive = ZipArchive.OpenArchive("archive.zip"))
|
||||
{
|
||||
// Simple async extraction - works for all archive types
|
||||
await archive.WriteToDirectoryAsync(
|
||||
@"C:\output",
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
await using var archive = await ZipArchive.OpenAsyncArchive("archive.zip", cancellationToken: cancellationToken);
|
||||
// Simple async extraction - works for all archive types
|
||||
await archive.WriteToDirectoryAsync(
|
||||
@"C:\output",
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
```
|
||||
|
||||
**Benefits of Async Operations:**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@ public abstract partial class AbstractArchive<TEntry, TVolume> : IArchive, IAsyn
|
||||
internal AbstractArchive(ArchiveType type)
|
||||
{
|
||||
Type = type;
|
||||
ReaderOptions = new();
|
||||
ReaderOptions = ReaderOptions.Default;
|
||||
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
|
||||
_lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
|
||||
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
@@ -34,7 +31,11 @@ public static partial class ArchiveFactory
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken);
|
||||
return OpenAsyncArchive(
|
||||
new FileInfo(filePath),
|
||||
options ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
@@ -53,20 +54,20 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var filesArray = fileInfos.ToArray();
|
||||
if (filesArray.Length == 0)
|
||||
var filesArray = fileInfos;
|
||||
if (filesArray.Count == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No files to open");
|
||||
}
|
||||
|
||||
var fileInfo = filesArray[0];
|
||||
if (filesArray.Length == 1)
|
||||
if (filesArray.Count == 1)
|
||||
{
|
||||
return await OpenAsyncArchive(fileInfo, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -83,21 +84,15 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
streams.NotNull(nameof(streams));
|
||||
var streamsArray = streams.ToArray();
|
||||
if (streamsArray.Length == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No streams");
|
||||
}
|
||||
|
||||
var streamsArray = streams.RequireReadable().RequireSeekable().ToList();
|
||||
var firstStream = streamsArray[0];
|
||||
if (streamsArray.Length == 1)
|
||||
if (streamsArray.Count == 1)
|
||||
{
|
||||
return await OpenAsyncArchive(firstStream, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -112,64 +107,4 @@ public static partial class ArchiveFactory
|
||||
.OpenAsyncArchive(streamsArray, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static ValueTask<T> FindFactoryAsync<T>(
|
||||
string path,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return FindFactoryAsync<T>(new FileInfo(path), cancellationToken);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo finfo,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
finfo.NotNull(nameof(finfo));
|
||||
using Stream stream = finfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
}
|
||||
|
||||
var factories = Factory.Factories.OfType<T>();
|
||||
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
if (
|
||||
await factory
|
||||
.IsArchiveAsync(stream, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
var extensions = string.Join(", ", factories.Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
264
src/SharpCompress/Archives/ArchiveFactory.Detection.cs
Normal file
264
src/SharpCompress/Archives/ArchiveFactory.Detection.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
public static partial class ArchiveFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path asynchronously,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await GetArchiveInformationAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path asynchronously,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return await GetArchiveInformationAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream asynchronously,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await GetArchiveInformationAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream asynchronously,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = await TryFindFactoryAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return factory is null
|
||||
? null
|
||||
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
|
||||
}
|
||||
|
||||
internal static ValueTask<T> FindFactoryAsync<T>(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
// Use the shared async detection loop over all factories. If the matched factory
|
||||
// implements T we return it; otherwise (or if nothing matched) we fall through
|
||||
// to the same "unsupported format" exception that the original code produced,
|
||||
// listing the T-typed factories as the hint for the caller.
|
||||
var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (factory is T typedFactory)
|
||||
{
|
||||
return typedFactory;
|
||||
}
|
||||
|
||||
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async counterpart of <see cref="ArchiveFactory.TryFindFactory"/>.
|
||||
/// Iterates all registered factories and returns the first one whose
|
||||
/// <see cref="IFactory.IsArchiveAsync"/> recognises the stream, or <see langword="null"/>.
|
||||
/// Stream position is restored to its value at entry on both success and failure.
|
||||
/// </summary>
|
||||
private static async ValueTask<IFactory?> TryFindFactoryAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
await TryFindFactoryAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
private static async ValueTask<IFactory?> TryFindFactoryAsync(
|
||||
Stream stream,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
var isArchive = await factory
|
||||
.IsArchiveAsync(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(string filePath) =>
|
||||
GetArchiveInformation(filePath, ReaderOptions.ForFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return GetArchiveInformation(stream, readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(Stream stream) =>
|
||||
GetArchiveInformation(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions
|
||||
)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
return factory is null
|
||||
? null
|
||||
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates all registered factories and returns the first one whose
|
||||
/// <see cref="IFactory.IsArchive"/> recognises the stream, or <see langword="null"/>.
|
||||
/// Stream position is restored to its value at entry on both success and failure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the shared, seekable-stream detection core used by
|
||||
/// <see cref="FindFactory{T}(Stream)"/>, <see cref="IsArchive(Stream, out ArchiveType?)"/>,
|
||||
/// and <see cref="GetArchiveInformation(Stream)"/>.
|
||||
/// <para>
|
||||
/// <see cref="ReaderFactory.OpenReader(Stream, ReaderOptions)"/> uses a separate code path
|
||||
/// based on <see cref="IO.SharpCompressStream"/> rewindable buffering, which supports
|
||||
/// non-seekable streams and is therefore not unified with this helper.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static IFactory? TryFindFactory(Stream stream) =>
|
||||
TryFindFactory(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
private static IFactory? TryFindFactory(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
var isArchive = factory.IsArchive(stream, readerOptions);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Factories;
|
||||
@@ -21,7 +23,7 @@ public static partial class ArchiveFactory
|
||||
where TOptions : IWriterOptions
|
||||
{
|
||||
var factory = Factory
|
||||
.Factories.OfType<IWriteableArchiveFactory<TOptions>>()
|
||||
.Factories.OfType<IWritableArchiveFactory<TOptions>>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (factory != null)
|
||||
@@ -35,7 +37,7 @@ public static partial class ArchiveFactory
|
||||
public static IArchive OpenArchive(string filePath, ReaderOptions? options = null)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenArchive(new FileInfo(filePath), options);
|
||||
return OpenArchive(new FileInfo(filePath), options ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null)
|
||||
@@ -46,19 +48,19 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? options = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var filesArray = fileInfos.ToArray();
|
||||
if (filesArray.Length == 0)
|
||||
var filesArray = fileInfos;
|
||||
if (filesArray.Count == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No files to open");
|
||||
}
|
||||
|
||||
var fileInfo = filesArray[0];
|
||||
if (filesArray.Length == 1)
|
||||
if (filesArray.Count == 1)
|
||||
{
|
||||
return OpenArchive(fileInfo, options);
|
||||
}
|
||||
@@ -69,17 +71,16 @@ public static partial class ArchiveFactory
|
||||
return FindFactory<IMultiArchiveFactory>(fileInfo).OpenArchive(filesArray, options);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
public static IArchive OpenArchive(IReadOnlyList<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var streamsArray = streams.ToArray();
|
||||
if (streamsArray.Length == 0)
|
||||
var streamsArray = streams.RequireReadable().RequireSeekable().ToList();
|
||||
if (streamsArray.Count == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No streams");
|
||||
}
|
||||
|
||||
var firstStream = streamsArray[0];
|
||||
if (streamsArray.Length == 1)
|
||||
if (streamsArray.Count == 1)
|
||||
{
|
||||
return OpenArchive(firstStream, options);
|
||||
}
|
||||
@@ -100,11 +101,11 @@ public static partial class ArchiveFactory
|
||||
archive.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
|
||||
public static T FindFactory<T>(string path)
|
||||
public static T FindFactory<T>(string filePath)
|
||||
where T : IFactory
|
||||
{
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
using Stream stream = File.OpenRead(path);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return FindFactory<T>(stream);
|
||||
}
|
||||
|
||||
@@ -119,29 +120,20 @@ public static partial class ArchiveFactory
|
||||
public static T FindFactory<T>(Stream stream)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
// Use the shared detection loop over all factories. If the matched factory
|
||||
// implements T we return it; otherwise (or if nothing matched) we fall through
|
||||
// to the same "unsupported format" exception that the original code produced,
|
||||
// listing the T-typed factories as the hint for the caller.
|
||||
var factory = TryFindFactory(stream);
|
||||
if (factory is T typedFactory)
|
||||
{
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
return typedFactory;
|
||||
}
|
||||
|
||||
var factories = Factory.Factories.OfType<T>();
|
||||
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
if (factory.IsArchive(stream))
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
var extensions = string.Join(", ", factories.Select(item => item.Name));
|
||||
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
@@ -149,37 +141,82 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static bool IsArchive(string filePath, out ArchiveType? type)
|
||||
{
|
||||
return IsArchive(filePath, ReaderOptions.ForFilePath, out type);
|
||||
}
|
||||
|
||||
public static bool IsArchive(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
out ArchiveType? type
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream s = File.OpenRead(filePath);
|
||||
return IsArchive(s, out type);
|
||||
return IsArchive(s, readerOptions ?? ReaderOptions.ForFilePath, out type);
|
||||
}
|
||||
|
||||
public static bool IsArchive(Stream stream, out ArchiveType? type)
|
||||
{
|
||||
type = null;
|
||||
stream.NotNull(nameof(stream));
|
||||
return IsArchive(stream, ReaderOptions.ForExternalStream, out type);
|
||||
}
|
||||
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
}
|
||||
public static bool IsArchive(Stream stream, ReaderOptions? readerOptions, out ArchiveType? type)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var startPosition = stream.Position;
|
||||
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
type = factory?.KnownArchiveType;
|
||||
return factory is not null;
|
||||
}
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
var isArchive = factory.IsArchive(stream);
|
||||
stream.Position = startPosition;
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await IsArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
type = factory.KnownArchiveType;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return await IsArchiveAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await IsArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = await TryFindFactoryAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return (factory is not null, factory?.KnownArchiveType);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetFileParts(string part1)
|
||||
|
||||
22
src/SharpCompress/Archives/ArchiveInformation.cs
Normal file
22
src/SharpCompress/Archives/ArchiveInformation.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about a detected archive, including its type and supported capabilities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="ArchiveFactory.GetArchiveInformation(System.IO.Stream)"/> or
|
||||
/// <see cref="ArchiveFactory.GetArchiveInformationAsync(System.IO.Stream,System.Threading.CancellationToken)"/>
|
||||
/// to obtain an instance of this record.
|
||||
/// </remarks>
|
||||
/// <param name="Type">
|
||||
/// The type of archive detected, or <see langword="null"/> when the format is not a registered well-known type.
|
||||
/// </param>
|
||||
/// <param name="SupportsRandomAccess">
|
||||
/// <see langword="true"/> when this archive format supports random access via the <see cref="IArchive"/> API,
|
||||
/// meaning the full file listing can be retrieved without decompressing the entire archive.
|
||||
/// <see langword="false"/> when only the <see cref="SharpCompress.Readers.IReader"/> API is available,
|
||||
/// which reads entries sequentially and can only report per-entry progress.
|
||||
/// </param>
|
||||
public record ArchiveInformation(ArchiveType? Type, bool SupportsRandomAccess);
|
||||
@@ -21,14 +21,14 @@ public partial class GZipArchive
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncArchive(new FileInfo(filePath), readerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
@@ -37,7 +37,7 @@ public partial class GZipArchive
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
@@ -50,39 +50,38 @@ public partial class GZipArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new GZipArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new GZipArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -92,15 +91,11 @@ public partial class GZipArchive
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new GZipArchive(
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public interface IArchiveOpenable<TSync, TASync>
|
||||
public static abstract TSync OpenArchive(Stream stream, ReaderOptions? readerOptions = null);
|
||||
|
||||
public static abstract ValueTask<TASync> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -12,12 +12,12 @@ public interface IMultiArchiveOpenable<TSync, TASync>
|
||||
where TASync : IAsyncArchive
|
||||
{
|
||||
public static abstract TSync OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
);
|
||||
|
||||
public static abstract TSync OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
@@ -11,7 +9,7 @@ public static class IWritableArchiveExtensions
|
||||
extension(IWritableArchive writableArchive)
|
||||
{
|
||||
public void AddAllFromDirectory(
|
||||
string filePath,
|
||||
string directoryPath,
|
||||
string searchPattern = "*.*",
|
||||
SearchOption searchOption = SearchOption.AllDirectories
|
||||
)
|
||||
@@ -19,12 +17,16 @@ public static class IWritableArchiveExtensions
|
||||
using (writableArchive.PauseEntryRebuilding())
|
||||
{
|
||||
foreach (
|
||||
var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)
|
||||
var filePath in Directory.EnumerateFiles(
|
||||
directoryPath,
|
||||
searchPattern,
|
||||
searchOption
|
||||
)
|
||||
)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
writableArchive.AddEntry(
|
||||
path.Substring(filePath.Length),
|
||||
filePath.Substring(directoryPath.Length),
|
||||
fileInfo.OpenRead(),
|
||||
true,
|
||||
fileInfo.Length,
|
||||
|
||||
@@ -3,7 +3,7 @@ using SharpCompress.Common.Options;
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
/// <summary>
|
||||
/// Decorator for <see cref="Factories.Factory"/> used to declare an archive format as able to create writeable archives
|
||||
/// Decorator for <see cref="Factories.Factory"/> used to declare an archive format as able to create writable archives.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implemented by:<br/>
|
||||
@@ -12,7 +12,8 @@ namespace SharpCompress.Archives;
|
||||
/// <item><see cref="Factories.ZipFactory"/></item>
|
||||
/// <item><see cref="Factories.GZipFactory"/></item>
|
||||
/// </list>
|
||||
public interface IWriteableArchiveFactory<TOptions> : Factories.IFactory
|
||||
/// </remarks>
|
||||
public interface IWritableArchiveFactory<TOptions> : Factories.IFactory
|
||||
where TOptions : IWriterOptions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -2,9 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
@@ -13,7 +11,7 @@ public static class IWritableAsyncArchiveExtensions
|
||||
extension(IWritableAsyncArchive writableArchive)
|
||||
{
|
||||
public async ValueTask AddAllFromDirectoryAsync(
|
||||
string filePath,
|
||||
string directoryPath,
|
||||
string searchPattern = "*.*",
|
||||
SearchOption searchOption = SearchOption.AllDirectories
|
||||
)
|
||||
@@ -21,13 +19,17 @@ public static class IWritableAsyncArchiveExtensions
|
||||
using (writableArchive.PauseEntryRebuilding())
|
||||
{
|
||||
foreach (
|
||||
var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)
|
||||
var filePath in Directory.EnumerateFiles(
|
||||
directoryPath,
|
||||
searchPattern,
|
||||
searchOption
|
||||
)
|
||||
)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
await writableArchive
|
||||
.AddEntryAsync(
|
||||
path.Substring(filePath.Length),
|
||||
filePath.Substring(directoryPath.Length),
|
||||
fileInfo.OpenRead(),
|
||||
true,
|
||||
fileInfo.Length,
|
||||
|
||||
@@ -15,18 +15,17 @@ namespace SharpCompress.Archives.Rar;
|
||||
internal class FileInfoRarArchiveVolume : RarVolume
|
||||
{
|
||||
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
|
||||
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
|
||||
: base(
|
||||
StreamingMode.Seekable,
|
||||
fileInfo.OpenRead(),
|
||||
options.WithLeaveStreamOpen(false),
|
||||
index
|
||||
)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
FileParts = GetVolumeFileParts().ToArray().ToReadOnly();
|
||||
}
|
||||
|
||||
private static ReaderOptions FixOptions(ReaderOptions options)
|
||||
{
|
||||
//make sure we're closing streams with fileinfo
|
||||
return options with { LeaveStreamOpen = false };
|
||||
}
|
||||
|
||||
internal ReadOnlyCollection<RarFilePart> FileParts { get; }
|
||||
|
||||
internal FileInfo FileInfo { get; }
|
||||
|
||||
@@ -21,14 +21,14 @@ public partial class RarArchive
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IRarAsyncArchive)OpenArchive(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
|
||||
@@ -39,7 +39,7 @@ public partial class RarArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -51,53 +51,48 @@ public partial class RarArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new RarArchive(
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new RarArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new RarArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -158,7 +153,7 @@ public partial class RarArchive
|
||||
{
|
||||
try
|
||||
{
|
||||
MarkHeader.Read(stream, true, false);
|
||||
MarkHeader.Read(stream, true, options?.LookForHeader ?? false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -177,7 +172,7 @@ public partial class RarArchive
|
||||
try
|
||||
{
|
||||
await MarkHeader
|
||||
.ReadAsync(stream, true, false, cancellationToken)
|
||||
.ReadAsync(stream, true, options?.LookForHeader ?? false, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,22 +17,25 @@ public partial class SevenZipArchive
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty("path");
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new(
|
||||
(IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions())
|
||||
(IAsyncArchive)OpenArchive(
|
||||
new FileInfo(filePath),
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
filePath.NotNullOrEmpty("filePath");
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
@@ -42,54 +45,49 @@ public partial class SevenZipArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,11 +143,14 @@ public partial class SevenZipArchive
|
||||
return IsSevenZipFile(stream);
|
||||
}
|
||||
|
||||
public static bool IsSevenZipFile(Stream stream)
|
||||
public static bool IsSevenZipFile(Stream stream) =>
|
||||
IsSevenZipFile(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
public static bool IsSevenZipFile(Stream stream, ReaderOptions? readerOptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SignatureMatch(stream);
|
||||
return SignatureMatch(stream, readerOptions?.LookForHeader ?? false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -160,12 +161,25 @@ public partial class SevenZipArchive
|
||||
public static async ValueTask<bool> IsSevenZipFileAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await IsSevenZipFileAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
public static async ValueTask<bool> IsSevenZipFileAsync(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
return await SignatureMatchAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
return await SignatureMatchAsync(
|
||||
stream,
|
||||
readerOptions?.LookForHeader ?? false,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -175,13 +189,29 @@ public partial class SevenZipArchive
|
||||
|
||||
private static ReadOnlySpan<byte> Signature => [(byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C];
|
||||
|
||||
private static bool SignatureMatch(Stream stream)
|
||||
private static bool SignatureMatch(Stream stream, bool lookForHeader)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(6);
|
||||
try
|
||||
{
|
||||
stream.ReadExact(buffer, 0, 6);
|
||||
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
|
||||
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
|
||||
for (var offset = 0; offset <= maxScanOffset; offset++)
|
||||
{
|
||||
stream.ReadExact(buffer, 0, 6);
|
||||
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Position -= 5;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -191,18 +221,39 @@ public partial class SevenZipArchive
|
||||
|
||||
private static async ValueTask<bool> SignatureMatchAsync(
|
||||
Stream stream,
|
||||
bool lookForHeader,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(6);
|
||||
try
|
||||
{
|
||||
if (!await stream.ReadFullyAsync(buffer, 0, 6, cancellationToken).ConfigureAwait(false))
|
||||
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
|
||||
for (var offset = 0; offset <= maxScanOffset; offset++)
|
||||
{
|
||||
return false;
|
||||
if (
|
||||
!await stream
|
||||
.ReadFullyAsync(buffer, 0, 6, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Position -= 5;
|
||||
}
|
||||
|
||||
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ public partial class TarArchive
|
||||
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var memoryStream = new PooledMemoryStream();
|
||||
await entryStream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
memoryStream.Position = 0;
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
@@ -38,23 +38,20 @@ public partial class TarArchive
|
||||
)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return OpenArchive(
|
||||
[fileInfo],
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
);
|
||||
return OpenArchive([fileInfo], readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IWritableArchive<TarWriterOptions> OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
var sourceStream = new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
);
|
||||
var compressionType = TarFactory.GetCompressionType(
|
||||
sourceStream,
|
||||
@@ -65,16 +62,15 @@ public partial class TarArchive
|
||||
}
|
||||
|
||||
public static IWritableArchive<TarWriterOptions> OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
var sourceStream = new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = TarFactory.GetCompressionType(
|
||||
sourceStream,
|
||||
@@ -89,12 +85,8 @@ public partial class TarArchive
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return OpenArchive([stream], readerOptions);
|
||||
}
|
||||
@@ -105,11 +97,12 @@ public partial class TarArchive
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
var sourceStream = new SourceStream(
|
||||
stream,
|
||||
i => null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
@@ -119,14 +112,14 @@ public partial class TarArchive
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncArchive(new FileInfo(filePath), readerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
@@ -137,7 +130,7 @@ public partial class TarArchive
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
var sourceStream = new SourceStream(fileInfo, i => null, readerOptions);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
@@ -153,12 +146,11 @@ public partial class TarArchive
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
var sourceStream = new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
@@ -175,11 +167,11 @@ public partial class TarArchive
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
var sourceStream = new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
|
||||
@@ -151,7 +151,7 @@ public partial class TarArchive
|
||||
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var memoryStream = new PooledMemoryStream();
|
||||
entryStream.CopyTo(memoryStream, Constants.BufferSize);
|
||||
memoryStream.Position = 0;
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
@@ -45,7 +45,7 @@ public partial class ZipArchive
|
||||
s = new SourceStream(
|
||||
v[0].Stream,
|
||||
i => i < v.Length ? v[i].Stream : null,
|
||||
new ReaderOptions() { LeaveStreamOpen = true }
|
||||
ReaderOptions.ForExternalStream
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -41,39 +41,38 @@ public partial class ZipArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<ZipWriterOptions> OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new ZipArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<ZipWriterOptions> OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new ZipArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -83,26 +82,22 @@ public partial class ZipArchive
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new ZipArchive(
|
||||
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, i => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(path, readerOptions));
|
||||
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(filePath, readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
|
||||
|
||||
@@ -86,7 +86,7 @@ public partial class ZipArchive
|
||||
s = new SourceStream(
|
||||
v[0].Stream,
|
||||
i => i < v.Length ? v[i].Stream : null,
|
||||
new ReaderOptions() { LeaveStreamOpen = true }
|
||||
ReaderOptions.ForExternalStream
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Breaking change:</b> Changed from field to init-only property in version 0.40.0.
|
||||
/// The default handler logs a warning message.
|
||||
/// If no handler is provided, symbolic links are silently skipped during extraction.
|
||||
/// </remarks>
|
||||
public Action<string, string>? SymbolicLinkHandler { get; init; }
|
||||
|
||||
@@ -58,10 +58,7 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// Creates a new ExtractionOptions instance with the specified overwrite behavior.
|
||||
/// </summary>
|
||||
/// <param name="overwrite">Whether to overwrite existing files.</param>
|
||||
public ExtractionOptions(bool overwrite)
|
||||
{
|
||||
Overwrite = overwrite;
|
||||
}
|
||||
public ExtractionOptions(bool overwrite) => Overwrite = overwrite;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ExtractionOptions instance with the specified extraction path and overwrite behavior.
|
||||
@@ -102,14 +99,4 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// </summary>
|
||||
public static ExtractionOptions PreserveMetadata =>
|
||||
new() { PreserveFileTime = true, PreserveAttributes = true };
|
||||
|
||||
/// <summary>
|
||||
/// Default symbolic link handler that logs a warning message.
|
||||
/// </summary>
|
||||
public static void DefaultSymbolicLinkHandler(string sourcePath, string targetPath)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace SharpCompress.Common.GZip;
|
||||
|
||||
public class GZipVolume : Volume
|
||||
{
|
||||
public GZipVolume(Stream stream, ReaderOptions? options, int index)
|
||||
public GZipVolume(Stream stream, ReaderOptions options, int index)
|
||||
: base(stream, options, index) { }
|
||||
|
||||
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
|
||||
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
|
||||
: base(fileInfo.OpenRead(), options.WithLeaveStreamOpen(false)) { }
|
||||
|
||||
public override bool IsFirstVolume => true;
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
internal static class EntryExtensions
|
||||
{
|
||||
internal static void PreserveExtractionOptions(
|
||||
this IEntry entry,
|
||||
string destinationFileName,
|
||||
ExtractionOptions options
|
||||
)
|
||||
{
|
||||
if (options.PreserveFileTime || options.PreserveAttributes)
|
||||
{
|
||||
var nf = new FileInfo(destinationFileName);
|
||||
if (!nf.Exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update file time to original packed time
|
||||
if (options.PreserveFileTime)
|
||||
{
|
||||
if (entry.CreatedTime.HasValue)
|
||||
{
|
||||
nf.CreationTime = entry.CreatedTime.Value;
|
||||
}
|
||||
|
||||
if (entry.LastModifiedTime.HasValue)
|
||||
{
|
||||
nf.LastWriteTime = entry.LastModifiedTime.Value;
|
||||
}
|
||||
|
||||
if (entry.LastAccessedTime.HasValue)
|
||||
{
|
||||
nf.LastAccessTime = entry.LastAccessedTime.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.PreserveAttributes)
|
||||
{
|
||||
if (entry.Attrib.HasValue)
|
||||
{
|
||||
nf.Attributes = (FileAttributes)
|
||||
System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,18 +86,7 @@ internal static partial class IEntryExtensions
|
||||
options ??= new ExtractionOptions();
|
||||
if (entry.LinkTarget != null)
|
||||
{
|
||||
if (options.SymbolicLinkHandler is not null)
|
||||
{
|
||||
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtractionOptions.DefaultSymbolicLinkHandler(
|
||||
destinationFileName,
|
||||
entry.LinkTarget
|
||||
);
|
||||
}
|
||||
return;
|
||||
options.SymbolicLinkHandler?.Invoke(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -107,19 +107,7 @@ internal static partial class IEntryExtensions
|
||||
options ??= new ExtractionOptions();
|
||||
if (entry.LinkTarget != null)
|
||||
{
|
||||
if (options.SymbolicLinkHandler is not null)
|
||||
{
|
||||
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtractionOptions.DefaultSymbolicLinkHandler(
|
||||
destinationFileName,
|
||||
entry.LinkTarget
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
options.SymbolicLinkHandler?.Invoke(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -134,5 +122,69 @@ internal static partial class IEntryExtensions
|
||||
entry.PreserveExtractionOptions(destinationFileName, options);
|
||||
}
|
||||
}
|
||||
|
||||
internal void PreserveExtractionOptions(
|
||||
string destinationFileName,
|
||||
ExtractionOptions options
|
||||
)
|
||||
{
|
||||
if (options.PreserveFileTime || options.PreserveAttributes)
|
||||
{
|
||||
var nf = new FileInfo(destinationFileName);
|
||||
if (!nf.Exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update file time to original packed time
|
||||
if (options.PreserveFileTime)
|
||||
{
|
||||
if (entry.CreatedTime.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
nf.CreationTime = entry.CreatedTime.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid time or the OS rejected
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.LastModifiedTime.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
nf.LastWriteTime = entry.LastModifiedTime.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid time or the OS rejected
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.LastAccessedTime.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
nf.LastAccessTime = entry.LastAccessedTime.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid time or the OS rejected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.PreserveAttributes)
|
||||
{
|
||||
if (entry.Attrib.HasValue)
|
||||
{
|
||||
nf.Attributes = (FileAttributes)
|
||||
Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace SharpCompress.Common.Lzw;
|
||||
|
||||
public class LzwVolume : Volume
|
||||
{
|
||||
public LzwVolume(Stream stream, ReaderOptions? options, int index)
|
||||
public LzwVolume(Stream stream, ReaderOptions options, int index)
|
||||
: base(stream, options, index) { }
|
||||
|
||||
public LzwVolume(FileInfo fileInfo, ReaderOptions options)
|
||||
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
|
||||
: base(fileInfo.OpenRead(), options.WithLeaveStreamOpen(false)) { }
|
||||
|
||||
public override bool IsFirstVolume => true;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal class CryptKey5 : ICryptKey
|
||||
)
|
||||
{
|
||||
var passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
#if LEGACY_DOTNET || NET5_0
|
||||
#if LEGACY_DOTNET
|
||||
using var hmac = new HMACSHA256(passwordBytes);
|
||||
var block = hmac.ComputeHash(salt);
|
||||
#else
|
||||
@@ -50,7 +50,7 @@ internal class CryptKey5 : ICryptKey
|
||||
{
|
||||
for (var i = 1; i < loop[x]; i++)
|
||||
{
|
||||
#if LEGACY_DOTNET || NET5_0
|
||||
#if LEGACY_DOTNET
|
||||
block = hmac.ComputeHash(block);
|
||||
#else
|
||||
block = HMACSHA256.HashData(passwordBytes, block);
|
||||
|
||||
@@ -1182,7 +1182,7 @@ internal partial class ArchiveReader
|
||||
}
|
||||
else
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_stream = new PooledMemoryStream();
|
||||
}
|
||||
_rem = _db._files[index].Size;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SharpCompress.Compressors.LZMA.Utilities;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.SevenZip;
|
||||
|
||||
@@ -215,7 +216,7 @@ internal sealed class SevenZipFilesInfoWriter
|
||||
Action<Stream> writeData
|
||||
)
|
||||
{
|
||||
using var dataStream = new MemoryStream();
|
||||
using var dataStream = new PooledMemoryStream();
|
||||
writeData(dataStream);
|
||||
|
||||
stream.WriteByte((byte)propertyId);
|
||||
|
||||
@@ -59,7 +59,7 @@ internal sealed partial class TarHeader
|
||||
int splitIndex = -1;
|
||||
for (int i = 0; i < dirSeps.Count; i++)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
int count = ArchiveEncoding
|
||||
.GetEncoding()
|
||||
.GetByteCount(fullName.AsSpan(0, dirSeps[i]));
|
||||
|
||||
@@ -89,7 +89,7 @@ internal sealed partial class TarHeader
|
||||
int splitIndex = -1;
|
||||
for (int i = 0; i < dirSeps.Count; i++)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
int count = ArchiveEncoding
|
||||
.GetEncoding()
|
||||
.GetByteCount(fullName.AsSpan(0, dirSeps[i]));
|
||||
|
||||
@@ -11,10 +11,10 @@ public abstract partial class Volume : IVolume, IAsyncDisposable
|
||||
private readonly Stream _baseStream;
|
||||
private readonly Stream _actualStream;
|
||||
|
||||
internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0)
|
||||
internal Volume(Stream stream, ReaderOptions readerOptions, int index = 0)
|
||||
{
|
||||
Index = index;
|
||||
ReaderOptions = readerOptions ?? new ReaderOptions();
|
||||
ReaderOptions = readerOptions;
|
||||
_baseStream = stream;
|
||||
|
||||
// Only rewind if it's a buffered SharpCompressStream (not passthrough)
|
||||
|
||||
@@ -270,7 +270,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
}
|
||||
|
||||
using var inputMs = new MemoryStream(data.ToArray(), writable: false);
|
||||
using var outputMs = new MemoryStream();
|
||||
using var outputMs = new PooledMemoryStream();
|
||||
|
||||
encoder.Code(inputMs, outputMs, data.Length, -1, null);
|
||||
|
||||
@@ -302,7 +302,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
decoder.SetDecoderProperties(props);
|
||||
|
||||
using var input = new MemoryStream(compressedData);
|
||||
using var output = new MemoryStream();
|
||||
using var output = new PooledMemoryStream();
|
||||
decoder.Code(input, output, compressedData.Length, uncompressedSize, null);
|
||||
|
||||
return (int)input.Position;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.RangeCoder;
|
||||
using SharpCompress.Compressors.PPMd.H;
|
||||
using SharpCompress.Compressors.PPMd.I1;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.PPMd;
|
||||
|
||||
@@ -179,7 +180,7 @@ public class PpmdStream : Stream, IAsyncDisposable
|
||||
{
|
||||
if (_compress)
|
||||
{
|
||||
_model.EncodeBlock(_stream, new MemoryStream(), true);
|
||||
_model.EncodeBlock(_stream, Stream.Null, true);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
@@ -32,12 +31,12 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
.ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
|
||||
Update(_blake2sp!, new ReadOnlySpan<byte>(buffer, offset, result));
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
|
||||
EnsureHash();
|
||||
if (!disableCRCCheck && !GetCrc().SequenceEqual(readStream.CurrentCrc) && count != 0)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
@@ -56,14 +55,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, buffer.Span.Slice(0, result), result);
|
||||
Update(_blake2sp!, buffer.Span.Slice(0, result));
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
EnsureHash();
|
||||
if (
|
||||
!disableCRCCheck
|
||||
&& !(GetCrc().SequenceEqual(readStream.CurrentCrc))
|
||||
&& !GetCrc().SequenceEqual(readStream.CurrentCrc)
|
||||
&& buffer.Length != 0
|
||||
)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
@@ -13,14 +10,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
private readonly MultiVolumeReadOnlyStreamBase readStream;
|
||||
private readonly bool disableCRCCheck;
|
||||
|
||||
const uint BLAKE2S_NUM_ROUNDS = 10;
|
||||
const uint BLAKE2S_FINAL_FLAG = (~(uint)0);
|
||||
const int BLAKE2S_BLOCK_SIZE = 64;
|
||||
const int BLAKE2S_DIGEST_SIZE = 32;
|
||||
const int BLAKE2SP_PARALLEL_DEGREE = 8;
|
||||
const uint BLAKE2S_INIT_IV_SIZE = 8;
|
||||
private const int BLAKE2S_NUM_ROUNDS = 10;
|
||||
private const uint BLAKE2S_FINAL_FLAG = ~(uint)0;
|
||||
private const int BLAKE2S_BLOCK_SIZE = 64;
|
||||
private const int BLAKE2S_DIGEST_SIZE = 32;
|
||||
private const int BLAKE2SP_PARALLEL_DEGREE = 8;
|
||||
private const int BLAKE2S_INIT_IV_SIZE = 8;
|
||||
|
||||
static readonly UInt32[] k_BLAKE2S_IV =
|
||||
private static readonly uint[] k_BLAKE2S_IV =
|
||||
{
|
||||
0x6A09E667U,
|
||||
0xBB67AE85U,
|
||||
@@ -32,7 +29,7 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
0x5BE0CD19U,
|
||||
};
|
||||
|
||||
static readonly byte[][] k_BLAKE2S_Sigma =
|
||||
private static readonly byte[][] k_BLAKE2S_Sigma =
|
||||
{
|
||||
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
|
||||
new byte[] { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
|
||||
@@ -46,14 +43,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
new byte[] { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
|
||||
};
|
||||
|
||||
internal class BLAKE2S
|
||||
private sealed class BLAKE2S
|
||||
{
|
||||
internal UInt32[] h;
|
||||
internal UInt32[] t;
|
||||
internal UInt32[] f;
|
||||
internal byte[] b;
|
||||
internal readonly uint[] h;
|
||||
internal readonly uint[] t;
|
||||
internal readonly uint[] f;
|
||||
internal readonly byte[] b;
|
||||
internal int bufferPosition;
|
||||
internal UInt32 lastNodeFlag;
|
||||
internal uint lastNodeFlag;
|
||||
|
||||
public BLAKE2S()
|
||||
{
|
||||
@@ -64,9 +61,9 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
}
|
||||
};
|
||||
|
||||
internal class BLAKE2SP
|
||||
private sealed class BLAKE2SP
|
||||
{
|
||||
internal BLAKE2S[] S;
|
||||
internal readonly BLAKE2S[] S;
|
||||
internal int bufferPosition;
|
||||
|
||||
public BLAKE2SP()
|
||||
@@ -79,9 +76,8 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
}
|
||||
};
|
||||
|
||||
BLAKE2SP _blake2sp;
|
||||
|
||||
byte[] _hash = [];
|
||||
private BLAKE2SP? _blake2sp;
|
||||
private byte[]? _hash;
|
||||
|
||||
private RarBLAKE2spStream(
|
||||
IRarUnpack unpack,
|
||||
@@ -92,10 +88,9 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
{
|
||||
this.readStream = readStream;
|
||||
|
||||
// TODO: rar uses a modified hash xor'ed with encryption key?
|
||||
disableCRCCheck = fileHeader.IsEncrypted;
|
||||
_hash = fileHeader.FileCrc.NotNull();
|
||||
_blake2sp = new BLAKE2SP();
|
||||
ResetCrc();
|
||||
this._blake2sp = CreateBlake2sp();
|
||||
}
|
||||
|
||||
public static RarBLAKE2spStream Create(
|
||||
@@ -111,19 +106,15 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
|
||||
// Async methods moved to RarBLAKE2spStream.Async.cs
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
public byte[] GetCrc() =>
|
||||
this._hash
|
||||
?? throw new InvalidOperationException(
|
||||
"hash not computed, has the stream been fully drained?"
|
||||
);
|
||||
|
||||
public byte[] GetCrc() => _hash;
|
||||
|
||||
internal void ResetCrc(BLAKE2S hash)
|
||||
private static void ResetCrc(BLAKE2S hash)
|
||||
{
|
||||
for (UInt32 j = 0; j < BLAKE2S_INIT_IV_SIZE; j++)
|
||||
{
|
||||
hash.h[j] = k_BLAKE2S_IV[j];
|
||||
}
|
||||
k_BLAKE2S_IV.AsSpan().CopyTo(hash.h);
|
||||
hash.t[0] = 0;
|
||||
hash.t[1] = 0;
|
||||
hash.f[0] = 0;
|
||||
@@ -132,14 +123,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
hash.lastNodeFlag = 0;
|
||||
}
|
||||
|
||||
internal void G(
|
||||
ref UInt32[] m,
|
||||
ref byte[] sigma,
|
||||
private static void G(
|
||||
Span<uint> m,
|
||||
byte[] sigma,
|
||||
int i,
|
||||
ref UInt32 a,
|
||||
ref UInt32 b,
|
||||
ref UInt32 c,
|
||||
ref UInt32 d
|
||||
ref uint a,
|
||||
ref uint b,
|
||||
ref uint c,
|
||||
ref uint d
|
||||
)
|
||||
{
|
||||
a += b + m[sigma[2 * i]];
|
||||
@@ -157,16 +148,22 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
b = (b >> 7) | (b << 25);
|
||||
}
|
||||
|
||||
internal void Compress(BLAKE2S hash)
|
||||
private static void Compress(BLAKE2S hash)
|
||||
{
|
||||
var m = new UInt32[16];
|
||||
var v = new UInt32[16];
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
Span<uint> m = stackalloc uint[16];
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
|
||||
MemoryMarshal.Cast<byte, uint>(hash.b).CopyTo(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
|
||||
}
|
||||
}
|
||||
|
||||
Span<uint> v = stackalloc uint[16];
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
v[i] = hash.h[i];
|
||||
@@ -184,16 +181,15 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
|
||||
for (var r = 0; r < BLAKE2S_NUM_ROUNDS; r++)
|
||||
{
|
||||
ref byte[] sigma = ref k_BLAKE2S_Sigma[r];
|
||||
|
||||
G(ref m, ref sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
|
||||
G(ref m, ref sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
|
||||
G(ref m, ref sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
|
||||
G(ref m, ref sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
|
||||
G(ref m, ref sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
|
||||
G(ref m, ref sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
|
||||
G(ref m, ref sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
|
||||
G(ref m, ref sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
|
||||
var sigma = k_BLAKE2S_Sigma[r];
|
||||
G(m, sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
|
||||
G(m, sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
|
||||
G(m, sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
|
||||
G(m, sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
|
||||
G(m, sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
|
||||
G(m, sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
|
||||
G(m, sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
|
||||
G(m, sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
@@ -202,103 +198,124 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(BLAKE2S hash, ReadOnlySpan<byte> data, int size)
|
||||
private static void Update(BLAKE2S hash, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var i = 0;
|
||||
while (size != 0)
|
||||
while (data.Length != 0)
|
||||
{
|
||||
var pos = hash.bufferPosition;
|
||||
var reminder = BLAKE2S_BLOCK_SIZE - pos;
|
||||
|
||||
if (size <= reminder)
|
||||
var chunkSize = BLAKE2S_BLOCK_SIZE - pos;
|
||||
if (data.Length <= chunkSize)
|
||||
{
|
||||
data.Slice(i, size).CopyTo(new Span<byte>(hash.b, pos, size));
|
||||
hash.bufferPosition += size;
|
||||
data.CopyTo(hash.b.AsSpan(pos));
|
||||
hash.bufferPosition += data.Length;
|
||||
return;
|
||||
}
|
||||
data.Slice(i, reminder).CopyTo(new Span<byte>(hash.b, pos, reminder));
|
||||
data.Slice(0, chunkSize).CopyTo(hash.b.AsSpan(pos));
|
||||
hash.t[0] += BLAKE2S_BLOCK_SIZE;
|
||||
hash.t[1] += hash.t[0] < BLAKE2S_BLOCK_SIZE ? 1U : 0U;
|
||||
Compress(hash);
|
||||
hash.bufferPosition = 0;
|
||||
i += reminder;
|
||||
size -= reminder;
|
||||
data = data.Slice(chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
internal byte[] Final(BLAKE2S hash)
|
||||
private static void Final(BLAKE2S hash, Span<byte> output)
|
||||
{
|
||||
hash.t[0] += (uint)hash.bufferPosition;
|
||||
hash.t[1] += hash.t[0] < hash.bufferPosition ? 1U : 0U;
|
||||
hash.f[0] = BLAKE2S_FINAL_FLAG;
|
||||
hash.f[1] = hash.lastNodeFlag;
|
||||
Array.Clear(hash.b, hash.bufferPosition, BLAKE2S_BLOCK_SIZE - hash.bufferPosition);
|
||||
hash.b.AsSpan(hash.bufferPosition).Clear();
|
||||
Compress(hash);
|
||||
|
||||
var mem = new MemoryStream();
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
mem.Write(BitConverter.GetBytes(hash.h[i]), 0, 4);
|
||||
MemoryMarshal.Cast<uint, byte>(hash.h).CopyTo(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
var v = hash.h[i];
|
||||
output[i * 4] = (byte)v;
|
||||
output[i * 4 + 1] = (byte)(v >> 8);
|
||||
output[i * 4 + 2] = (byte)(v >> 16);
|
||||
output[i * 4 + 3] = (byte)(v >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
|
||||
public void ResetCrc()
|
||||
private static BLAKE2SP CreateBlake2sp()
|
||||
{
|
||||
_blake2sp.bufferPosition = 0;
|
||||
var blake2sp = new BLAKE2SP();
|
||||
|
||||
for (UInt32 i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
for (var i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
{
|
||||
_blake2sp.S[i].bufferPosition = 0;
|
||||
ResetCrc(_blake2sp.S[i]);
|
||||
_blake2sp.S[i].h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
|
||||
_blake2sp.S[i].h[2] ^= i;
|
||||
_blake2sp.S[i].h[3] ^= (BLAKE2S_DIGEST_SIZE << 24);
|
||||
var blake2S = blake2sp.S[i];
|
||||
ResetCrc(blake2S);
|
||||
|
||||
var h = blake2S.h;
|
||||
// word[0]: digest_length | (fanout<<16) | (depth<<24)
|
||||
h[0] ^= BLAKE2S_DIGEST_SIZE | (BLAKE2SP_PARALLEL_DEGREE << 16) | (2 << 24);
|
||||
// word[2]: node_offset = leaf index
|
||||
h[2] ^= (uint)i;
|
||||
// word[3]: inner_length in bits 24-31
|
||||
h[3] ^= BLAKE2S_DIGEST_SIZE << 24;
|
||||
}
|
||||
|
||||
_blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
return blake2sp;
|
||||
}
|
||||
|
||||
internal void Update(BLAKE2SP hash, ReadOnlySpan<byte> data, int size)
|
||||
private static void Update(BLAKE2SP hash, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var i = 0;
|
||||
var pos = hash.bufferPosition;
|
||||
while (size != 0)
|
||||
while (data.Length != 0)
|
||||
{
|
||||
var index = pos / BLAKE2S_BLOCK_SIZE;
|
||||
var reminder = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
|
||||
if (reminder > size)
|
||||
var chunkSize = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
|
||||
if (chunkSize > data.Length)
|
||||
{
|
||||
reminder = size;
|
||||
chunkSize = data.Length;
|
||||
}
|
||||
// Update(hash.S[index], data, size);
|
||||
Update(hash.S[index], data.Slice(i, reminder), reminder);
|
||||
size -= reminder;
|
||||
i += reminder;
|
||||
pos += reminder;
|
||||
pos &= (BLAKE2S_BLOCK_SIZE * (BLAKE2SP_PARALLEL_DEGREE - 1));
|
||||
Update(hash.S[index], data.Slice(0, chunkSize));
|
||||
data = data.Slice(chunkSize);
|
||||
pos = (pos + chunkSize) & (BLAKE2S_BLOCK_SIZE * BLAKE2SP_PARALLEL_DEGREE - 1);
|
||||
}
|
||||
hash.bufferPosition = pos;
|
||||
}
|
||||
|
||||
internal byte[] Final(BLAKE2SP hash)
|
||||
private static byte[] Final(BLAKE2SP blake2sp)
|
||||
{
|
||||
var h = new BLAKE2S();
|
||||
var blake2s = new BLAKE2S();
|
||||
ResetCrc(blake2s);
|
||||
|
||||
ResetCrc(h);
|
||||
h.h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
|
||||
h.h[3] ^= (1 << 16 | BLAKE2S_DIGEST_SIZE << 24);
|
||||
h.lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
var h = blake2s.h;
|
||||
// word[0]: digest_length | (fanout<<16) | (depth<<24) — same as leaves
|
||||
h[0] ^= BLAKE2S_DIGEST_SIZE | (BLAKE2SP_PARALLEL_DEGREE << 16) | (2 << 24);
|
||||
// word[3]: node_depth=1 (bits 16-23), inner_length=32 (bits 24-31)
|
||||
h[3] ^= (1 << 16) | (BLAKE2S_DIGEST_SIZE << 24);
|
||||
blake2s.lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
|
||||
Span<byte> digest = stackalloc byte[BLAKE2S_DIGEST_SIZE];
|
||||
for (var i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
{
|
||||
var digest = Final(_blake2sp.S[i]);
|
||||
Update(h, digest, BLAKE2S_DIGEST_SIZE);
|
||||
Final(blake2sp.S[i], digest);
|
||||
Update(blake2s, digest);
|
||||
}
|
||||
|
||||
return Final(h);
|
||||
Final(blake2s, digest);
|
||||
return digest.ToArray();
|
||||
}
|
||||
|
||||
private void EnsureHash()
|
||||
{
|
||||
if (this._hash == null)
|
||||
{
|
||||
this._hash = Final(this._blake2sp!);
|
||||
// prevent incorrect usage past hash finality by failing fast
|
||||
this._blake2sp = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
@@ -306,12 +323,12 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
var result = base.Read(buffer, offset, count);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
|
||||
Update(this._blake2sp!, new ReadOnlySpan<byte>(buffer, offset, result));
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
|
||||
EnsureHash();
|
||||
if (!disableCRCCheck && !GetCrc().SequenceEqual(readStream.CurrentCrc) && count != 0)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Squeezed;
|
||||
|
||||
@@ -54,14 +55,14 @@ public partial class SqueezeStream
|
||||
|
||||
if (bytesRead != 2)
|
||||
{
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
return new PooledMemoryStream();
|
||||
}
|
||||
|
||||
int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8);
|
||||
|
||||
if (numnodes >= NUMVALS || numnodes == 0)
|
||||
{
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
return new PooledMemoryStream();
|
||||
}
|
||||
|
||||
var dnode = new int[numnodes, 2];
|
||||
@@ -82,7 +83,7 @@ public partial class SqueezeStream
|
||||
}
|
||||
|
||||
var bitReader = new BitReader(_stream);
|
||||
var huffmanDecoded = new MemoryStream();
|
||||
var huffmanDecoded = new PooledMemoryStream();
|
||||
int i = 0;
|
||||
|
||||
while (true)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Text;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Squeezed;
|
||||
|
||||
@@ -67,7 +68,7 @@ public partial class SqueezeStream : Stream
|
||||
|
||||
if (numnodes >= NUMVALS || numnodes == 0)
|
||||
{
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
return new PooledMemoryStream();
|
||||
}
|
||||
|
||||
var dnode = new int[numnodes, 2];
|
||||
@@ -78,7 +79,7 @@ public partial class SqueezeStream : Stream
|
||||
}
|
||||
|
||||
var bitReader = new BitReader(_stream);
|
||||
var huffmanDecoded = new MemoryStream();
|
||||
var huffmanDecoded = new PooledMemoryStream();
|
||||
int i = 0;
|
||||
|
||||
while (true)
|
||||
|
||||
@@ -5,7 +5,7 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
#endif
|
||||
|
||||
@@ -554,7 +554,7 @@ public static unsafe partial class Methods
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ZSTD_copy16(void* dst, void* src)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
if (AdvSimd.IsSupported)
|
||||
{
|
||||
AdvSimd.Store((byte*)dst, AdvSimd.LoadVector128((byte*)src));
|
||||
|
||||
@@ -6,7 +6,7 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
#endif
|
||||
|
||||
@@ -1172,7 +1172,7 @@ public static unsafe partial class Methods
|
||||
{
|
||||
assert(rowEntries == 16 || rowEntries == 32 || rowEntries == 64);
|
||||
assert(rowEntries <= 64);
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
if (AdvSimd.IsSupported && BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (rowEntries == 16)
|
||||
@@ -1272,7 +1272,7 @@ public static unsafe partial class Methods
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
if (AdvSimd.IsSupported && BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (rowEntries == 16)
|
||||
|
||||
@@ -23,12 +23,12 @@ public class AceFactory : Factory, IReaderFactory
|
||||
yield return "ace";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
AceHeader.IsArchive(stream);
|
||||
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => AceHeader.IsArchiveAsync(stream, cancellationToken);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class ArcFactory : Factory, IReaderFactory
|
||||
yield return "arc";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null)
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
//You may have to use some(paranoid) checks to ensure that you actually are
|
||||
//processing an ARC file, since other archivers also adopted the idea of putting
|
||||
@@ -63,7 +63,7 @@ public class ArcFactory : Factory, IReaderFactory
|
||||
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
|
||||
@@ -23,12 +23,12 @@ public class ArjFactory : Factory, IReaderFactory
|
||||
yield return "arj";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
ArjHeader.IsArchive(stream);
|
||||
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => ArjHeader.IsArchiveAsync(stream, cancellationToken);
|
||||
|
||||
|
||||
@@ -54,11 +54,10 @@ public abstract class Factory : IFactory
|
||||
public abstract IEnumerable<string> GetSupportedExtensions();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsArchive(Stream stream, string? password = null);
|
||||
|
||||
public abstract bool IsArchive(Stream stream, ReaderOptions readerOptions);
|
||||
public abstract ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -86,7 +85,7 @@ public abstract class Factory : IFactory
|
||||
if (this is IReaderFactory readerFactory)
|
||||
{
|
||||
stream.Rewind();
|
||||
if (IsArchive(stream, options.Password))
|
||||
if (IsArchive(stream, options))
|
||||
{
|
||||
stream.Rewind(true);
|
||||
reader = readerFactory.OpenReader(stream, options);
|
||||
@@ -106,10 +105,7 @@ public abstract class Factory : IFactory
|
||||
if (this is IReaderFactory readerFactory)
|
||||
{
|
||||
stream.Rewind();
|
||||
if (
|
||||
await IsArchiveAsync(stream, options.Password, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
if (await IsArchiveAsync(stream, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
stream.Rewind(true);
|
||||
return await readerFactory
|
||||
|
||||
@@ -27,7 +27,7 @@ public class GZipFactory
|
||||
IMultiArchiveFactory,
|
||||
IReaderFactory,
|
||||
IWriterFactory,
|
||||
IWriteableArchiveFactory<GZipWriterOptions>
|
||||
IWritableArchiveFactory<GZipWriterOptions>
|
||||
{
|
||||
#region IFactory
|
||||
|
||||
@@ -44,13 +44,13 @@ public class GZipFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
GZipArchive.IsGZipFile(stream);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => GZipArchive.IsGZipFileAsync(stream, cancellationToken);
|
||||
|
||||
@@ -218,7 +218,7 @@ public class GZipFactory
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWriteableArchiveFactory
|
||||
#region IWritableArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IWritableArchive<GZipWriterOptions> CreateArchive() => GZipArchive.CreateArchive();
|
||||
|
||||
@@ -37,18 +37,18 @@ public interface IFactory
|
||||
/// Returns true if the stream represents an archive of the format defined by this type.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
|
||||
/// <param name="password">optional password</param>
|
||||
bool IsArchive(Stream stream, string? password = null);
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
bool IsArchive(Stream stream, ReaderOptions readerOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the stream represents an archive of the format defined by this type asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
|
||||
/// <param name="password">optional password</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">cancellation token</param>
|
||||
ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
|
||||
@@ -32,13 +32,13 @@ public class LzwFactory : Factory, IReaderFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
LzwStream.IsLzwStream(stream);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => LzwStream.IsLzwStreamAsync(stream, cancellationToken);
|
||||
|
||||
|
||||
@@ -31,15 +31,15 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
RarArchive.IsRarFile(stream);
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
RarArchive.IsRarFile(stream, readerOptions);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => RarArchive.IsRarFileAsync(stream, cancellationToken: cancellationToken);
|
||||
) => RarArchive.IsRarFileAsync(stream, readerOptions, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override FileInfo? GetFilePart(int index, FileInfo part1) =>
|
||||
|
||||
@@ -34,15 +34,15 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory, I
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
SevenZipArchive.IsSevenZipFile(stream);
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
SevenZipArchive.IsSevenZipFile(stream, readerOptions);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => SevenZipArchive.IsSevenZipFileAsync(stream, cancellationToken);
|
||||
) => SevenZipArchive.IsSevenZipFileAsync(stream, readerOptions, cancellationToken);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class TarFactory
|
||||
IMultiArchiveFactory,
|
||||
IReaderFactory,
|
||||
IWriterFactory,
|
||||
IWriteableArchiveFactory<TarWriterOptions>
|
||||
IWritableArchiveFactory<TarWriterOptions>
|
||||
{
|
||||
#region IFactory
|
||||
|
||||
@@ -48,8 +48,9 @@ public class TarFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null)
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var providers = readerOptions.Providers;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -76,10 +77,11 @@ public class TarFactory
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var providers = readerOptions.Providers;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -311,7 +313,7 @@ public class TarFactory
|
||||
/// <inheritdoc/>
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options)
|
||||
{
|
||||
options ??= new ReaderOptions();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -343,7 +345,7 @@ public class TarFactory
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
options ??= new ReaderOptions();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -469,7 +471,7 @@ public class TarFactory
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWriteableArchiveFactory
|
||||
#region IWritableArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IWritableArchive<TarWriterOptions> CreateArchive() => TarArchive.CreateArchive();
|
||||
|
||||
@@ -21,12 +21,12 @@ internal class ZStandardFactory : Factory
|
||||
yield return "zstd";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
ZStandardStream.IsZStandard(stream);
|
||||
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => ZStandardStream.IsZStandardAsync(stream, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ZipFactory
|
||||
IMultiArchiveFactory,
|
||||
IReaderFactory,
|
||||
IWriterFactory,
|
||||
IWriteableArchiveFactory<ZipWriterOptions>
|
||||
IWritableArchiveFactory<ZipWriterOptions>
|
||||
{
|
||||
#region IFactory
|
||||
|
||||
@@ -43,10 +43,10 @@ public class ZipFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null)
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var startPosition = stream.CanSeek ? stream.Position : -1;
|
||||
if (ZipArchive.IsZipFile(stream, password))
|
||||
if (ZipArchive.IsZipFile(stream, readerOptions.Password))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class ZipFactory
|
||||
stream.Position = startPosition;
|
||||
|
||||
//test the zip (last) file of a multipart zip
|
||||
if (ZipArchive.IsZipMulti(stream, password))
|
||||
if (ZipArchive.IsZipMulti(stream, readerOptions.Password))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class ZipFactory
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
@@ -84,7 +84,7 @@ public class ZipFactory
|
||||
// probe for single volume zip
|
||||
if (
|
||||
await ZipArchive
|
||||
.IsZipFileAsync(stream, password, cancellationToken)
|
||||
.IsZipFileAsync(stream, readerOptions.Password, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
@@ -102,7 +102,7 @@ public class ZipFactory
|
||||
//test the zip (last) file of a multipart zip
|
||||
if (
|
||||
await ZipArchive
|
||||
.IsZipMultiAsync(stream, password, cancellationToken)
|
||||
.IsZipMultiAsync(stream, readerOptions.Password, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
@@ -246,7 +246,7 @@ public class ZipFactory
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWriteableArchiveFactory
|
||||
#region IWritableArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IWritableArchive<ZipWriterOptions> CreateArchive() => ZipArchive.CreateArchive();
|
||||
|
||||
701
src/SharpCompress/IO/PooledMemoryStream.cs
Normal file
701
src/SharpCompress/IO/PooledMemoryStream.cs
Normal file
@@ -0,0 +1,701 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
/// <summary>
|
||||
/// MemoryStream implementation backed by pooled byte arrays.
|
||||
/// Uses <see cref="ArrayPool{T}"/> to reduce GC pressure for temporary buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation is not thread-safe. Use appropriate synchronization for concurrent access.
|
||||
/// Buffers exposed via <see cref="GetBuffer"/> or <see cref="TryGetBuffer"/> are allocated as
|
||||
/// fresh non-pooled arrays to avoid exposing pooled memory.
|
||||
/// </remarks>
|
||||
public sealed class PooledMemoryStream : MemoryStream
|
||||
{
|
||||
private const int MaxStreamLength = int.MaxValue;
|
||||
|
||||
private readonly ArrayPool<byte> _arrayPool;
|
||||
private readonly int _blockSize;
|
||||
|
||||
private List<byte[]>? _blocks;
|
||||
private bool _isOpen;
|
||||
private int _position;
|
||||
private int _length;
|
||||
private int _capacity;
|
||||
|
||||
public PooledMemoryStream()
|
||||
: this(0) { }
|
||||
|
||||
public PooledMemoryStream(int capacity)
|
||||
: this(capacity, Constants.BufferSize, ArrayPool<byte>.Shared) { }
|
||||
|
||||
public PooledMemoryStream(int capacity, int blockSize)
|
||||
: this(capacity, blockSize, ArrayPool<byte>.Shared) { }
|
||||
|
||||
public PooledMemoryStream(int capacity, int blockSize, ArrayPool<byte> arrayPool)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(arrayPool, nameof(arrayPool));
|
||||
ThrowHelper.ThrowIfNegative(capacity, nameof(capacity));
|
||||
ThrowHelper.ThrowIfNegativeOrZero(blockSize, nameof(blockSize));
|
||||
|
||||
_arrayPool = arrayPool;
|
||||
_blockSize = blockSize;
|
||||
|
||||
_blocks = new List<byte[]>();
|
||||
_isOpen = true;
|
||||
_position = 0;
|
||||
_length = 0;
|
||||
_capacity = capacity;
|
||||
|
||||
EnsureSegmentedAllocated(capacity);
|
||||
}
|
||||
|
||||
public override bool CanRead => _isOpen;
|
||||
|
||||
public override bool CanSeek => _isOpen;
|
||||
|
||||
public override bool CanWrite => _isOpen;
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return _length;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return _position;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureNotClosed();
|
||||
ThrowHelper.ThrowIfNegative(value, nameof(value));
|
||||
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
|
||||
|
||||
_position = (int)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return _capacity;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowHelper.ThrowIfLessThan(value, _length, nameof(value));
|
||||
|
||||
EnsureNotClosed();
|
||||
|
||||
var target = value;
|
||||
if (target == _capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetCapacityAbsolute(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
EnsureNotClosed();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin loc)
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var anchor = loc switch
|
||||
{
|
||||
SeekOrigin.Begin => 0,
|
||||
SeekOrigin.Current => _position,
|
||||
SeekOrigin.End => _length,
|
||||
_ => throw new ArgumentException("Invalid seek origin.", nameof(loc)),
|
||||
};
|
||||
|
||||
var target = anchor + offset;
|
||||
if (target < 0)
|
||||
{
|
||||
throw new IOException("Attempted to seek before the beginning of the stream.");
|
||||
}
|
||||
|
||||
if (target > MaxStreamLength)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
_position = (int)target;
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
EnsureWritable();
|
||||
|
||||
ThrowHelper.ThrowIfNegative(value, nameof(value));
|
||||
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
|
||||
var newLength = (int)value;
|
||||
if (newLength > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(newLength);
|
||||
}
|
||||
|
||||
if (newLength > _length)
|
||||
{
|
||||
ClearRange(_length, newLength - _length);
|
||||
}
|
||||
|
||||
_length = newLength;
|
||||
if (_position > newLength)
|
||||
{
|
||||
_position = newLength;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ValidateReadWriteBufferArguments(buffer, offset, count);
|
||||
EnsureNotClosed();
|
||||
|
||||
var available = _length - _position;
|
||||
if (available <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count > available)
|
||||
{
|
||||
count = available;
|
||||
}
|
||||
|
||||
CopyFromSegmented(_position, buffer, offset, count);
|
||||
|
||||
_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
if (_position >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var blockIndex = _position / _blockSize;
|
||||
var blockOffset = _position % _blockSize;
|
||||
var value = _blocks![blockIndex][blockOffset];
|
||||
|
||||
_position++;
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ValidateReadWriteBufferArguments(buffer, offset, count);
|
||||
EnsureWritable();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPosition = _position + count;
|
||||
if (endPosition < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (endPosition > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(endPosition);
|
||||
}
|
||||
|
||||
if (_position > _length)
|
||||
{
|
||||
ClearRange(_length, _position - _length);
|
||||
}
|
||||
|
||||
CopyToSegmented(_position, buffer, offset, count);
|
||||
|
||||
_position = endPosition;
|
||||
if (_position > _length)
|
||||
{
|
||||
_length = _position;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
EnsureWritable();
|
||||
|
||||
var endPosition = _position + 1;
|
||||
if (endPosition < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (endPosition > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(endPosition);
|
||||
}
|
||||
|
||||
if (_position > _length)
|
||||
{
|
||||
ClearRange(_length, _position - _length);
|
||||
}
|
||||
|
||||
var blockIndex = _position / _blockSize;
|
||||
var blockOffset = _position % _blockSize;
|
||||
_blocks![blockIndex][blockOffset] = value;
|
||||
|
||||
_position = endPosition;
|
||||
if (_position > _length)
|
||||
{
|
||||
_length = _position;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] CreateExposableBuffer()
|
||||
{
|
||||
var exposable = new byte[_capacity];
|
||||
if (_length == 0)
|
||||
{
|
||||
return exposable;
|
||||
}
|
||||
|
||||
CopyFromSegmented(0, exposable, 0, _length);
|
||||
|
||||
return exposable;
|
||||
}
|
||||
|
||||
public override byte[] GetBuffer()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return CreateExposableBuffer();
|
||||
}
|
||||
|
||||
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var exposableBuffer = CreateExposableBuffer();
|
||||
buffer = new ArraySegment<byte>(exposableBuffer, 0, _length);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override byte[] ToArray()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var count = _length;
|
||||
if (count == 0)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var copy = new byte[count];
|
||||
CopyFromSegmented(0, copy, 0, count);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public override void WriteTo(Stream stream)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(stream, nameof(stream));
|
||||
EnsureNotClosed();
|
||||
|
||||
var count = _length;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = 0;
|
||||
var remaining = count;
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = position / _blockSize;
|
||||
var blockOffset = position % _blockSize;
|
||||
var toWrite = Math.Min(remaining, _blockSize - blockOffset);
|
||||
stream.Write(_blocks![blockIndex], blockOffset, toWrite);
|
||||
position += toWrite;
|
||||
remaining -= toWrite;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<int>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(Read(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
Write(buffer, offset, count);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var available = _length - _position;
|
||||
if (available <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = Math.Min(available, buffer.Length);
|
||||
var sourcePosition = _position;
|
||||
var destinationOffset = 0;
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = sourcePosition / _blockSize;
|
||||
var blockOffset = sourcePosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
_blocks!
|
||||
[blockIndex]
|
||||
.AsSpan(blockOffset, toCopy)
|
||||
.CopyTo(buffer.Slice(destinationOffset, toCopy));
|
||||
|
||||
sourcePosition += toCopy;
|
||||
destinationOffset += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
|
||||
_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
EnsureWritable();
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPosition = _position + buffer.Length;
|
||||
if (endPosition < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (endPosition > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(endPosition);
|
||||
}
|
||||
|
||||
if (_position > _length)
|
||||
{
|
||||
ClearRange(_length, _position - _length);
|
||||
}
|
||||
|
||||
var sourceOffset = 0;
|
||||
var destinationPosition = _position;
|
||||
var remaining = buffer.Length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = destinationPosition / _blockSize;
|
||||
var blockOffset = destinationPosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
|
||||
buffer
|
||||
.Slice(sourceOffset, toCopy)
|
||||
.CopyTo(_blocks![blockIndex].AsSpan(blockOffset, toCopy));
|
||||
|
||||
sourceOffset += toCopy;
|
||||
destinationPosition += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
|
||||
_position = endPosition;
|
||||
if (_position > _length)
|
||||
{
|
||||
_length = _position;
|
||||
}
|
||||
}
|
||||
|
||||
public override ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ValueTask.FromCanceled<int>(cancellationToken);
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(Read(buffer.Span));
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(
|
||||
ReadOnlyMemory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ValueTask.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
Write(buffer.Span);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isOpen)
|
||||
{
|
||||
_isOpen = false;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ReturnPooledBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void EnsureNotClosed()
|
||||
{
|
||||
if (!_isOpen)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(PooledMemoryStream));
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureWritable()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
}
|
||||
|
||||
private void EnsureCapacityForAppend(int requiredLength)
|
||||
{
|
||||
if (requiredLength < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (requiredLength <= _capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nextCapacity = RoundUpToBlockBoundary(requiredLength);
|
||||
SetCapacityAbsolute(nextCapacity);
|
||||
}
|
||||
|
||||
private void SetCapacityAbsolute(int newCapacity)
|
||||
{
|
||||
ThrowHelper.ThrowIfLessThan(newCapacity, _length, nameof(newCapacity));
|
||||
|
||||
EnsureSegmentedAllocated(newCapacity);
|
||||
|
||||
_capacity = newCapacity;
|
||||
if (_length > _capacity)
|
||||
{
|
||||
_length = _capacity;
|
||||
}
|
||||
if (_position > _capacity)
|
||||
{
|
||||
_position = _capacity;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSegmentedAllocated(int capacity)
|
||||
{
|
||||
var requiredAllocated = RoundUpToBlockBoundary(capacity);
|
||||
var requiredBlocks = requiredAllocated == 0 ? 0 : requiredAllocated / _blockSize;
|
||||
|
||||
_blocks ??= new List<byte[]>();
|
||||
|
||||
while (_blocks.Count < requiredBlocks)
|
||||
{
|
||||
_blocks.Add(_arrayPool.Rent(_blockSize));
|
||||
}
|
||||
|
||||
while (_blocks.Count > requiredBlocks)
|
||||
{
|
||||
var index = _blocks.Count - 1;
|
||||
var block = _blocks[index];
|
||||
_blocks.RemoveAt(index);
|
||||
_arrayPool.Return(block);
|
||||
}
|
||||
}
|
||||
|
||||
private int RoundUpToBlockBoundary(int value)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var rounded = ((long)value + _blockSize - 1) / _blockSize * _blockSize;
|
||||
if (rounded > MaxStreamLength)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
return (int)rounded;
|
||||
}
|
||||
|
||||
private void ClearRange(int absoluteStart, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = absoluteStart;
|
||||
var remaining = count;
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = position / _blockSize;
|
||||
var blockOffset = position % _blockSize;
|
||||
var toClear = Math.Min(remaining, _blockSize - blockOffset);
|
||||
Array.Clear(_blocks![blockIndex], blockOffset, toClear);
|
||||
position += toClear;
|
||||
remaining -= toClear;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyFromSegmented(
|
||||
int absoluteSourcePosition,
|
||||
byte[] destination,
|
||||
int offset,
|
||||
int count
|
||||
)
|
||||
{
|
||||
var sourcePosition = absoluteSourcePosition;
|
||||
var destinationOffset = offset;
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = sourcePosition / _blockSize;
|
||||
var blockOffset = sourcePosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
Buffer.BlockCopy(
|
||||
_blocks![blockIndex],
|
||||
blockOffset,
|
||||
destination,
|
||||
destinationOffset,
|
||||
toCopy
|
||||
);
|
||||
|
||||
sourcePosition += toCopy;
|
||||
destinationOffset += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToSegmented(
|
||||
int absoluteDestinationPosition,
|
||||
byte[] source,
|
||||
int offset,
|
||||
int count
|
||||
)
|
||||
{
|
||||
var sourceOffset = offset;
|
||||
var destinationPosition = absoluteDestinationPosition;
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = destinationPosition / _blockSize;
|
||||
var blockOffset = destinationPosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
Buffer.BlockCopy(source, sourceOffset, _blocks![blockIndex], blockOffset, toCopy);
|
||||
|
||||
sourceOffset += toCopy;
|
||||
destinationPosition += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnSegmentedBlocks()
|
||||
{
|
||||
if (_blocks is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
_arrayPool.Return(_blocks[i]);
|
||||
}
|
||||
|
||||
_blocks.Clear();
|
||||
}
|
||||
|
||||
private void ReturnPooledBuffers()
|
||||
{
|
||||
ReturnSegmentedBlocks();
|
||||
_blocks = null;
|
||||
}
|
||||
|
||||
private static void ValidateReadWriteBufferArguments(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(buffer, nameof(buffer));
|
||||
ThrowHelper.ThrowIfNegative(offset, nameof(offset));
|
||||
ThrowHelper.ThrowIfNegative(count, nameof(count));
|
||||
if (buffer.Length - offset < count)
|
||||
{
|
||||
throw new ArgumentException("Offset and length are out of bounds.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,11 +203,17 @@ public partial class SharpCompressStream : Stream, IStreamStack
|
||||
// Allocate ring buffer with the requested minimum size (at least the global default).
|
||||
if (_ringBuffer is null)
|
||||
{
|
||||
var size =
|
||||
var requiredSize =
|
||||
minBufferSize.GetValueOrDefault() > Constants.RewindableBufferSize
|
||||
? minBufferSize.GetValueOrDefault()
|
||||
: Constants.RewindableBufferSize;
|
||||
_ringBuffer = new RingBuffer(size);
|
||||
_ringBuffer = new RingBuffer(requiredSize);
|
||||
}
|
||||
else if (minBufferSize.HasValue && minBufferSize.Value > _ringBuffer.Capacity)
|
||||
{
|
||||
throw new ArchiveOperationException(
|
||||
$"StartRecording requires a ring buffer of at least {minBufferSize.Value} bytes, but the stream was created with capacity {_ringBuffer.Capacity}."
|
||||
);
|
||||
}
|
||||
|
||||
// Mark current position as recording anchor
|
||||
|
||||
@@ -28,7 +28,7 @@ public static class StreamExtensions
|
||||
public async ValueTask SkipAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
await stream.CopyToAsync(Stream.Null, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
|
||||
|
||||
@@ -22,20 +22,16 @@ public abstract partial class AbstractReader<TEntry, TVolume> : IReader, IAsyncR
|
||||
private bool _wroteCurrentEntry;
|
||||
private readonly bool _disposeVolume;
|
||||
|
||||
internal AbstractReader(
|
||||
ReaderOptions options,
|
||||
ArchiveType archiveType,
|
||||
bool disposeVolume = true
|
||||
)
|
||||
internal AbstractReader(ReaderOptions options, ArchiveType type, bool disposeVolume = true)
|
||||
{
|
||||
ArchiveType = archiveType;
|
||||
Type = type;
|
||||
_disposeVolume = disposeVolume;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
internal ReaderOptions Options { get; }
|
||||
|
||||
public ArchiveType ArchiveType { get; }
|
||||
public ArchiveType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current volume that the current entry resides in
|
||||
|
||||
@@ -19,8 +19,8 @@ public partial class AceReader
|
||||
/// <returns>An AceReader instance.</returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeAceReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new SingleVolumeAceReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -31,19 +31,19 @@ public partial class AceReader
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeAceReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -61,8 +61,8 @@ public partial class AceReader
|
||||
ReaderOptions? options = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeAceReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -84,6 +84,7 @@ public partial class AceReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ internal class SingleVolumeAceReader : AceReader
|
||||
internal SingleVolumeAceReader(Stream stream, ReaderOptions options)
|
||||
: base(options)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Arc;
|
||||
public partial class ArcReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -48,6 +48,7 @@ public partial class ArcReader : IReaderOpenable
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ public partial class ArcReader : AbstractReader<ArcEntry, ArcVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ArcReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new ArcReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ArcEntry> GetEntries(Stream stream)
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Arj;
|
||||
public partial class ArjReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -48,6 +48,7 @@ public partial class ArjReader : IReaderOpenable
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ public abstract partial class ArjReader : AbstractReader<ArjEntry, ArjVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeArjReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new SingleVolumeArjReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,8 +43,8 @@ public abstract partial class ArjReader : AbstractReader<ArjEntry, ArjVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeArjReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeArjReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
protected abstract void ValidateArchive(ArjVolume archive);
|
||||
|
||||
@@ -12,7 +12,7 @@ internal class SingleVolumeArjReader : ArjReader
|
||||
internal SingleVolumeArjReader(Stream stream, ReaderOptions options)
|
||||
: base(options)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ public partial class GZipReader
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -49,12 +49,13 @@ public partial class GZipReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new GZipReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new GZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace SharpCompress.Readers;
|
||||
|
||||
public interface IAsyncReader : IAsyncDisposable
|
||||
{
|
||||
ArchiveType ArchiveType { get; }
|
||||
ArchiveType Type { get; }
|
||||
|
||||
IEntry Entry { get; }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace SharpCompress.Readers;
|
||||
|
||||
public interface IReader : IDisposable
|
||||
{
|
||||
ArchiveType ArchiveType { get; }
|
||||
ArchiveType Type { get; }
|
||||
|
||||
IEntry Entry { get; }
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@ public interface IReaderFactory : Factories.IFactory
|
||||
ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public interface IReaderOpenable
|
||||
public static abstract IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null);
|
||||
|
||||
public static abstract ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -10,14 +10,14 @@ public partial class LzwReader
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -49,12 +49,13 @@ public partial class LzwReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new LzwReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new LzwReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Rar;
|
||||
public partial class RarReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
|
||||
@@ -49,7 +49,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
readerOptions ??= new ReaderOptions { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
|
||||
public static IReader OpenReader(IEnumerable<FileInfo> fileInfos, ReaderOptions? options = null)
|
||||
{
|
||||
options ??= new ReaderOptions { LeaveStreamOpen = false };
|
||||
options ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfos.Select(x => x.OpenRead()), options);
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeRarReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new SingleVolumeRarReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,8 +84,8 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeRarReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeRarReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
protected override IEnumerable<RarReaderEntry> GetEntries(Stream stream)
|
||||
|
||||
@@ -25,7 +25,11 @@ public static partial class ReaderFactory
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken);
|
||||
return OpenAsyncReader(
|
||||
new FileInfo(filePath),
|
||||
options ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +56,7 @@ public static partial class ReaderFactory
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
|
||||
@@ -12,7 +12,7 @@ public static partial class ReaderFactory
|
||||
public static IReader OpenReader(string filePath, ReaderOptions? options = null)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenReader(new FileInfo(filePath), options);
|
||||
return OpenReader(new FileInfo(filePath), options ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null)
|
||||
@@ -29,7 +29,7 @@ public static partial class ReaderFactory
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? options = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
|
||||
@@ -24,9 +24,36 @@ namespace SharpCompress.Readers;
|
||||
public sealed record ReaderOptions : IReaderOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool LeaveStreamOpen { get; init; } = true;
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Default behavior (LeaveStreamOpen = false):</b>
|
||||
/// When you open an archive from a file path (e.g., <c>GZipArchive.OpenArchive(filePath)</c>),
|
||||
/// SharpCompress manages the stream lifetime and closes it on Dispose.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Caller-provided streams (LeaveStreamOpen = true):</b>
|
||||
/// When you pass a stream you created (FileStream, MemoryStream, NetworkStream, etc.),
|
||||
/// set LeaveStreamOpen = true to prevent SharpCompress from disposing it.
|
||||
/// Use <see cref="ForExternalStream"/> preset for convenience.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Example:</b>
|
||||
/// <code>
|
||||
/// // 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);
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool LeaveStreamOpen { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Encoding to use for archive entry names.
|
||||
@@ -124,35 +151,34 @@ public sealed record ReaderOptions : IReaderOptions
|
||||
/// <summary>
|
||||
/// Gets ReaderOptions configured for caller-provided streams.
|
||||
/// </summary>
|
||||
public static ReaderOptions ForExternalStream => new() { LeaveStreamOpen = true };
|
||||
internal static ReaderOptions Default => new();
|
||||
|
||||
public static ReaderOptions ForExternalStream => Default.WithLeaveStreamOpen(true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets ReaderOptions configured for file-based overloads that open their own stream.
|
||||
/// </summary>
|
||||
public static ReaderOptions ForFilePath => new() { LeaveStreamOpen = false };
|
||||
public static ReaderOptions ForFilePath => Default;
|
||||
|
||||
/// <summary>
|
||||
/// Creates ReaderOptions for reading encrypted archives.
|
||||
/// </summary>
|
||||
/// <param name="password">The password for encrypted archives.</param>
|
||||
public static ReaderOptions ForEncryptedArchive(string? password = null) =>
|
||||
new ReaderOptions().WithPassword(password);
|
||||
Default.WithPassword(password);
|
||||
|
||||
/// <summary>
|
||||
/// Creates ReaderOptions for archives with custom character encoding.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding for archive entry names.</param>
|
||||
public static ReaderOptions ForEncoding(IArchiveEncoding encoding) =>
|
||||
new ReaderOptions().WithArchiveEncoding(encoding);
|
||||
Default.WithArchiveEncoding(encoding);
|
||||
|
||||
/// <summary>
|
||||
/// Creates ReaderOptions for self-extracting archives that require header search.
|
||||
/// </summary>
|
||||
public static ReaderOptions ForSelfExtractingArchive(string? password = null) =>
|
||||
new ReaderOptions()
|
||||
.WithLookForHeader(true)
|
||||
.WithPassword(password)
|
||||
.WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
|
||||
Default.WithLookForHeader(true).WithPassword(password).WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
|
||||
|
||||
// Note: Parameterized constructors have been removed.
|
||||
// Use fluent With*() helpers or object initializers instead:
|
||||
|
||||
@@ -72,13 +72,13 @@ public partial class TarReader
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return OpenAsyncReader(new FileInfo(path), readerOptions, cancellationToken);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncReader(new FileInfo(filePath), readerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -89,7 +89,7 @@ public partial class TarReader
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
stream.NotNull(nameof(stream));
|
||||
readerOptions ??= new ReaderOptions();
|
||||
readerOptions ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
stream,
|
||||
bufferSize: Math.Max(
|
||||
@@ -143,7 +143,7 @@ public partial class TarReader
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
var stream = fileInfo.OpenAsyncReadStream(cancellationToken);
|
||||
return await OpenAsyncReader(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -158,7 +158,7 @@ public partial class TarReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
@@ -170,8 +170,8 @@ public partial class TarReader
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
readerOptions ??= new ReaderOptions();
|
||||
stream.RequireReadable();
|
||||
readerOptions ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
stream,
|
||||
bufferSize: Math.Max(
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Zip;
|
||||
public partial class ZipReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -48,6 +48,7 @@ public partial class ZipReader : IReaderOpenable
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ZipReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new ZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(
|
||||
@@ -57,8 +57,8 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
|
||||
IEnumerable<ZipEntry> entries
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ZipReader(stream, options ?? new ReaderOptions(), entries);
|
||||
stream.RequireReadable();
|
||||
return new ZipReader(stream, options ?? ReaderOptions.ForExternalStream, entries);
|
||||
}
|
||||
|
||||
#endregion Open
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Authors>Adam Hathcock</Authors>
|
||||
<TargetFrameworks>net48;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<TargetFrameworks>net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net10.0</TargetFrameworks>
|
||||
<AssemblyName>SharpCompress</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>../../SharpCompress.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
56
src/SharpCompress/StreamValidationExtensions.cs
Normal file
56
src/SharpCompress/StreamValidationExtensions.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress;
|
||||
|
||||
internal static class StreamValidationExtensions
|
||||
{
|
||||
internal static void RequireReadable(this Stream stream)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException("Stream must be readable", nameof(stream));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RequireSeekable(this Stream stream)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RequireWritable(this Stream stream)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (!stream.CanWrite)
|
||||
{
|
||||
throw new ArgumentException("Stream must be writable", nameof(stream));
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Stream> RequireSeekable(this IEnumerable<Stream> streams)
|
||||
{
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.RequireSeekable();
|
||||
yield return stream;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Stream> RequireReadable(this IEnumerable<Stream> streams)
|
||||
{
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
yield return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,15 @@ internal static class ThrowHelper
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowIfGreaterThan(long value, long other, string? paramName = null)
|
||||
{
|
||||
if (value > other)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowIfGreaterThan(uint value, uint other, string? paramName = null)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract partial class AbstractWriter(ArchiveType type, IWriterOptions wr
|
||||
|
||||
protected Stream? OutputStream { get; private set; }
|
||||
|
||||
public ArchiveType WriterType { get; } = type;
|
||||
public ArchiveType Type { get; } = type;
|
||||
|
||||
protected IWriterOptions WriterOptions { get; } = writerOptions;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers.GZip;
|
||||
@@ -15,28 +17,43 @@ public partial class GZipWriter : IWriterOpenable<GZipWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, GZipWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new GZipWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new GZipWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
|
||||
}
|
||||
|
||||
public static IWriter OpenWriter(Stream stream, GZipWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new GZipWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(string stream, GZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
GZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, GZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
GZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, GZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
GZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace SharpCompress.Writers;
|
||||
|
||||
public interface IWriter : IDisposable
|
||||
{
|
||||
ArchiveType WriterType { get; }
|
||||
ArchiveType Type { get; }
|
||||
void Write(string filename, Stream source, DateTime? modificationTime);
|
||||
void WriteDirectory(string directoryName, DateTime? modificationTime);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Options;
|
||||
|
||||
namespace SharpCompress.Writers;
|
||||
@@ -17,22 +18,25 @@ public interface IWriterOpenable<TWriterOptions>
|
||||
/// Opens a Writer asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write to.</param>
|
||||
/// <param name="archiveType">The archive type.</param>
|
||||
/// <param name="writerOptions">Writer options.</param>
|
||||
/// <returns>A task that returns an IWriter.</returns>
|
||||
public static abstract IAsyncWriter OpenAsyncWriter(
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that returns an async writer.</returns>
|
||||
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
TWriterOptions writerOptions
|
||||
TWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public static abstract IAsyncWriter OpenAsyncWriter(
|
||||
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
TWriterOptions writerOptions
|
||||
TWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public static abstract IAsyncWriter OpenAsyncWriter(
|
||||
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
TWriterOptions writerOptions
|
||||
TWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Writers.SevenZip;
|
||||
|
||||
@@ -20,7 +22,13 @@ public partial class SevenZipWriter : IWriterOpenable<SevenZipWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, SevenZipWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new SevenZipWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new SevenZipWriter(
|
||||
fileInfo.OpenWrite(),
|
||||
writerOptions with
|
||||
{
|
||||
LeaveStreamOpen = false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -28,35 +36,47 @@ public partial class SevenZipWriter : IWriterOpenable<SevenZipWriterOptions>
|
||||
/// </summary>
|
||||
public static IWriter OpenWriter(Stream stream, SevenZipWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new SevenZipWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new async SevenZipWriter for the specified file path.
|
||||
/// </summary>
|
||||
public static IAsyncWriter OpenAsyncWriter(string filePath, SevenZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
SevenZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(filePath, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new async SevenZipWriter for the specified stream.
|
||||
/// </summary>
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, SevenZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
SevenZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new async SevenZipWriter for the specified file.
|
||||
/// </summary>
|
||||
public static IAsyncWriter OpenAsyncWriter(
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
SevenZipWriterOptions writerOptions
|
||||
SevenZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -203,7 +203,7 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
var filesInfo = new SevenZipFilesInfoWriter { Entries = entries.ToArray() };
|
||||
|
||||
// Write header to a temporary stream first
|
||||
using var headerStream = new MemoryStream();
|
||||
using var headerStream = new PooledMemoryStream();
|
||||
ArchiveHeaderWriter.WriteRawHeader(headerStream, mainStreamsInfo, filesInfo);
|
||||
|
||||
// Optionally compress the header
|
||||
@@ -244,7 +244,7 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
};
|
||||
|
||||
// Write encoded header to a second temporary stream
|
||||
using var encodedHeaderStream = new MemoryStream();
|
||||
using var encodedHeaderStream = new PooledMemoryStream();
|
||||
ArchiveHeaderWriter.WriteEncodedHeader(encodedHeaderStream, headerStreamsInfo);
|
||||
|
||||
// Write the encoded header to the output
|
||||
@@ -252,12 +252,10 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
encodedHeaderStream.Position = 0;
|
||||
encodedHeaderStream.CopyTo(output);
|
||||
|
||||
// Compute CRC of the encoded header
|
||||
var headerCrc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
encodedHeaderStream.GetBuffer().AsSpan(0, (int)encodedHeaderStream.Length)
|
||||
);
|
||||
// Compute CRC of the encoded header without allocating a contiguous buffer
|
||||
var encodedHeaderCrcSink = new Crc32Stream(Stream.Null);
|
||||
encodedHeaderStream.WriteTo(encodedHeaderCrcSink);
|
||||
var headerCrc = encodedHeaderCrcSink.Crc;
|
||||
|
||||
// Back-patch signature header
|
||||
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
|
||||
@@ -283,12 +281,10 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
rawHeaderStream.Position = 0;
|
||||
rawHeaderStream.CopyTo(output);
|
||||
|
||||
// Compute CRC of the raw header
|
||||
var headerCrc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
rawHeaderStream.GetBuffer().AsSpan(0, (int)rawHeaderStream.Length)
|
||||
);
|
||||
// Compute CRC of the raw header without allocating a contiguous buffer
|
||||
var rawHeaderCrcSink = new Crc32Stream(Stream.Null);
|
||||
rawHeaderStream.WriteTo(rawHeaderCrcSink);
|
||||
var headerCrc = rawHeaderCrcSink.Crc;
|
||||
|
||||
// Back-patch signature header
|
||||
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers.Tar;
|
||||
@@ -15,28 +17,43 @@ public partial class TarWriter : IWriterOpenable<TarWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, TarWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new TarWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new TarWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
|
||||
}
|
||||
|
||||
public static IWriter OpenWriter(Stream stream, TarWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new TarWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(string stream, TarWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
TarWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, TarWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
TarWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, TarWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
TarWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -75,6 +75,8 @@ public static class WriterFactory
|
||||
IWriterOptions writerOptions
|
||||
)
|
||||
{
|
||||
stream.RequireWritable();
|
||||
|
||||
var factory = Factories
|
||||
.Factory.Factories.OfType<IWriterFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == archiveType);
|
||||
@@ -102,6 +104,8 @@ public static class WriterFactory
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.RequireWritable();
|
||||
|
||||
var factory = Factories
|
||||
.Factory.Factories.OfType<IWriterFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == archiveType);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Providers;
|
||||
using D = SharpCompress.Compressors.Deflate;
|
||||
|
||||
@@ -18,17 +17,10 @@ namespace SharpCompress.Writers;
|
||||
/// </remarks>
|
||||
public sealed record WriterOptions : IWriterOptions
|
||||
{
|
||||
private CompressionType _compressionType;
|
||||
private int _compressionLevel;
|
||||
|
||||
/// <summary>
|
||||
/// The compression type to use for the archive.
|
||||
/// </summary>
|
||||
public CompressionType CompressionType
|
||||
{
|
||||
get => _compressionType;
|
||||
init => _compressionType = value;
|
||||
}
|
||||
public CompressionType CompressionType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The compression level to be used when the compression type supports variable levels.
|
||||
@@ -40,11 +32,11 @@ public sealed record WriterOptions : IWriterOptions
|
||||
/// </summary>
|
||||
public int CompressionLevel
|
||||
{
|
||||
get => _compressionLevel;
|
||||
get;
|
||||
init
|
||||
{
|
||||
CompressionLevelValidation.Validate(CompressionType, value);
|
||||
_compressionLevel = value;
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers.Zip;
|
||||
@@ -15,28 +17,43 @@ public partial class ZipWriter : IWriterOpenable<ZipWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, ZipWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new ZipWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new ZipWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
|
||||
}
|
||||
|
||||
public static IWriter OpenWriter(Stream stream, ZipWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new ZipWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(string stream, ZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
ZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, ZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
ZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, ZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
ZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -313,48 +313,6 @@
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
".NETCoreApp,Version=v5.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.3, )",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.102, )",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "10.0.102",
|
||||
"Microsoft.SourceLink.Common": "10.0.102"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.14.15, )",
|
||||
"resolved": "17.14.15",
|
||||
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net6.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
@@ -397,48 +355,6 @@
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net7.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.3, )",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.102, )",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "10.0.102",
|
||||
"Microsoft.SourceLink.Common": "10.0.102"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.14.15, )",
|
||||
"resolved": "17.14.15",
|
||||
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
@@ -486,48 +402,6 @@
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net9.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.3, )",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.102, )",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "10.0.102",
|
||||
"Microsoft.SourceLink.Common": "10.0.102"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.14.15, )",
|
||||
"resolved": "17.14.15",
|
||||
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class TarBenchmarks : ArchiveBenchmarkBase
|
||||
using var stream = new MemoryStream(_tarBytes);
|
||||
using var archive = TarArchive.OpenArchive(
|
||||
stream,
|
||||
new ReaderOptions().WithProviders(
|
||||
ReaderOptions.ForExternalStream.WithProviders(
|
||||
CompressionProviderRegistry.Empty.With(new SystemGZipCompressionProvider())
|
||||
)
|
||||
);
|
||||
@@ -87,7 +87,7 @@ public class TarBenchmarks : ArchiveBenchmarkBase
|
||||
using var stream = new MemoryStream(_tarBytes);
|
||||
using var reader = ReaderFactory.OpenReader(
|
||||
stream,
|
||||
new ReaderOptions().WithProviders(
|
||||
ReaderOptions.ForExternalStream.WithProviders(
|
||||
CompressionProviderRegistry.Empty.With(new SystemGZipCompressionProvider())
|
||||
)
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ZipBenchmarks : ArchiveBenchmarkBase
|
||||
using var stream = new MemoryStream(_archiveBytes);
|
||||
using var archive = ZipArchive.OpenArchive(
|
||||
stream,
|
||||
new ReaderOptions().WithProviders(
|
||||
ReaderOptions.ForExternalStream.WithProviders(
|
||||
CompressionProviderRegistry.Empty.With(new SystemDeflateCompressionProvider())
|
||||
)
|
||||
);
|
||||
@@ -73,7 +73,7 @@ public class ZipBenchmarks : ArchiveBenchmarkBase
|
||||
using var stream = new MemoryStream(_archiveBytes);
|
||||
using var reader = ReaderFactory.OpenReader(
|
||||
stream,
|
||||
new ReaderOptions().WithProviders(
|
||||
ReaderOptions.ForExternalStream.WithProviders(
|
||||
CompressionProviderRegistry.Empty.With(new SystemDeflateCompressionProvider())
|
||||
)
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user