Compare commits

..

80 Commits

Author SHA1 Message Date
Adam Hathcock
158460bc77 fix benchmarks 2026-02-12 13:22:13 +00:00
Adam Hathcock
b5bd4cbf53 fmt 2026-02-12 12:15:00 +00:00
Adam Hathcock
2dfe535e0b add async read and write for files 2026-02-12 12:10:43 +00:00
Adam Hathcock
92c04a9ba4 don't leave created streams open 2026-02-12 11:59:11 +00:00
Adam Hathcock
5f3031db4a Open for writing should be async 2026-02-12 11:53:19 +00:00
Adam Hathcock
c81e78b5bb fix async benchmarks 2026-02-12 11:07:11 +00:00
Adam Hathcock
fb707aa676 push to nuget only on tags 2026-02-12 10:56:26 +00:00
Adam Hathcock
147dbc878a Merge pull request #1210 from adamhathcock/adam/issue-1206
OpenAsyncReader, OpenAsyncArchive and others must be async for Tar detection
2026-02-12 10:43:10 +00:00
Adam Hathcock
06bd6d9bed Merge pull request #1202 from adamhathcock/adam/async-benchmarks
update benchmarks to include async paths
2026-02-12 10:37:21 +00:00
Adam Hathcock
7f6272807d update docs 2026-02-12 10:32:20 +00:00
Adam Hathcock
89d948b4e1 use configure await false 2026-02-12 10:29:15 +00:00
Adam Hathcock
51c42b89b4 OpenAsyncArchive has to be async 2026-02-12 10:26:18 +00:00
Adam Hathcock
5a319ffe2c create/open always has to be async for detection 2026-02-12 10:18:43 +00:00
Adam Hathcock
bae660381c TarArchive should use a compression method like TarReader 2026-02-12 09:48:06 +00:00
Adam Hathcock
b2f1d007c6 Clean up some code paths 2026-02-12 08:50:18 +00:00
Adam Hathcock
33e9c78626 Merge pull request #1203 from adamhathcock/adam/issue-1201 2026-02-11 17:41:08 +00:00
Adam Hathcock
6f50545c31 more cleaning 2026-02-11 16:48:37 +00:00
Adam Hathcock
ab1dd45e9c more moved and validated 2026-02-11 16:47:20 +00:00
Adam Hathcock
cd5da3da5d moved and validated more async code 2026-02-11 16:35:41 +00:00
Adam Hathcock
218af5a8b3 validate and make sure rar5 methods are the same 2026-02-11 16:27:53 +00:00
Adam Hathcock
e786c00767 divide async and sync logic 2026-02-11 16:20:51 +00:00
Adam Hathcock
103ae60631 codex found problems 2026-02-11 16:10:55 +00:00
Adam Hathcock
98d0f1913e make sure things compile adam 2026-02-11 14:19:21 +00:00
Adam Hathcock
8aa93f4e34 fix fmt 2026-02-11 14:02:27 +00:00
Adam Hathcock
3689b893db Update tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
2026-02-11 14:00:37 +00:00
Adam Hathcock
fbec7dc083 generate github actions baseline 2026-02-11 13:57:06 +00:00
Adam Hathcock
7cf7623438 update benchmarks to include async paths 2026-02-11 13:36:26 +00:00
Adam Hathcock
8a54f253d5 Merge pull request #1200 from adamhathcock/adam/fix-async-7z-seeking 2026-02-11 12:35:18 +00:00
Adam Hathcock
d0baa16502 Fix 7z seeking to be contigous in async too 2026-02-11 12:16:19 +00:00
Adam Hathcock
4178c382e1 update README 2026-02-11 09:13:41 +00:00
Adam Hathcock
775a9bf144 update agents md 2026-02-11 09:11:57 +00:00
Adam Hathcock
57cd2f12d0 Merge pull request #1195 from adamhathcock/adam/add-configure-await
add configure await
2026-02-10 15:04:22 +00:00
Adam Hathcock
b2066fc022 some review suggestions 2026-02-10 14:41:26 +00:00
Adam Hathcock
4639748461 Merge remote-tracking branch 'origin/master' into adam/add-configure-await
# Conflicts:
#	src/SharpCompress/Archives/ArchiveFactory.Async.cs
2026-02-10 13:55:34 +00:00
Adam Hathcock
42e118db68 Merge pull request #1189 from adamhathcock/copilot/add-lzwreader-support
Add LzwReader support for .Z compressed archives
2026-02-10 13:29:15 +00:00
Adam Hathcock
8b375b9179 remove some dangling completed tasks 2026-02-10 13:28:48 +00:00
Adam Hathcock
e4ad307413 maybe done 2026-02-10 13:10:42 +00:00
Adam Hathcock
e7609329d7 finished? 2026-02-10 12:58:53 +00:00
Adam Hathcock
64a8a9c1dc even more 2026-02-10 12:51:41 +00:00
Adam Hathcock
51aa6d782a more 2026-02-10 12:46:42 +00:00
Adam Hathcock
e040e5e449 more 2026-02-10 12:40:30 +00:00
Adam Hathcock
3aca691ea0 first round of configure await false 2026-02-10 12:01:16 +00:00
Adam Hathcock
a0a7da9254 merge fixes 2026-02-10 11:36:35 +00:00
Adam Hathcock
960920993a Merge remote-tracking branch 'origin/master' into copilot/add-lzwreader-support
# Conflicts:
#	src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs
#	src/SharpCompress/Archives/Tar/TarArchive.Factory.cs
#	src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs
#	src/SharpCompress/Factories/GZipFactory.cs
#	src/SharpCompress/Factories/TarFactory.cs
#	src/SharpCompress/Factories/ZipFactory.cs
#	src/SharpCompress/Writers/IWriterFactory.cs
#	src/SharpCompress/Writers/WriterFactory.cs
2026-02-10 11:23:18 +00:00
Adam Hathcock
e1df6557d1 Merge pull request #1193 from adamhathcock/adam/cleanup-options
Change and clean up options
2026-02-10 11:21:51 +00:00
Adam Hathcock
34f4314b86 Fix test 2026-02-10 11:16:39 +00:00
Adam Hathcock
bd99c1ab27 more fluent interface for options 2026-02-10 11:10:04 +00:00
Adam Hathcock
2e364ac0eb cleaned up writing and added more validation and tests 2026-02-10 10:48:42 +00:00
Adam Hathcock
0dcfbf56c6 fix more usage of writers 2026-02-10 09:35:39 +00:00
Adam Hathcock
399572d9d6 use specific writer options for writing archives 2026-02-10 09:03:10 +00:00
Adam Hathcock
e3c3b50ac1 Use nullability for time in arj 2026-02-10 08:26:25 +00:00
Adam Hathcock
e21631526b mismerge 2026-02-09 17:36:22 +00:00
Adam Hathcock
cf0ad9b323 Merge remote-tracking branch 'origin/copilot/fix-rar-extraction-issues' into adam/cleanup-options 2026-02-09 17:30:50 +00:00
Adam Hathcock
938692ef33 refactor how options for reading was done 2026-02-09 17:30:11 +00:00
Adam Hathcock
84c49f152e more removal 2026-02-09 17:05:23 +00:00
Adam Hathcock
04dd177f19 first pass of removing extraction options (folded into reader options) 2026-02-09 16:52:54 +00:00
Adam Hathcock
2e074e18d4 Merge remote-tracking branch 'origin/master' into adam/cleanup-options 2026-02-09 16:22:34 +00:00
Adam Hathcock
4f04122eb8 Merge remote-tracking branch 'origin/copilot/add-lzwreader-support' into copilot/add-lzwreader-support 2026-02-09 16:17:20 +00:00
Adam Hathcock
4475c2af73 add back substring usage 2026-02-09 16:16:32 +00:00
Adam Hathcock
ed52ed1e73 Merge remote-tracking branch 'origin/copilot/add-lzwreader-support' into copilot/add-lzwreader-support 2026-02-09 15:05:10 +00:00
copilot-swe-agent[bot]
b67b4fd57f Add LzwReader support for .Z compressed archives
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-09 15:01:25 +00:00
copilot-swe-agent[bot]
1ba438d4c7 Add tests for plain .Z files (not tar-wrapped)
- Create test .Z file (large_test.txt.Z) using compress tool
- Add tests for direct LzwReader usage with plain .Z files
- Add tests for ReaderFactory detection of plain .Z files
- Improve filename derivation to unwrap SharpCompressStream
- Verify decompression works correctly for non-tar .Z files
- All 15 tests now passing

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-09 14:56:54 +00:00
Adam Hathcock
77c5fbd739 Merge remote-tracking branch 'origin/master' into copilot/add-lzwreader-support 2026-02-09 14:45:12 +00:00
Adam Hathcock
756cb7bd9d Merge pull request #1192 from adamhathcock/dependabot/nuget/dot-config/csharpier-1.2.6
Bump csharpier from 1.2.5 to 1.2.6
2026-02-09 13:14:17 +00:00
copilot-swe-agent[bot]
a8c06386a3 Address code review feedback: use range syntax and dispose testStream
- Use C# range syntax [..^2] instead of Substring for better readability
- Wrap testStream in using statement for proper resource cleanup

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-09 10:56:11 +00:00
copilot-swe-agent[bot]
b3038010d9 Improve test documentation for tar.Z wrapper detection
- Rename test to clarify it tests tar wrapper detection
- Add detailed comment explaining why we use Tar.tar.Z (LzwStream compression not supported)
- Add assertions to verify ArchiveType.Tar and CompressionType.Lzw are correctly detected

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-09 10:52:13 +00:00
copilot-swe-agent[bot]
ed6c774f08 Address PR review feedback: async IsArchive, tar detection, filename derivation, and code style
- Use LzwStream.IsLzwStreamAsync for async archive detection
- Add TryOpenReader override to detect tar.Z files and return TarReader
- Derive filename from FileStream (strip .Z extension) or use "data" as fallback
- Simplify if-else statements to use ternary operators

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-09 10:50:48 +00:00
dependabot[bot]
c37209618d Bump csharpier from 1.2.5 to 1.2.6
---
updated-dependencies:
- dependency-name: csharpier
  dependency-version: 1.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 10:48:15 +00:00
Adam Hathcock
0048452efa Remove cancellation tokens for factory methods that aren't async 2026-02-09 09:59:49 +00:00
Adam Hathcock
c4a28e7cfb Update tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-09 09:11:19 +00:00
Adam Hathcock
29197f2142 Update tests/SharpCompress.Test/Rar/RarArchiveTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-09 09:10:59 +00:00
copilot-swe-agent[bot]
2a4081362e Complete fix for RAR extraction subdirectory issue
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-08 12:56:44 +00:00
copilot-swe-agent[bot]
d5cab8172b Address code review feedback: clarify file size comment
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-08 12:54:11 +00:00
copilot-swe-agent[bot]
4084b347d4 Fix RAR extraction to preserve subdirectory structure
- Set default ExtractFullPath=true in WriteToDirectoryInternal methods
- Add test case with sample RAR archive containing subdirectories
- Tests verify files are extracted to correct subdirectories, not root

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-08 12:52:25 +00:00
copilot-swe-agent[bot]
5437d9ff8c Initial plan 2026-02-08 12:38:49 +00:00
copilot-swe-agent[bot]
7b746c49cf Fix code review issues: LzwVolume multi-volume flag and extension case
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-07 10:30:56 +00:00
copilot-swe-agent[bot]
1da178a4be Run code formatter on LzwReader implementation
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-07 10:30:00 +00:00
copilot-swe-agent[bot]
2b74807f5e Implement LzwReader support for .Z archives
- Add Lzw to ArchiveType enum
- Create Common/Lzw classes (LzwEntry, LzwVolume, LzwFilePart)
- Create Readers/Lzw/LzwReader with factory methods
- Create LzwFactory for integration with ReaderFactory
- Add comprehensive tests in Lzw test directory
- Update ReaderFactory error message to include Lzw format

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
2026-02-07 10:29:18 +00:00
copilot-swe-agent[bot]
38d295b089 Initial plan 2026-02-07 10:23:41 +00:00
Adam Hathcock
cc6e410be8 some options 2026-02-06 15:16:45 +00:00
279 changed files with 7243 additions and 4507 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.2.5",
"version": "1.2.6",
"commands": [
"csharpier"
],

View File

@@ -53,9 +53,9 @@ jobs:
name: ${{ matrix.os }}-nuget-package
path: artifacts/*.nupkg
# Push to NuGet.org using C# build target (Windows only, not on PRs)
# Push to NuGet.org only for version tag pushes (Windows only)
- name: Push to NuGet
if: success() && matrix.os == 'windows-latest' && github.event_name != 'pull_request'
if: success() && matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/')
run: dotnet run --project build/build.csproj -- push-to-nuget
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}

112
AGENTS.md
View File

@@ -6,21 +6,25 @@ applyTo: '**/*.cs'
# SharpCompress Development
## About SharpCompress
SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard) for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0, and .NET 8.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios.
SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard). The project currently targets .NET Framework 4.8, .NET Standard 2.0, .NET 8.0, and .NET 10.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios.
## C# Instructions
- Always use the latest version C#, currently C# 13 features.
- Write clear and concise comments for each function.
- Use language features supported by the current project toolchain (`LangVersion=latest`) and existing codebase patterns.
- Add comments for non-obvious logic and important design decisions; avoid redundant comments.
- Follow the existing code style and patterns in the codebase.
## General Instructions
- **Agents should NEVER commit to git** - Agents should stage files and leave committing to the user. Only create commits when the user explicitly requests them.
- **Do not commit or stage changes unless the user explicitly asks for it.**
- Make only high confidence suggestions when reviewing code changes.
- Write code with good maintainability practices, including comments on why certain design decisions were made.
- Handle edge cases and write clear exception handling.
- For libraries or external dependencies, mention their usage and purpose in comments.
- Preserve backward compatibility when making changes to public APIs.
### Workspace Hygiene
- Do not edit generated or machine-local files unless required for the task (for example: `bin/`, `obj/`, `*.csproj.user`).
- Avoid broad formatting-only diffs in unrelated files.
## Naming Conventions
- Follow PascalCase for component names, method names, and public members.
@@ -64,7 +68,7 @@ SharpCompress is a pure C# compression library supporting multiple archive forma
## Project Setup and Structure
- The project targets multiple frameworks: .NET Framework 4.62, .NET Standard 2.1, .NET 6.0, and .NET 8.0
- The project targets multiple frameworks: .NET Framework 4.8, .NET Standard 2.0, .NET 8.0, and .NET 10.0
- Main library is in `src/SharpCompress/`
- Tests are in `tests/SharpCompress.Test/`
- Performance tests are in `tests/SharpCompress.Performance/`
@@ -89,13 +93,21 @@ src/SharpCompress/
tests/SharpCompress.Test/
├── Zip/, Tar/, Rar/, SevenZip/, GZip/, BZip2/ # Format-specific tests
├── TestBase.cs # Base test class with helper methods
└── TestArchives/ # Test data (not checked into main test project)
tests/
├── SharpCompress.Test/ # Unit/integration tests
├── SharpCompress.Performance/ # Benchmark tests
└── TestArchives/ # Test data archives
```
### Factory Pattern
All format types implement factory interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) for auto-detection:
- `ReaderFactory.Open()` - Auto-detects format by probing stream
- `WriterFactory.Open()` - Creates writer for specified `ArchiveType`
Factory implementations can implement one or more interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) depending on format capabilities:
- `ArchiveFactory.OpenArchive()` - Opens archive API objects from seekable streams/files
- `ArchiveFactory.OpenAsyncArchive()` - Opens async archive API objects for async archive use cases
- `ReaderFactory.OpenReader()` - Auto-detects and opens forward-only readers
- `ReaderFactory.OpenAsyncReader()` - Auto-detects and opens forward-only async readers
- `WriterFactory.OpenWriter()` - Creates a writer for a specified `ArchiveType`
- `WriterFactory.OpenAsyncWriter()` - Creates an async writer for async write scenarios
- Factories located in: `src/SharpCompress/Factories/`
## Nullable Reference Types
@@ -123,6 +135,9 @@ SharpCompress supports multiple archive and compression formats:
### Async/Await Patterns
- All I/O operations support async/await with `CancellationToken`
- Async methods follow the naming convention: `MethodNameAsync`
- For async archive scenarios, prefer `ArchiveFactory.OpenAsyncArchive(...)` over sync `OpenArchive(...)`.
- For async forward-only read scenarios, prefer `ReaderFactory.OpenAsyncReader(...)` over sync `OpenReader(...)`.
- For async write scenarios, prefer `WriterFactory.OpenAsyncWriter(...)` over sync `OpenWriter(...)`.
- Key async methods:
- `WriteEntryToAsync` - Extract entry asynchronously
- `WriteAllToDirectoryAsync` - Extract all entries asynchronously
@@ -166,71 +181,32 @@ SharpCompress supports multiple archive and compression formats:
- Test stream disposal and `LeaveStreamOpen` behavior.
- Test edge cases: empty archives, large files, corrupted archives, encrypted archives.
### Validation Expectations
- Run targeted tests for the changed area first.
- Run `dotnet csharpier format .` after code edits.
- Run `dotnet csharpier check .` before handing off changes.
### Test Organization
- Base class: `TestBase` - Provides `TEST_ARCHIVES_PATH`, `SCRATCH_FILES_PATH`, temp directory management
- Framework: xUnit with AwesomeAssertions
- Test archives: `tests/TestArchives/` - Use existing archives, don't create new ones unnecessarily
- Match naming style of nearby test files
### Public API Change Checklist
- Preserve existing public method signatures and behavior when possible.
- If a breaking change is unavoidable, document it and provide a migration path.
- Add or update tests that cover backward compatibility expectations.
### Stream Ownership and Position Checklist
- Verify `LeaveStreamOpen` behavior for externally owned streams.
- Validate behavior for both seekable and non-seekable streams.
- Ensure stream position assumptions are explicit and tested.
## Common Pitfalls
1. **Don't mix Archive and Reader APIs** - Archive needs seekable stream, Reader doesn't
2. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction
3. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close)
4. **Tar + non-seekable stream** - Must provide file size or it will throw
6. **Format detection** - Use `ReaderFactory.Open()` for auto-detection, test with actual archive files
### Async Struct-Copy Bug in LZMA RangeCoder
When implementing async methods on mutable `struct` types (like `BitEncoder` and `BitDecoder` in the LZMA RangeCoder), be aware that the async state machine copies the struct when `await` is encountered. This means mutations to struct fields after the `await` point may not persist back to the original struct stored in arrays or fields.
**The Bug:**
```csharp
// BAD: async method on mutable struct
public async ValueTask<uint> DecodeAsync(Decoder decoder, CancellationToken cancellationToken = default)
{
var newBound = (decoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob;
if (decoder._code < newBound)
{
decoder._range = newBound;
_prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; // Mutates _prob
await decoder.Normalize2Async(cancellationToken).ConfigureAwait(false); // Struct gets copied here
return 0; // Original _prob update may be lost
}
// ...
}
```
**The Fix:**
Refactor async methods on mutable structs to perform all struct mutations synchronously before any `await`, or use a helper method to separate the await from the struct mutation:
```csharp
// GOOD: struct mutations happen synchronously, await is conditional
public ValueTask<uint> DecodeAsync(Decoder decoder, CancellationToken cancellationToken = default)
{
var newBound = (decoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob;
if (decoder._code < newBound)
{
decoder._range = newBound;
_prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS; // All mutations complete
return DecodeAsyncHelper(decoder.Normalize2Async(cancellationToken), 0); // Await in helper
}
decoder._range -= newBound;
decoder._code -= newBound;
_prob -= (_prob) >> K_NUM_MOVE_BITS; // All mutations complete
return DecodeAsyncHelper(decoder.Normalize2Async(cancellationToken), 1); // Await in helper
}
private static async ValueTask<uint> DecodeAsyncHelper(ValueTask normalizeTask, uint result)
{
await normalizeTask.ConfigureAwait(false);
return result;
}
```
**Why This Matters:**
In LZMA, the `BitEncoder` and `BitDecoder` structs maintain adaptive probability models in their `_prob` field. When these structs are stored in arrays (e.g., `_models[m]`), the async state machine copy breaks the adaptive model, causing incorrect bit decoding and eventually `DataErrorException` exceptions.
**Related Files:**
- `src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBit.Async.cs` - Fixed
- `src/SharpCompress/Compressors/LZMA/RangeCoder/RangeCoderBitTree.Async.cs` - Uses readonly structs, so this pattern doesn't apply
2. **Don't mix sync and async open paths** - For async workflows use `OpenAsyncArchive`/`OpenAsyncReader`/`OpenAsyncWriter`, not `OpenArchive`/`OpenReader`/`OpenWriter`
3. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction
4. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close)
5. **Tar + non-seekable stream** - Must provide file size or it will throw
6. **Format detection** - Use `ReaderFactory.OpenReader()` / `ReaderFactory.OpenAsyncReader()` for auto-detection, test with actual archive files

View File

@@ -26,7 +26,7 @@ RAR is not recommended as it's a proprietary format and the compression is close
7Zip and XZ both are overly complicated. 7Zip does not support streamable formats. XZ has known holes explained here: (http://www.nongnu.org/lzip/xz_inadequate.html) Use Tar/LZip for LZMA compression instead.
ZStandard is an efficient format that works well for streaming with a flexible compression level to tweak the speed/performance trade off you are looking for. We currently only implement decompression for ZStandard but as we leverage the [ZstdSharp](https://github.com/oleg-st/ZstdSharp) library one could likely add compression support without much trouble (PRs are welcome!).
ZStandard is an efficient format that works well for streaming with a flexible compression level to tweak the speed/performance trade off you are looking for.
## A Simple Request
@@ -46,6 +46,8 @@ XZ BCJ filters support contributed by Louis-Michel Bergeron, on behalf of aDolus
7Zip implementation based on: https://code.google.com/p/managed-lzma/
Zstandard implementation from: https://github.com/oleg-st/ZstdSharp
LICENSE
Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)

View File

@@ -20,14 +20,18 @@ using (var archive = RarArchive.OpenArchive("file.rar"))
using (var archive = SevenZipArchive.OpenArchive("file.7z"))
using (var archive = GZipArchive.OpenArchive("file.gz"))
// With options
var options = new ReaderOptions
// With fluent options (preferred)
var options = ReaderOptions.ForEncryptedArchive("password")
.WithArchiveEncoding(new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var archive = ZipArchive.OpenArchive("encrypted.zip", options))
// Alternative: object initializer
var options2 = new ReaderOptions
{
Password = "password",
LeaveStreamOpen = true,
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
using (var archive = ZipArchive.OpenArchive("encrypted.zip", options))
```
### Creating Archives
@@ -44,16 +48,21 @@ using (var archive = ZipArchive.CreateArchive())
using (var archive = TarArchive.CreateArchive())
using (var archive = GZipArchive.CreateArchive())
// With options
var options = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9,
LeaveStreamOpen = false
};
// With fluent options (preferred)
var options = WriterOptions.ForZip()
.WithCompressionLevel(9)
.WithLeaveStreamOpen(false);
using (var archive = ZipArchive.CreateArchive())
{
archive.SaveTo("output.zip", options);
}
// Alternative: constructor with object initializer
var options2 = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9,
LeaveStreamOpen = false
};
```
---
@@ -72,16 +81,11 @@ using (var archive = ZipArchive.OpenArchive("file.zip"))
var entry = archive.Entries.FirstOrDefault(e => e.Key == "file.txt");
// Extract all
archive.WriteToDirectory(@"C:\output", new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
archive.WriteToDirectory(@"C:\output");
// Extract single entry
var entry = archive.Entries.First();
entry.WriteToFile(@"C:\output\file.txt");
entry.WriteToFile(@"C:\output\file.txt", new ExtractionOptions { Overwrite = true });
// Get entry stream
using (var stream = entry.OpenEntryStream())
@@ -91,11 +95,10 @@ using (var archive = ZipArchive.OpenArchive("file.zip"))
}
// Async extraction (requires IAsyncArchive)
using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip"))
await using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip"))
{
await asyncArchive.WriteToDirectoryAsync(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true },
cancellationToken: cancellationToken
);
}
@@ -174,7 +177,7 @@ using (var reader = ReaderFactory.OpenReader(stream))
// Async variants (use OpenAsyncReader to get IAsyncReader)
using (var stream = File.OpenRead("file.zip"))
using (var reader = await ReaderFactory.OpenAsyncReader(stream))
await using (var reader = await ReaderFactory.OpenAsyncReader(stream))
{
while (await reader.MoveToNextEntryAsync())
{
@@ -187,7 +190,6 @@ using (var reader = await ReaderFactory.OpenAsyncReader(stream))
// Async extraction of all entries
await reader.WriteAllToDirectoryAsync(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true },
cancellationToken
);
}
@@ -229,43 +231,91 @@ using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, Compressio
### ReaderOptions
Use factory presets and fluent helpers for common configurations:
```csharp
var options = new ReaderOptions
{
Password = "password", // For encrypted archives
LeaveStreamOpen = true, // Don't close wrapped stream
ArchiveEncoding = new ArchiveEncoding // Custom character encoding
{
Default = Encoding.GetEncoding(932)
}
};
// External stream with password and custom encoding
var options = ReaderOptions.ForExternalStream()
.WithPassword("password")
.WithArchiveEncoding(new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var archive = ZipArchive.OpenArchive("file.zip", options))
{
// ...
}
// Common presets
var safeOptions = ReaderOptions.SafeExtract; // No overwrite
var flatOptions = ReaderOptions.FlatExtract; // No directory structure
// Factory defaults:
// - file path / FileInfo overloads use LeaveStreamOpen = false
// - stream overloads use LeaveStreamOpen = true
```
Alternative: traditional object initializer:
```csharp
var options = new ReaderOptions
{
Password = "password",
LeaveStreamOpen = true,
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
```
### WriterOptions
Factory methods provide a clean, discoverable way to create writer options:
```csharp
// Factory methods for common archive types
var zipOptions = WriterOptions.ForZip() // ZIP with Deflate
.WithCompressionLevel(9) // 0-9 for Deflate
.WithLeaveStreamOpen(false); // Close stream when done
var tarOptions = WriterOptions.ForTar(CompressionType.GZip) // TAR with GZip
.WithLeaveStreamOpen(false);
var gzipOptions = WriterOptions.ForGZip() // GZip file
.WithCompressionLevel(6);
archive.SaveTo("output.zip", zipOptions);
```
Alternative: traditional constructor with object initializer:
```csharp
var options = new WriterOptions(CompressionType.Deflate)
{
CompressionLevel = 9, // 0-9 for Deflate
LeaveStreamOpen = true, // Don't close stream
CompressionLevel = 9,
LeaveStreamOpen = true,
};
archive.SaveTo("output.zip", options);
```
### ExtractionOptions
### Extraction behavior
```csharp
var options = new ExtractionOptions
var options = new ReaderOptions
{
ExtractFullPath = true, // Recreate directory structure
Overwrite = true, // Overwrite existing files
PreserveFileTime = true // Keep original timestamps
};
archive.WriteToDirectory(@"C:\output", options);
using (var archive = ZipArchive.OpenArchive("file.zip", options))
{
archive.WriteToDirectory(@"C:\output");
}
```
### Options matrix
```text
ReaderOptions: open-time behavior (password, encoding, stream ownership, extraction defaults)
WriterOptions: write-time behavior (compression type/level, encoding, stream ownership)
ZipWriterEntryOptions: per-entry ZIP overrides (compression, level, timestamps, comments, zip64)
```
---
@@ -317,13 +367,9 @@ ArchiveType.ZStandard
try
{
using (var archive = ZipArchive.Open("archive.zip",
new ReaderOptions { Password = "password" }))
ReaderOptions.ForEncryptedArchive("password")))
{
archive.WriteToDirectory(@"C:\output", new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
archive.WriteToDirectory(@"C:\output");
}
}
catch (PasswordRequiredException)
@@ -348,7 +394,7 @@ var progress = new Progress<ProgressReport>(report =>
Console.WriteLine($"Extracting {report.EntryPath}: {report.PercentComplete}%");
});
var options = new ReaderOptions { Progress = progress };
var options = ReaderOptions.ForOwnedFile().WithProgress(progress);
using (var archive = ZipArchive.OpenArchive("archive.zip", options))
{
archive.WriteToDirectory(@"C:\output");
@@ -363,11 +409,10 @@ cts.CancelAfter(TimeSpan.FromMinutes(5));
try
{
using (var archive = await ZipArchive.OpenAsyncArchive("archive.zip"))
await using (var archive = await ZipArchive.OpenAsyncArchive("archive.zip"))
{
await archive.WriteToDirectoryAsync(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true },
cancellationToken: cts.Token
);
}

View File

@@ -90,7 +90,7 @@ Common types, options, and enumerations used across formats.
- `ArchiveType.cs` - Enum for archive formats
- `CompressionType.cs` - Enum for compression methods
- `ArchiveEncoding.cs` - Character encoding configuration
- `ExtractionOptions.cs` - Extraction configuration
- `IExtractionOptions.cs` - Extraction configuration exposed through `ReaderOptions`
- Format-specific headers: `Zip/Headers/`, `Tar/Headers/`, `Rar/Headers/`, etc.
#### `Compressors/` - Compression Algorithms
@@ -215,13 +215,13 @@ using (var compressor = new DeflateStream(nonDisposingStream))
public abstract class AbstractArchive : IArchive
{
// Template methods
public virtual void WriteToDirectory(string destinationDirectory, ExtractionOptions options)
public virtual void WriteToDirectory(string destinationDirectory)
{
// Common extraction logic
foreach (var entry in Entries)
{
// Call subclass method
entry.WriteToFile(destinationPath, options);
entry.WriteToFile(destinationPath);
}
}
@@ -267,8 +267,7 @@ public interface IArchive : IDisposable
{
IEnumerable<IEntry> Entries { get; }
void WriteToDirectory(string destinationDirectory,
ExtractionOptions options = null);
void WriteToDirectory(string destinationDirectory);
IEntry FirstOrDefault(Func<IEntry, bool> predicate);
@@ -287,8 +286,7 @@ public interface IReader : IDisposable
bool MoveToNextEntry();
void WriteEntryToDirectory(string destinationDirectory,
ExtractionOptions options = null);
void WriteEntryToDirectory(string destinationDirectory);
Stream OpenEntryStream();
@@ -327,7 +325,7 @@ public interface IEntry
DateTime? LastModifiedTime { get; }
CompressionType CompressionType { get; }
void WriteToFile(string fullPath, ExtractionOptions options = null);
void WriteToFile(string fullPath);
void WriteToStream(Stream destinationStream);
Stream OpenEntryStream();

View File

@@ -18,14 +18,9 @@ Most archive formats store filenames and metadata as bytes. SharpCompress must c
using SharpCompress.Common;
using SharpCompress.Readers;
// Configure encoding before opening archive
var options = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding
{
Default = Encoding.GetEncoding(932) // cp932 for Japanese
}
};
// Configure encoding using fluent factory method (preferred)
var options = ReaderOptions.ForEncoding(
new ArchiveEncoding { Default = Encoding.GetEncoding(932) }); // cp932 for Japanese
using (var archive = ZipArchive.OpenArchive("japanese.zip", options))
{
@@ -34,6 +29,12 @@ using (var archive = ZipArchive.OpenArchive("japanese.zip", options))
Console.WriteLine(entry.Key); // Now shows correct characters
}
}
// Alternative: object initializer
var options2 = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
```
### ArchiveEncoding Properties
@@ -47,10 +48,8 @@ using (var archive = ZipArchive.OpenArchive("japanese.zip", options))
**Archive API:**
```csharp
var options = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
var options = ReaderOptions.ForEncoding(
new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var archive = ZipArchive.OpenArchive("file.zip", options))
{
// Use archive with correct encoding
@@ -59,10 +58,8 @@ using (var archive = ZipArchive.OpenArchive("file.zip", options))
**Reader API:**
```csharp
var options = new ReaderOptions
{
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) }
};
var options = ReaderOptions.ForEncoding(
new ArchiveEncoding { Default = Encoding.GetEncoding(932) });
using (var stream = File.OpenRead("file.zip"))
using (var reader = ReaderFactory.OpenReader(stream, options))
{
@@ -390,11 +387,7 @@ var options = new ReaderOptions
using (var archive = ZipArchive.OpenArchive("japanese_files.zip", options))
{
archive.WriteToDirectory(@"C:\output", new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
archive.WriteToDirectory(@"C:\output");
}
// Files extracted with correct Japanese names
```

View File

@@ -213,11 +213,7 @@ using (var archive = RarArchive.OpenArchive("solid.rar"))
using (var archive = RarArchive.OpenArchive("solid.rar"))
{
// Method 1: Use WriteToDirectory (recommended)
archive.WriteToDirectory(@"C:\output", new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
archive.WriteToDirectory(@"C:\output");
// Method 2: Use ExtractAllEntries
archive.ExtractAllEntries();
@@ -337,7 +333,6 @@ using (var archive = ZipArchive.OpenArchive("archive.zip"))
{
await archive.WriteToDirectoryAsync(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true },
cancellationToken
);
}
@@ -355,10 +350,7 @@ Async doesn't improve performance for:
// Sync extraction (simpler, same performance on fast I/O)
using (var archive = ZipArchive.OpenArchive("archive.zip"))
{
archive.WriteToDirectory(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
archive.WriteToDirectory(@"C:\output");
}
// Simple and fast - no async needed
```
@@ -377,7 +369,6 @@ try
{
await archive.WriteToDirectoryAsync(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true },
cts.Token
);
}

View File

@@ -6,7 +6,7 @@ SharpCompress now provides full async/await support for all I/O operations. All
**Key Async Methods:**
- `reader.WriteEntryToAsync(stream, cancellationToken)` - Extract entry asynchronously
- `reader.WriteAllToDirectoryAsync(path, options, cancellationToken)` - Extract all asynchronously
- `reader.WriteAllToDirectoryAsync(path, cancellationToken)` - Extract all asynchronously
- `writer.WriteAsync(filename, stream, modTime, cancellationToken)` - Write entry asynchronously
- `writer.WriteAllAsync(directory, pattern, searchOption, cancellationToken)` - Write directory asynchronously
- `entry.OpenEntryStreamAsync(cancellationToken)` - Open entry stream asynchronously
@@ -40,6 +40,10 @@ To deal with the "correct" rules as well as the expectations of users, I've deci
To be explicit though, consider always using the overloads that use `ReaderOptions` or `WriterOptions` and explicitly set `LeaveStreamOpen` the way you want.
Default behavior in factory APIs:
- File path / `FileInfo` overloads set `LeaveStreamOpen = false`.
- Caller-provided `Stream` overloads set `LeaveStreamOpen = true`.
If using Compression Stream classes directly and you don't want the wrapped stream to be closed. Use the `NonDisposingStream` as a wrapper to prevent the stream being disposed. The change in 0.21 simplified a lot even though the usage is a bit more convoluted.
## Samples
@@ -90,14 +94,14 @@ Note: Extracting a solid rar or 7z file needs to be done in sequential order to
`ExtractAllEntries` is primarily intended for solid archives (like solid Rar) or 7Zip archives, where sequential extraction provides the best performance. For general/simple extraction with any supported archive type, use `archive.WriteToDirectory()` instead.
```C#
using (var archive = RarArchive.OpenArchive("Test.rar"))
// Using fluent factory method for extraction options
using (var archive = RarArchive.OpenArchive("Test.rar",
ReaderOptions.ForOwnedFile()
.WithExtractFullPath(true)
.WithOverwrite(true)))
{
// Simple extraction with RarArchive; this WriteToDirectory pattern works for all archive types
archive.WriteToDirectory(@"D:\temp", new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
archive.WriteToDirectory(@"D:\temp");
}
```
@@ -126,13 +130,13 @@ var progress = new Progress<ProgressReport>(report =>
Console.WriteLine($"Extracting {report.EntryPath}: {report.PercentComplete}%");
});
using (var archive = RarArchive.OpenArchive("archive.rar", new ReaderOptions { Progress = progress })) // Must be solid Rar or 7Zip
using (var archive = RarArchive.OpenArchive("archive.rar",
ReaderOptions.ForOwnedFile()
.WithProgress(progress)
.WithExtractFullPath(true)
.WithOverwrite(true))) // Must be solid Rar or 7Zip
{
archive.WriteToDirectory(@"D:\output", new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
archive.WriteToDirectory(@"D:\output");
}
```
@@ -147,11 +151,7 @@ using (var reader = ReaderFactory.OpenReader(stream))
if (!reader.Entry.IsDirectory)
{
Console.WriteLine(reader.Entry.Key);
reader.WriteEntryToDirectory(@"C:\temp", new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
reader.WriteEntryToDirectory(@"C:\temp");
}
}
}
@@ -180,10 +180,10 @@ using (var reader = ReaderFactory.OpenReader(stream))
```C#
using (Stream stream = File.OpenWrite("C:\\temp.tgz"))
using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip)
{
LeaveOpenStream = true
}))
using (var writer = WriterFactory.OpenWriter(
stream,
ArchiveType.Tar,
WriterOptions.ForTar(CompressionType.GZip).WithLeaveStreamOpen(true)))
{
writer.WriteAll("D:\\temp", "*", SearchOption.AllDirectories);
}
@@ -192,15 +192,15 @@ using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new Writer
### Extract zip which has non-utf8 encoded filename(cp932)
```C#
var opts = new SharpCompress.Readers.ReaderOptions();
var encoding = Encoding.GetEncoding(932);
opts.ArchiveEncoding = new SharpCompress.Common.ArchiveEncoding();
opts.ArchiveEncoding.CustomDecoder = (data, x, y) =>
{
return encoding.GetString(data);
};
var tr = SharpCompress.Archives.Zip.ZipArchive.OpenArchive("test.zip", opts);
foreach(var entry in tr.Entries)
var opts = new ReaderOptions()
.WithArchiveEncoding(new ArchiveEncoding
{
CustomDecoder = (data, x, y) => encoding.GetString(data)
});
using var archive = ZipArchive.OpenArchive("test.zip", opts);
foreach(var entry in archive.Entries)
{
Console.WriteLine($"{entry.Key}");
}
@@ -238,11 +238,6 @@ using (var reader = ReaderFactory.OpenReader(stream))
{
await reader.WriteAllToDirectoryAsync(
@"D:\temp",
new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
},
cancellationToken
);
}
@@ -321,7 +316,6 @@ using (var archive = ZipArchive.OpenArchive("archive.zip"))
// Simple async extraction - works for all archive types
await archive.WriteToDirectoryAsync(
@"C:\output",
new ExtractionOptions() { ExtractFullPath = true, Overwrite = true },
cancellationToken
);
}

View File

@@ -21,7 +21,7 @@ public abstract partial class AbstractArchive<TEntry, TVolume>
IAsyncEnumerable<TVolume> volumes
)
{
foreach (var item in LoadEntries(await volumes.ToListAsync()))
foreach (var item in LoadEntries(await volumes.ToListAsync().ConfigureAwait(false)))
{
yield return item;
}
@@ -47,8 +47,8 @@ public abstract partial class AbstractArchive<TEntry, TVolume>
private async ValueTask EnsureEntriesLoadedAsync()
{
await _lazyEntriesAsync.EnsureFullyLoaded();
await _lazyVolumesAsync.EnsureFullyLoaded();
await _lazyEntriesAsync.EnsureFullyLoaded().ConfigureAwait(false);
await _lazyVolumesAsync.EnsureFullyLoaded().ConfigureAwait(false);
}
private async IAsyncEnumerable<IArchiveEntry> EntriesAsyncCast()
@@ -73,29 +73,31 @@ public abstract partial class AbstractArchive<TEntry, TVolume>
public async ValueTask<IAsyncReader> ExtractAllEntriesAsync()
{
if (!await IsSolidAsync() && Type != ArchiveType.SevenZip)
if (!await IsSolidAsync().ConfigureAwait(false) && Type != ArchiveType.SevenZip)
{
throw new SharpCompressException(
"ExtractAllEntries can only be used on solid archives or 7Zip archives (which require random access)."
);
}
await EnsureEntriesLoadedAsync();
return await CreateReaderForSolidExtractionAsync();
await EnsureEntriesLoadedAsync().ConfigureAwait(false);
return await CreateReaderForSolidExtractionAsync().ConfigureAwait(false);
}
public virtual ValueTask<bool> IsSolidAsync() => new(false);
public async ValueTask<bool> IsCompleteAsync()
{
await EnsureEntriesLoadedAsync();
return await EntriesAsync.AllAsync(x => x.IsComplete);
await EnsureEntriesLoadedAsync().ConfigureAwait(false);
return await EntriesAsync.AllAsync(x => x.IsComplete).ConfigureAwait(false);
}
public async ValueTask<long> TotalSizeAsync() =>
await EntriesAsync.AggregateAsync(0L, (total, cf) => total + cf.CompressedSize);
await EntriesAsync
.AggregateAsync(0L, (total, cf) => total + cf.CompressedSize)
.ConfigureAwait(false);
public async ValueTask<long> TotalUncompressedSizeAsync() =>
await EntriesAsync.AggregateAsync(0L, (total, cf) => total + cf.Size);
await EntriesAsync.AggregateAsync(0L, (total, cf) => total + cf.Size).ConfigureAwait(false);
public ValueTask<bool> IsEncryptedAsync() => new(IsEncrypted);

View File

@@ -20,7 +20,7 @@ public abstract partial class AbstractArchive<TEntry, TVolume> : IArchive, IAsyn
private readonly LazyAsyncReadOnlyCollection<TVolume> _lazyVolumesAsync;
private readonly LazyAsyncReadOnlyCollection<TEntry> _lazyEntriesAsync;
protected ReaderOptions ReaderOptions { get; }
public ReaderOptions ReaderOptions { get; protected set; }
internal AbstractArchive(ArchiveType type, SourceStream sourceStream)
{

View File

@@ -5,13 +5,14 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Writers;
using SharpCompress.Common.Options;
namespace SharpCompress.Archives;
public abstract partial class AbstractWritableArchive<TEntry, TVolume>
public abstract partial class AbstractWritableArchive<TEntry, TVolume, TOptions>
where TEntry : IArchiveEntry
where TVolume : IVolume
where TOptions : IWriterOptions
{
// Async property moved from main file
private IAsyncEnumerable<TEntry> OldEntriesAsync =>
@@ -38,7 +39,7 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
if (!removedEntries.Contains(entry))
{
removedEntries.Add(entry);
await RebuildModifiedCollectionAsync();
await RebuildModifiedCollectionAsync().ConfigureAwait(false);
}
}
@@ -85,7 +86,7 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
}
var entry = CreateEntry(key, source, size, modified, closeStream);
newEntries.Add(entry);
await RebuildModifiedCollectionAsync();
await RebuildModifiedCollectionAsync().ConfigureAwait(false);
return entry;
}
@@ -105,13 +106,13 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
}
var entry = CreateDirectoryEntry(key, modified);
newEntries.Add(entry);
await RebuildModifiedCollectionAsync();
await RebuildModifiedCollectionAsync().ConfigureAwait(false);
return entry;
}
public async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
TOptions options,
CancellationToken cancellationToken = default
)
{
@@ -120,4 +121,12 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
await SaveToAsync(stream, options, OldEntriesAsync, newEntries, cancellationToken)
.ConfigureAwait(false);
}
protected abstract ValueTask SaveToAsync(
Stream stream,
TOptions options,
IAsyncEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries,
CancellationToken cancellationToken = default
);
}

View File

@@ -5,23 +5,25 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.IO;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
public abstract partial class AbstractWritableArchive<TEntry, TVolume>
public abstract partial class AbstractWritableArchive<TEntry, TVolume, TOptions>
: AbstractArchive<TEntry, TVolume>,
IWritableArchive,
IWritableAsyncArchive
IWritableArchive<TOptions>,
IWritableAsyncArchive<TOptions>
where TEntry : IArchiveEntry
where TVolume : IVolume
where TOptions : IWriterOptions
{
private class RebuildPauseDisposable : IDisposable
{
private readonly AbstractWritableArchive<TEntry, TVolume> archive;
private readonly AbstractWritableArchive<TEntry, TVolume, TOptions> archive;
public RebuildPauseDisposable(AbstractWritableArchive<TEntry, TVolume> archive)
public RebuildPauseDisposable(AbstractWritableArchive<TEntry, TVolume, TOptions> archive)
{
this.archive = archive;
archive.pauseRebuilding = true;
@@ -150,13 +152,15 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
long size,
DateTime? modified,
CancellationToken cancellationToken
) => await AddEntryAsync(key, source, closeStream, size, modified, cancellationToken);
) =>
await AddEntryAsync(key, source, closeStream, size, modified, cancellationToken)
.ConfigureAwait(false);
async ValueTask<IArchiveEntry> IWritableAsyncArchive.AddDirectoryEntryAsync(
string key,
DateTime? modified,
CancellationToken cancellationToken
) => await AddDirectoryEntryAsync(key, modified, cancellationToken);
) => await AddDirectoryEntryAsync(key, modified, cancellationToken).ConfigureAwait(false);
public TEntry AddDirectoryEntry(string key, DateTime? modified = null)
{
@@ -174,7 +178,7 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
return entry;
}
public void SaveTo(Stream stream, WriterOptions options)
public void SaveTo(Stream stream, TOptions options)
{
//reset streams of new entries
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
@@ -210,19 +214,11 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
protected abstract void SaveTo(
Stream stream,
WriterOptions options,
TOptions options,
IEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries
);
protected abstract ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
IAsyncEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries,
CancellationToken cancellationToken = default
);
public override void Dispose()
{
base.Dispose();

View File

@@ -19,9 +19,12 @@ public static partial class ArchiveFactory
CancellationToken cancellationToken = default
)
{
readerOptions ??= new ReaderOptions();
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken);
return factory.OpenAsyncArchive(stream, readerOptions);
readerOptions ??= ReaderOptions.ForExternalStream;
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken)
.ConfigureAwait(false);
return await factory
.OpenAsyncArchive(stream, readerOptions, cancellationToken)
.ConfigureAwait(false);
}
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
@@ -40,10 +43,13 @@ public static partial class ArchiveFactory
CancellationToken cancellationToken = default
)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
options ??= ReaderOptions.ForOwnedFile;
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken);
return factory.OpenAsyncArchive(fileInfo, options);
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken)
.ConfigureAwait(false);
return await factory
.OpenAsyncArchive(fileInfo, options, cancellationToken)
.ConfigureAwait(false);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
@@ -62,14 +68,18 @@ public static partial class ArchiveFactory
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
{
return await OpenAsyncArchive(fileInfo, options, cancellationToken);
return await OpenAsyncArchive(fileInfo, options, cancellationToken)
.ConfigureAwait(false);
}
fileInfo.NotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
options ??= ReaderOptions.ForOwnedFile;
var factory = await FindFactoryAsync<IMultiArchiveFactory>(fileInfo, cancellationToken);
return factory.OpenAsyncArchive(filesArray, options, cancellationToken);
var factory = await FindFactoryAsync<IMultiArchiveFactory>(fileInfo, cancellationToken)
.ConfigureAwait(false);
return await factory
.OpenAsyncArchive(filesArray, options, cancellationToken)
.ConfigureAwait(false);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
@@ -89,14 +99,18 @@ public static partial class ArchiveFactory
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
{
return await OpenAsyncArchive(firstStream, options, cancellationToken);
return await OpenAsyncArchive(firstStream, options, cancellationToken)
.ConfigureAwait(false);
}
firstStream.NotNull(nameof(firstStream));
options ??= new ReaderOptions();
options ??= ReaderOptions.ForExternalStream;
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken);
return factory.OpenAsyncArchive(streamsArray, options);
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken)
.ConfigureAwait(false);
return await factory
.OpenAsyncArchive(streamsArray, options, cancellationToken)
.ConfigureAwait(false);
}
public static ValueTask<T> FindFactoryAsync<T>(
@@ -117,7 +131,7 @@ public static partial class ArchiveFactory
{
finfo.NotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return await FindFactoryAsync<T>(stream, cancellationToken);
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
}
private static async ValueTask<T> FindFactoryAsync<T>(
@@ -140,7 +154,11 @@ public static partial class ArchiveFactory
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (await factory.IsArchiveAsync(stream, cancellationToken: cancellationToken))
if (
await factory
.IsArchiveAsync(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false)
)
{
stream.Seek(startPosition, SeekOrigin.Begin);

View File

@@ -2,11 +2,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.Common.Options;
using SharpCompress.Factories;
using SharpCompress.IO;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
@@ -15,22 +13,23 @@ public static partial class ArchiveFactory
{
public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
{
readerOptions ??= new ReaderOptions();
readerOptions ??= ReaderOptions.ForExternalStream;
return FindFactory<IArchiveFactory>(stream).OpenArchive(stream, readerOptions);
}
public static IWritableArchive CreateArchive(ArchiveType type)
public static IWritableArchive<TOptions> CreateArchive<TOptions>()
where TOptions : IWriterOptions
{
var factory = Factory
.Factories.OfType<IWriteableArchiveFactory>()
.FirstOrDefault(item => item.KnownArchiveType == type);
.Factories.OfType<IWriteableArchiveFactory<TOptions>>()
.FirstOrDefault();
if (factory != null)
{
return factory.CreateArchive();
}
throw new NotSupportedException("Cannot create Archives of type: " + type);
throw new NotSupportedException("Cannot create Archives of type: " + typeof(TOptions));
}
public static IArchive OpenArchive(string filePath, ReaderOptions? options = null)
@@ -41,7 +40,7 @@ public static partial class ArchiveFactory
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
options ??= ReaderOptions.ForOwnedFile;
return FindFactory<IArchiveFactory>(fileInfo).OpenArchive(fileInfo, options);
}
@@ -65,7 +64,7 @@ public static partial class ArchiveFactory
}
fileInfo.NotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
options ??= ReaderOptions.ForOwnedFile;
return FindFactory<IMultiArchiveFactory>(fileInfo).OpenArchive(filesArray, options);
}
@@ -86,7 +85,7 @@ public static partial class ArchiveFactory
}
firstStream.NotNull(nameof(firstStream));
options ??= new ReaderOptions();
options ??= ReaderOptions.ForExternalStream;
return FindFactory<IMultiArchiveFactory>(firstStream).OpenArchive(streamsArray, options);
}
@@ -94,11 +93,11 @@ public static partial class ArchiveFactory
public static void WriteToDirectory(
string sourceArchive,
string destinationDirectory,
ExtractionOptions? options = null
ReaderOptions? options = null
)
{
using var archive = OpenArchive(sourceArchive);
archive.WriteToDirectory(destinationDirectory, options);
using var archive = OpenArchive(sourceArchive, options);
archive.WriteToDirectory(destinationDirectory);
}
public static T FindFactory<T>(string path)

View File

@@ -5,7 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.GZip;
using SharpCompress.IO;
using SharpCompress.Common.Options;
using SharpCompress.Readers;
using SharpCompress.Readers.GZip;
using SharpCompress.Writers;
@@ -24,13 +24,13 @@ public partial class GZipArchive
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await SaveToAsync(stream, new WriterOptions(CompressionType.GZip), cancellationToken)
await SaveToAsync(stream, new GZipWriterOptions(CompressionType.GZip), cancellationToken)
.ConfigureAwait(false);
}
protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
GZipWriterOptions options,
IAsyncEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries,
CancellationToken cancellationToken = default
@@ -40,14 +40,19 @@ public partial class GZipArchive
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
await using var writer = new GZipWriter(
stream,
options as GZipWriterOptions ?? new GZipWriterOptions(options)
);
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
{
if (!entry.IsDirectory)
{
using var entryStream = entry.OpenEntryStream();
using var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
await writer
.WriteAsync(
entry.Key.NotNull("Entry Key is null"),
@@ -59,7 +64,9 @@ public partial class GZipArchive
}
foreach (var entry in newEntries.Where(x => !x.IsDirectory))
{
using var entryStream = entry.OpenEntryStream();
using var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
await writer
.WriteAsync(entry.Key.NotNull("Entry Key is null"), entryStream, cancellationToken)
.ConfigureAwait(false);
@@ -77,10 +84,13 @@ public partial class GZipArchive
IAsyncEnumerable<GZipVolume> volumes
)
{
var stream = (await volumes.SingleAsync()).Stream;
var stream = (await volumes.SingleAsync().ConfigureAwait(false)).Stream;
yield return new GZipArchiveEntry(
this,
await GZipFilePart.CreateAsync(stream, ReaderOptions.ArchiveEncoding)
await GZipFilePart
.CreateAsync(stream, ReaderOptions.ArchiveEncoding)
.ConfigureAwait(false),
ReaderOptions
);
}
}

View File

@@ -5,23 +5,22 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.GZip;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.GZip;
using SharpCompress.Writers;
using SharpCompress.Writers.GZip;
namespace SharpCompress.Archives.GZip;
public partial class GZipArchive
#if NET8_0_OR_GREATER
: IWritableArchiveOpenable,
IMultiArchiveOpenable<IWritableArchive, IWritableAsyncArchive>
: IWritableArchiveOpenable<GZipWriterOptions>,
IMultiArchiveOpenable<
IWritableArchive<GZipWriterOptions>,
IWritableAsyncArchive<GZipWriterOptions>
>
#endif
{
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
@@ -29,19 +28,19 @@ public partial class GZipArchive
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return (IWritableAsyncArchive)OpenArchive(
new FileInfo(path),
readerOptions ?? new ReaderOptions()
);
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
}
public static IWritableArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
public static IWritableArchive<GZipWriterOptions> OpenArchive(
string filePath,
ReaderOptions? readerOptions = null
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<GZipWriterOptions> OpenArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null
)
@@ -56,7 +55,7 @@ public partial class GZipArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<GZipWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@@ -72,7 +71,7 @@ public partial class GZipArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<GZipWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
@@ -88,7 +87,10 @@ public partial class GZipArchive
);
}
public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive<GZipWriterOptions> OpenArchive(
Stream stream,
ReaderOptions? readerOptions = null
)
{
stream.NotNull(nameof(stream));
@@ -102,49 +104,50 @@ public partial class GZipArchive
);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(stream, readerOptions);
return new((IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(stream, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions);
return new((IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(fileInfo, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(streams, readerOptions);
return new((IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(streams, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions);
return new((IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(fileInfos, readerOptions));
}
public static IWritableArchive CreateArchive() => new GZipArchive();
public static IWritableArchive<GZipWriterOptions> CreateArchive() => new GZipArchive();
public static IWritableAsyncArchive CreateAsyncArchive() => new GZipArchive();
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> CreateAsyncArchive() =>
new(new GZipArchive());
public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath));

View File

@@ -2,10 +2,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.Common.GZip;
using SharpCompress.Common.Options;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.GZip;
@@ -14,7 +13,8 @@ using SharpCompress.Writers.GZip;
namespace SharpCompress.Archives.GZip;
public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
public partial class GZipArchive
: AbstractWritableArchive<GZipArchiveEntry, GZipVolume, GZipWriterOptions>
{
private GZipArchive(SourceStream sourceStream)
: base(ArchiveType.GZip, sourceStream) { }
@@ -33,7 +33,7 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
public void SaveTo(FileInfo fileInfo)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
SaveTo(stream, new WriterOptions(CompressionType.GZip));
SaveTo(stream, new GZipWriterOptions(CompressionType.GZip));
}
protected override GZipArchiveEntry CreateEntryInternal(
@@ -58,7 +58,7 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
protected override void SaveTo(
Stream stream,
WriterOptions options,
GZipWriterOptions options,
IEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries
)
@@ -67,7 +67,10 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
using var writer = new GZipWriter(
stream,
options as GZipWriterOptions ?? new GZipWriterOptions(options)
);
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
using var entryStream = entry.OpenEntryStream();
@@ -84,7 +87,8 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
var stream = volumes.Single().Stream;
yield return new GZipArchiveEntry(
this,
GZipFilePart.Create(stream, ReaderOptions.ArchiveEncoding)
GZipFilePart.Create(stream, ReaderOptions.ArchiveEncoding),
ReaderOptions
);
}

View File

@@ -1,15 +1,16 @@
using System.IO;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.GZip;
using SharpCompress.Common.Options;
namespace SharpCompress.Archives.GZip;
public class GZipArchiveEntry : GZipEntry, IArchiveEntry
{
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part)
: base(part) => Archive = archive;
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part, IReaderOptions readerOptions)
: base(part, readerOptions) => Archive = archive;
public virtual Stream OpenEntryStream()
{

View File

@@ -19,7 +19,7 @@ internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArch
DateTime? lastModified,
bool closeStream
)
: base(archive, null)
: base(archive, null, archive.ReaderOptions)
{
this.stream = stream;
Key = path;

View File

@@ -12,6 +12,11 @@ public interface IArchive : IDisposable
ArchiveType Type { get; }
/// <summary>
/// The options used when opening this archive, including extraction behavior settings.
/// </summary>
ReaderOptions ReaderOptions { get; }
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be

View File

@@ -47,11 +47,13 @@ public static class IArchiveEntryExtensions
}
#if LEGACY_DOTNET
using var entryStream = await archiveEntry.OpenEntryStreamAsync(cancellationToken);
using var entryStream = await archiveEntry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
#else
await using var entryStream = await archiveEntry.OpenEntryStreamAsync(
cancellationToken
);
await using var entryStream = await archiveEntry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
#endif
var sourceStream = WrapWithProgress(entryStream, archiveEntry, progress);
await sourceStream
@@ -100,15 +102,11 @@ public static class IArchiveEntryExtensions
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public void WriteToDirectory(
string destinationDirectory,
ExtractionOptions? options = null
) =>
public void WriteToDirectory(string destinationDirectory) =>
ExtractionMethods.WriteEntryToDirectory(
entry,
destinationDirectory,
options,
entry.WriteToFile
(path) => entry.WriteToFile(path)
);
/// <summary>
@@ -116,15 +114,14 @@ public static class IArchiveEntryExtensions
/// </summary>
public async ValueTask WriteToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
await ExtractionMethods
.WriteEntryToDirectoryAsync(
entry,
destinationDirectory,
options,
entry.WriteToFileAsync,
async (path, ct) =>
await entry.WriteToFileAsync(path, ct).ConfigureAwait(false),
cancellationToken
)
.ConfigureAwait(false);
@@ -132,11 +129,10 @@ public static class IArchiveEntryExtensions
/// <summary>
/// Extract to specific file
/// </summary>
public void WriteToFile(string destinationFileName, ExtractionOptions? options = null) =>
public void WriteToFile(string destinationFileName) =>
ExtractionMethods.WriteEntryToFile(
entry,
destinationFileName,
options,
(x, fm) =>
{
using var fs = File.Open(destinationFileName, fm);
@@ -149,14 +145,12 @@ public static class IArchiveEntryExtensions
/// </summary>
public async ValueTask WriteToFileAsync(
string destinationFileName,
ExtractionOptions? options = null,
CancellationToken cancellationToken = default
) =>
await ExtractionMethods
.WriteEntryToFileAsync(
entry,
destinationFileName,
options,
async (x, fm, ct) =>
{
using var fs = File.Open(destinationFileName, fm);

View File

@@ -14,28 +14,25 @@ public static class IArchiveExtensions
/// Extract to specific directory with progress reporting
/// </summary>
/// <param name="destinationDirectory">The folder to extract into.</param>
/// <param name="options">Extraction options.</param>
/// <param name="progress">Optional progress reporter for tracking extraction progress.</param>
public void WriteToDirectory(
string destinationDirectory,
ExtractionOptions? options = null,
IProgress<ProgressReport>? progress = null
)
{
if (archive.IsSolid || archive.Type == ArchiveType.SevenZip)
{
using var reader = archive.ExtractAllEntries();
reader.WriteAllToDirectory(destinationDirectory, options);
reader.WriteAllToDirectory(destinationDirectory);
}
else
{
archive.WriteToDirectoryInternal(destinationDirectory, options, progress);
archive.WriteToDirectoryInternal(destinationDirectory, progress);
}
}
private void WriteToDirectoryInternal(
string destinationDirectory,
ExtractionOptions? options,
IProgress<ProgressReport>? progress
)
{
@@ -61,7 +58,7 @@ public static class IArchiveExtensions
continue;
}
entry.WriteToDirectory(destinationDirectory, options);
entry.WriteToDirectory(destinationDirectory);
bytesRead += entry.Size;
progress?.Report(

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Factories;
using SharpCompress.Readers;
@@ -32,7 +33,13 @@ public interface IArchiveFactory : IFactory
/// </summary>
/// <param name="stream">An open, readable and seekable stream.</param>
/// <param name="readerOptions">reading options.</param>
IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null);
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> containing the opened async archive.</returns>
ValueTask<IAsyncArchive> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Constructor with a FileInfo object to an existing file.
@@ -47,5 +54,10 @@ public interface IArchiveFactory : IFactory
/// <param name="fileInfo">the file to open.</param>
/// <param name="readerOptions">reading options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncArchive OpenAsyncArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null);
/// <returns>A <see cref="ValueTask{TResult}"/> containing the opened async archive.</returns>
ValueTask<IAsyncArchive> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
}

View File

@@ -1,6 +1,7 @@
#if NET8_0_OR_GREATER
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
@@ -18,19 +19,19 @@ public interface IArchiveOpenable<TSync, TASync>
public static abstract TSync OpenArchive(Stream stream, ReaderOptions? readerOptions = null);
public static abstract TASync OpenAsyncArchive(
public static abstract ValueTask<TASync> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
public static abstract TASync OpenAsyncArchive(
public static abstract ValueTask<TASync> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
public static abstract TASync OpenAsyncArchive(
public static abstract ValueTask<TASync> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default

View File

@@ -17,44 +17,45 @@ public static class IAsyncArchiveExtensions
/// </summary>
/// <param name="archive">The archive to extract.</param>
/// <param name="destinationDirectory">The folder to extract into.</param>
/// <param name="options">Extraction options.</param>
/// <param name="progress">Optional progress reporter for tracking extraction progress.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
public async ValueTask WriteToDirectoryAsync(
string destinationDirectory,
ExtractionOptions? options = null,
IProgress<ProgressReport>? progress = null,
CancellationToken cancellationToken = default
)
{
if (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip)
if (
await archive.IsSolidAsync().ConfigureAwait(false)
|| archive.Type == ArchiveType.SevenZip
)
{
await using var reader = await archive.ExtractAllEntriesAsync();
await reader.WriteAllToDirectoryAsync(
destinationDirectory,
options,
cancellationToken
);
await using var reader = await archive
.ExtractAllEntriesAsync()
.ConfigureAwait(false);
await reader
.WriteAllToDirectoryAsync(destinationDirectory, cancellationToken)
.ConfigureAwait(false);
}
else
{
await archive.WriteToDirectoryAsyncInternal(
destinationDirectory,
options,
progress,
cancellationToken
);
await archive
.WriteToDirectoryAsyncInternal(
destinationDirectory,
progress,
cancellationToken
)
.ConfigureAwait(false);
}
}
private async ValueTask WriteToDirectoryAsyncInternal(
string destinationDirectory,
ExtractionOptions? options,
IProgress<ProgressReport>? progress,
CancellationToken cancellationToken
)
{
var totalBytes = await archive.TotalUncompressedSizeAsync();
var totalBytes = await archive.TotalUncompressedSizeAsync().ConfigureAwait(false);
var bytesRead = 0L;
var seenDirectories = new HashSet<string>();
@@ -79,7 +80,7 @@ public static class IAsyncArchiveExtensions
}
await entry
.WriteToDirectoryAsync(destinationDirectory, options, cancellationToken)
.WriteToDirectoryAsync(destinationDirectory, cancellationToken)
.ConfigureAwait(false);
bytesRead += entry.Size;

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Factories;
using SharpCompress.Readers;
@@ -33,9 +34,12 @@ public interface IMultiArchiveFactory : IFactory
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions">reading options.</param>
IAsyncArchive OpenAsyncArchive(
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> containing the opened async archive.</returns>
ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
/// <summary>
@@ -51,7 +55,8 @@ public interface IMultiArchiveFactory : IFactory
/// <param name="fileInfos"></param>
/// <param name="readerOptions">reading options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncArchive OpenAsyncArchive(
/// <returns>A <see cref="ValueTask{TResult}"/> containing the opened async archive.</returns>
ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.Archives;
@@ -20,13 +21,13 @@ public interface IMultiArchiveOpenable<TSync, TASync>
ReaderOptions? readerOptions = null
);
public static abstract TASync OpenAsyncArchive(
public static abstract ValueTask<TASync> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
public static abstract TASync OpenAsyncArchive(
public static abstract ValueTask<TASync> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default

View File

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Options;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
@@ -27,28 +28,23 @@ public interface IWritableArchive : IArchive, IWritableArchiveCommon
IArchiveEntry AddDirectoryEntry(string key, DateTime? modified = null);
/// <summary>
/// Saves the archive to the specified stream using the given writer options.
/// </summary>
void SaveTo(Stream stream, WriterOptions options);
/// <summary>
/// Removes the specified entry from the archive.
/// </summary>
void RemoveEntry(IArchiveEntry entry);
}
public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon
public interface IWritableArchive<TOptions> : IWritableArchive
where TOptions : IWriterOptions
{
/// <summary>
/// Asynchronously saves the archive to the specified stream using the given writer options.
/// Saves the archive to the specified stream using the given writer options.
/// </summary>
ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
CancellationToken cancellationToken = default
);
void SaveTo(Stream stream, TOptions options);
}
public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon
{
/// <summary>
/// Asynchronously adds an entry to the archive with the specified key, source stream, and options.
/// </summary>
@@ -75,3 +71,16 @@ public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon
/// </summary>
ValueTask RemoveEntryAsync(IArchiveEntry entry);
}
public interface IWritableAsyncArchive<TOptions> : IWritableAsyncArchive
where TOptions : IWriterOptions
{
/// <summary>
/// Asynchronously saves the archive to the specified stream using the given writer options.
/// </summary>
ValueTask SaveToAsync(
Stream stream,
TOptions options,
CancellationToken cancellationToken = default
);
}

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
@@ -57,14 +58,23 @@ public static class IWritableArchiveExtensions
fileInfo.LastWriteTime
);
}
}
public void SaveTo(string filePath, WriterOptions? options = null) =>
writableArchive.SaveTo(new FileInfo(filePath), options ?? new(CompressionType.Deflate));
public static void SaveTo<TOptions>(
this IWritableArchive<TOptions> writableArchive,
string filePath,
TOptions options
)
where TOptions : IWriterOptions => writableArchive.SaveTo(new FileInfo(filePath), options);
public void SaveTo(FileInfo fileInfo, WriterOptions? options = null)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
writableArchive.SaveTo(stream, options ?? new(CompressionType.Deflate));
}
public static void SaveTo<TOptions>(
this IWritableArchive<TOptions> writableArchive,
FileInfo fileInfo,
TOptions options
)
where TOptions : IWriterOptions
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
writableArchive.SaveTo(stream, options);
}
}

View File

@@ -1,10 +1,14 @@
using System.Threading.Tasks;
using SharpCompress.Common.Options;
#if NET8_0_OR_GREATER
namespace SharpCompress.Archives;
public interface IWritableArchiveOpenable
: IArchiveOpenable<IWritableArchive, IWritableAsyncArchive>
public interface IWritableArchiveOpenable<TOptions>
: IArchiveOpenable<IWritableArchive<TOptions>, IWritableAsyncArchive<TOptions>>
where TOptions : IWriterOptions
{
public static abstract IWritableArchive CreateArchive();
public static abstract IWritableAsyncArchive CreateAsyncArchive();
public static abstract IWritableArchive<TOptions> CreateArchive();
public static abstract ValueTask<IWritableAsyncArchive<TOptions>> CreateAsyncArchive();
}
#endif

View File

@@ -3,6 +3,7 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Writers;
namespace SharpCompress.Archives;
@@ -24,13 +25,15 @@ public static class IWritableAsyncArchiveExtensions
)
{
var fileInfo = new FileInfo(path);
await writableArchive.AddEntryAsync(
path.Substring(filePath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
await writableArchive
.AddEntryAsync(
path.Substring(filePath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
)
.ConfigureAwait(false);
}
}
}
@@ -59,28 +62,26 @@ public static class IWritableAsyncArchiveExtensions
fileInfo.LastWriteTime
);
}
}
public ValueTask SaveToAsync(
string filePath,
WriterOptions? options = null,
CancellationToken cancellationToken = default
) =>
writableArchive.SaveToAsync(
new FileInfo(filePath),
options ?? new(CompressionType.Deflate),
cancellationToken
);
public static ValueTask SaveToAsync<TOptions>(
this IWritableAsyncArchive<TOptions> writableArchive,
string filePath,
TOptions options,
CancellationToken cancellationToken = default
)
where TOptions : IWriterOptions =>
writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken);
public async ValueTask SaveToAsync(
FileInfo fileInfo,
WriterOptions? options = null,
CancellationToken cancellationToken = default
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await writableArchive
.SaveToAsync(stream, options ?? new(CompressionType.Deflate), cancellationToken)
.ConfigureAwait(false);
}
public static async ValueTask SaveToAsync<TOptions>(
this IWritableAsyncArchive<TOptions> writableArchive,
FileInfo fileInfo,
TOptions options,
CancellationToken cancellationToken = default
)
where TOptions : IWriterOptions
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await writableArchive.SaveToAsync(stream, options, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -1,3 +1,5 @@
using SharpCompress.Common.Options;
namespace SharpCompress.Archives;
/// <summary>
@@ -10,11 +12,12 @@ namespace SharpCompress.Archives;
/// <item><see cref="Factories.ZipFactory"/></item>
/// <item><see cref="Factories.GZipFactory"/></item>
/// </list>
public interface IWriteableArchiveFactory : Factories.IFactory
public interface IWriteableArchiveFactory<TOptions> : Factories.IFactory
where TOptions : IWriterOptions
{
/// <summary>
/// Creates a new, empty archive, ready to be written.
/// </summary>
/// <returns></returns>
IWritableArchive CreateArchive();
IWritableArchive<TOptions> CreateArchive();
}

View File

@@ -24,8 +24,7 @@ internal class FileInfoRarArchiveVolume : RarVolume
private static ReaderOptions FixOptions(ReaderOptions options)
{
//make sure we're closing streams with fileinfo
options.LeaveStreamOpen = false;
return options;
return options with { LeaveStreamOpen = false };
}
internal ReadOnlyCollection<RarFilePart> FileParts { get; }

View File

@@ -25,13 +25,13 @@ public partial class RarArchive
}
_disposed = true;
await base.DisposeAsync();
await base.DisposeAsync().ConfigureAwait(false);
}
}
protected override async ValueTask<IAsyncReader> CreateReaderForSolidExtractionAsync()
{
if (await this.IsMultipartVolumeAsync())
if (await this.IsMultipartVolumeAsync().ConfigureAwait(false))
{
var streams = await VolumesAsync
.Select(volume =>
@@ -39,15 +39,18 @@ public partial class RarArchive
volume.Stream.Position = 0;
return volume.Stream;
})
.ToListAsync();
.ToListAsync()
.ConfigureAwait(false);
return (RarReader)RarReader.OpenReader(streams, ReaderOptions);
}
var stream = (await VolumesAsync.FirstAsync()).Stream;
var stream = (await VolumesAsync.FirstAsync().ConfigureAwait(false)).Stream;
stream.Position = 0;
return (RarReader)RarReader.OpenReader(stream, ReaderOptions);
}
public override async ValueTask<bool> IsSolidAsync() =>
await (await VolumesAsync.CastAsync<RarVolume>().FirstAsync()).IsSolidArchiveAsync();
await (await VolumesAsync.CastAsync<RarVolume>().FirstAsync().ConfigureAwait(false))
.IsSolidArchiveAsync()
.ConfigureAwait(false);
}

View File

@@ -25,12 +25,16 @@ public static class RarArchiveExtensions
/// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing.
/// </summary>
public async ValueTask<bool> IsFirstVolumeAsync() =>
(await archive.VolumesAsync.CastAsync<RarVolume>().FirstAsync()).IsFirstVolume;
(
await archive.VolumesAsync.CastAsync<RarVolume>().FirstAsync().ConfigureAwait(false)
).IsFirstVolume;
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public async ValueTask<bool> IsMultipartVolumeAsync() =>
(await archive.VolumesAsync.CastAsync<RarVolume>().FirstAsync()).IsMultiVolume;
(
await archive.VolumesAsync.CastAsync<RarVolume>().FirstAsync().ConfigureAwait(false)
).IsMultiVolume;
}
}

View File

@@ -20,7 +20,7 @@ public partial class RarArchive
IMultiArchiveOpenable<IRarArchive, IRarAsyncArchive>
#endif
{
public static IRarAsyncArchive OpenAsyncArchive(
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
@@ -28,7 +28,7 @@ public partial class RarArchive
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return (IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions);
return new((IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions));
}
public static IRarArchive OpenArchive(string filePath, ReaderOptions? options = null)
@@ -100,44 +100,44 @@ public partial class RarArchive
);
}
public static IRarAsyncArchive OpenAsyncArchive(
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(stream, readerOptions);
return new((IRarAsyncArchive)OpenArchive(stream, readerOptions));
}
public static IRarAsyncArchive OpenAsyncArchive(
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(fileInfo, readerOptions);
return new((IRarAsyncArchive)OpenArchive(fileInfo, readerOptions));
}
public static IRarAsyncArchive OpenAsyncArchive(
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(streams, readerOptions);
return new((IRarAsyncArchive)OpenArchive(streams, readerOptions));
}
public static IRarAsyncArchive OpenAsyncArchive(
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(fileInfos, readerOptions);
return new((IRarAsyncArchive)OpenArchive(fileInfos, readerOptions));
}
public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath));

View File

@@ -21,9 +21,9 @@ public partial class RarArchiveEntry
stream = new RarStream(
archive.UnpackV1.Value,
FileHeader,
await MultiVolumeReadOnlyAsyncStream.Create(
Parts.ToAsyncEnumerable().CastAsync<RarFilePart>()
)
await MultiVolumeReadOnlyAsyncStream
.Create(Parts.ToAsyncEnumerable().CastAsync<RarFilePart>())
.ConfigureAwait(false)
);
}
else
@@ -31,13 +31,13 @@ public partial class RarArchiveEntry
stream = new RarStream(
archive.UnpackV2017.Value,
FileHeader,
await MultiVolumeReadOnlyAsyncStream.Create(
Parts.ToAsyncEnumerable().CastAsync<RarFilePart>()
)
await MultiVolumeReadOnlyAsyncStream
.Create(Parts.ToAsyncEnumerable().CastAsync<RarFilePart>())
.ConfigureAwait(false)
);
}
await stream.InitializeAsync(cancellationToken);
await stream.InitializeAsync(cancellationToken).ConfigureAwait(false);
return stream;
}
}

View File

@@ -23,6 +23,7 @@ public partial class RarArchiveEntry : RarEntry, IArchiveEntry
IEnumerable<RarFilePart> parts,
ReaderOptions readerOptions
)
: base(readerOptions)
{
this.parts = parts.ToList();
this.archive = archive;

View File

@@ -21,15 +21,12 @@ public partial class SevenZipArchive
{
stream.Position = 0;
var reader = new ArchiveReader();
await reader.OpenAsync(
stream,
lookForHeader: ReaderOptions.LookForHeader,
cancellationToken
);
_database = await reader.ReadDatabaseAsync(
new PasswordProvider(ReaderOptions.Password),
cancellationToken
);
await reader
.OpenAsync(stream, lookForHeader: ReaderOptions.LookForHeader, cancellationToken)
.ConfigureAwait(false);
_database = await reader
.ReadDatabaseAsync(new PasswordProvider(ReaderOptions.Password), cancellationToken)
.ConfigureAwait(false);
}
}
@@ -37,8 +34,8 @@ public partial class SevenZipArchive
IAsyncEnumerable<SevenZipVolume> volumes
)
{
var stream = (await volumes.SingleAsync()).Stream;
await LoadFactoryAsync(stream);
var stream = (await volumes.SingleAsync().ConfigureAwait(false)).Stream;
await LoadFactoryAsync(stream).ConfigureAwait(false);
if (_database is null)
{
yield break;
@@ -49,7 +46,8 @@ public partial class SevenZipArchive
var file = _database._files[i];
entries[i] = new SevenZipArchiveEntry(
this,
new SevenZipFilePart(stream, _database, i, file, ReaderOptions.ArchiveEncoding)
new SevenZipFilePart(stream, _database, i, file, ReaderOptions.ArchiveEncoding),
ReaderOptions
);
}
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))

View File

@@ -16,7 +16,7 @@ public partial class SevenZipArchive
IMultiArchiveOpenable<IArchive, IAsyncArchive>
#endif
{
public static IAsyncArchive OpenAsyncArchive(
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
@@ -24,7 +24,9 @@ public partial class SevenZipArchive
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty("path");
return (IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions());
return new(
(IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions())
);
}
public static IArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
@@ -91,44 +93,44 @@ public partial class SevenZipArchive
);
}
public static IAsyncArchive OpenAsyncArchive(
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(stream, readerOptions);
return new((IAsyncArchive)OpenArchive(stream, readerOptions));
}
public static IAsyncArchive OpenAsyncArchive(
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(fileInfo, readerOptions);
return new((IAsyncArchive)OpenArchive(fileInfo, readerOptions));
}
public static IAsyncArchive OpenAsyncArchive(
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(streams, readerOptions);
return new((IAsyncArchive)OpenArchive(streams, readerOptions));
}
public static IAsyncArchive OpenAsyncArchive(
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(fileInfos, readerOptions);
return new((IAsyncArchive)OpenArchive(fileInfos, readerOptions));
}
public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath));
@@ -163,7 +165,7 @@ public partial class SevenZipArchive
cancellationToken.ThrowIfCancellationRequested();
try
{
return await SignatureMatchAsync(stream, cancellationToken);
return await SignatureMatchAsync(stream, cancellationToken).ConfigureAwait(false);
}
catch
{

View File

@@ -55,7 +55,8 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
i,
file,
ReaderOptions.ArchiveEncoding
)
),
ReaderOptions
);
}
foreach (
@@ -181,15 +182,15 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
);
}
// Wrap with SyncOnlyStream to work around LZMA async bugs
// Return a ReadOnlySubStream that reads from the shared folder stream
return CreateEntryStream(
new SyncOnlyStream(
new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true)
)
new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true)
);
}
protected override ValueTask<EntryStream> GetEntryStreamAsync(
CancellationToken cancellationToken = default
) => new(GetEntryStream());
public override void Dispose()
{
_currentFolderStream?.Dispose();

View File

@@ -1,20 +1,28 @@
using System.IO;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Options;
using SharpCompress.Common.SevenZip;
namespace SharpCompress.Archives.SevenZip;
public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry
{
internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part)
: base(part) => Archive = archive;
internal SevenZipArchiveEntry(
SevenZipArchive archive,
SevenZipFilePart part,
IReaderOptions readerOptions
)
: base(part, readerOptions) => Archive = archive;
public Stream OpenEntryStream() => FilePart.GetCompressedStream();
public async ValueTask<Stream> OpenEntryStreamAsync(
CancellationToken cancellationToken = default
) => (await FilePart.GetCompressedStreamAsync(cancellationToken)).NotNull();
) =>
(
await FilePart.GetCompressedStreamAsync(cancellationToken).ConfigureAwait(false)
).NotNull();
public IArchive Archive { get; }

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Common.Tar;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
@@ -18,13 +19,16 @@ public partial class TarArchive
{
protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
TarWriterOptions options,
IAsyncEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries,
CancellationToken cancellationToken = default
)
{
using var writer = new TarWriter(stream, new TarWriterOptions(options));
using var writer = new TarWriter(
stream,
options as TarWriterOptions ?? new TarWriterOptions(options)
);
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)
@@ -92,7 +96,7 @@ public partial class TarArchive
IAsyncEnumerable<TarVolume> volumes
)
{
var stream = (await volumes.SingleAsync()).Stream;
var stream = (await volumes.SingleAsync().ConfigureAwait(false)).Stream;
if (stream.CanSeek)
{
stream.Position = 0;
@@ -123,7 +127,8 @@ public partial class TarArchive
var entry = new TarArchiveEntry(
this,
new TarFilePart(previousHeader, stream),
CompressionType.None
CompressionType.None,
ReaderOptions
);
var oldStreamPos = stream.Position;
@@ -131,7 +136,7 @@ public partial class TarArchive
using (var entryStream = entry.OpenEntryStream())
{
using var memoryStream = new MemoryStream();
await entryStream.CopyToAsync(memoryStream);
await entryStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();
@@ -147,7 +152,8 @@ public partial class TarArchive
yield return new TarArchiveEntry(
this,
new TarFilePart(header, stream),
CompressionType.None
CompressionType.None,
ReaderOptions
);
}
}

View File

@@ -7,71 +7,81 @@ using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Factories;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Writers.Tar;
namespace SharpCompress.Archives.Tar;
public partial class TarArchive
#if NET8_0_OR_GREATER
: IWritableArchiveOpenable,
IMultiArchiveOpenable<IWritableArchive, IWritableAsyncArchive>
: IWritableArchiveOpenable<TarWriterOptions>,
IMultiArchiveOpenable<
IWritableArchive<TarWriterOptions>,
IWritableAsyncArchive<TarWriterOptions>
>
#endif
{
public static IWritableArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
public static IWritableArchive<TarWriterOptions> OpenArchive(
string filePath,
ReaderOptions? readerOptions = null
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenArchive(new FileInfo(filePath), readerOptions);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<TarWriterOptions> OpenArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null
)
{
fileInfo.NotNull(nameof(fileInfo));
return new TarArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
)
return OpenArchive(
[fileInfo],
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<TarWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new TarArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
)
var sourceStream = new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
);
var compressionType = TarFactory.GetCompressionType(sourceStream);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<TarWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
return new TarArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
var sourceStream = new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
);
var compressionType = TarFactory.GetCompressionType(sourceStream);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
}
public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive<TarWriterOptions> OpenArchive(
Stream stream,
ReaderOptions? readerOptions = null
)
{
stream.NotNull(nameof(stream));
@@ -80,59 +90,96 @@ public partial class TarArchive
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new TarArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
return OpenArchive([stream], readerOptions);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(stream, readerOptions);
stream.NotNull(nameof(stream));
var sourceStream = new SourceStream(
stream,
i => null,
readerOptions ?? new ReaderOptions()
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(new FileInfo(path), readerOptions);
path.NotNullOrEmpty(nameof(path));
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions);
fileInfo.NotNull(nameof(fileInfo));
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
var sourceStream = new SourceStream(fileInfo, i => null, readerOptions);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(streams, readerOptions);
streams.NotNull(nameof(streams));
var strms = streams.ToArray();
var sourceStream = new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions);
fileInfos.NotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
var sourceStream = new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
);
var compressionType = await TarFactory
.GetCompressionTypeAsync(sourceStream, cancellationToken)
.ConfigureAwait(false);
sourceStream.Seek(0, SeekOrigin.Begin);
return new TarArchive(sourceStream, compressionType);
}
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
@@ -181,7 +228,7 @@ public partial class TarArchive
#else
using var reader = new AsyncBinaryReader(stream, leaveOpen: true);
#endif
var readSucceeded = await tarHeader.ReadAsync(reader);
var readSucceeded = await tarHeader.ReadAsync(reader).ConfigureAwait(false);
var isEmptyArchive =
tarHeader.Name?.Length == 0
&& tarHeader.Size == 0
@@ -196,7 +243,8 @@ public partial class TarArchive
}
}
public static IWritableArchive CreateArchive() => new TarArchive();
public static IWritableArchive<TarWriterOptions> CreateArchive() => new TarArchive();
public static IWritableAsyncArchive CreateAsyncArchive() => new TarArchive();
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> CreateAsyncArchive() =>
new(new TarArchive());
}

View File

@@ -2,36 +2,60 @@ 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.Tar;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.Xz;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.Tar;
using SharpCompress.Writers;
using SharpCompress.Writers.Tar;
using CompressionMode = SharpCompress.Compressors.CompressionMode;
using Constants = SharpCompress.Common.Constants;
namespace SharpCompress.Archives.Tar;
public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
public partial class TarArchive
: AbstractWritableArchive<TarArchiveEntry, TarVolume, TarWriterOptions>
{
private readonly CompressionType _compressionType;
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream sourceStream)
{
sourceStream.NotNull("SourceStream is null").LoadAllParts();
return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable();
}
private TarArchive(SourceStream sourceStream)
: base(ArchiveType.Tar, sourceStream) { }
internal TarArchive(SourceStream sourceStream, CompressionType compressionType)
: base(ArchiveType.Tar, sourceStream)
{
_compressionType = compressionType;
}
private TarArchive()
: base(ArchiveType.Tar) { }
private Stream GetStream(Stream stream) =>
_compressionType switch
{
CompressionType.BZip2 => BZip2Stream.Create(stream, CompressionMode.Decompress, false),
CompressionType.GZip => new GZipStream(stream, CompressionMode.Decompress),
CompressionType.ZStandard => new ZStandardStream(stream),
CompressionType.LZip => new LZipStream(stream, CompressionMode.Decompress),
CompressionType.Xz => new XZStream(stream),
CompressionType.Lzw => new LzwStream(stream),
CompressionType.None => stream,
_ => throw new NotSupportedException("Invalid compression type: " + _compressionType),
};
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
{
var stream = volumes.Single().Stream;
var stream = GetStream(volumes.Single().Stream);
if (stream.CanSeek)
{
stream.Position = 0;
@@ -39,7 +63,9 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
TarHeader? previousHeader = null;
foreach (
var header in TarHeaderFactory.ReadHeader(
StreamingMode.Seekable,
_compressionType == CompressionType.None
? StreamingMode.Seekable
: StreamingMode.Streaming,
stream,
ReaderOptions.ArchiveEncoding
)
@@ -58,7 +84,8 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
var entry = new TarArchiveEntry(
this,
new TarFilePart(previousHeader, stream),
CompressionType.None
CompressionType.None,
ReaderOptions
);
var oldStreamPos = stream.Position;
@@ -80,7 +107,8 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
yield return new TarArchiveEntry(
this,
new TarFilePart(header, stream),
CompressionType.None
CompressionType.None,
ReaderOptions
);
}
}
@@ -115,12 +143,15 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
protected override void SaveTo(
Stream stream,
WriterOptions options,
TarWriterOptions options,
IEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries
)
{
using var writer = new TarWriter(stream, new TarWriterOptions(options));
using var writer = new TarWriter(
stream,
options as TarWriterOptions ?? new TarWriterOptions(options)
);
foreach (var entry in oldEntries.Concat(newEntries))
{
if (entry.IsDirectory)
@@ -147,6 +178,6 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return TarReader.OpenReader(stream);
return TarReader.OpenReader(GetStream(stream));
}
}

View File

@@ -1,22 +1,31 @@
using System.IO;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Common.Tar;
namespace SharpCompress.Archives.Tar;
public class TarArchiveEntry : TarEntry, IArchiveEntry
{
internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType compressionType)
: base(part, compressionType) => Archive = archive;
internal TarArchiveEntry(
TarArchive archive,
TarFilePart? part,
CompressionType compressionType,
IReaderOptions readerOptions
)
: base(part, compressionType, readerOptions) => Archive = archive;
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
public async ValueTask<Stream> OpenEntryStreamAsync(
CancellationToken cancellationToken = default
) => (await Parts.Single().GetCompressedStreamAsync(cancellationToken)).NotNull();
) =>
(
await Parts.Single().GetCompressedStreamAsync(cancellationToken).ConfigureAwait(false)
).NotNull();
#region IArchiveEntry Members

View File

@@ -21,7 +21,7 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
DateTime? lastModified,
bool closeStream
)
: base(archive, null, compressionType)
: base(archive, null, compressionType, archive.ReaderOptions)
{
this.stream = stream;
Key = path;
@@ -36,7 +36,7 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
string directoryPath,
DateTime? lastModified
)
: base(archive, null, CompressionType.None)
: base(archive, null, CompressionType.None, archive.ReaderOptions)
{
stream = null;
Key = directoryPath;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
@@ -20,7 +21,7 @@ public partial class ZipArchive
IAsyncEnumerable<ZipVolume> volumes
)
{
var vols = await volumes.ToListAsync();
var vols = await volumes.ToListAsync().ConfigureAwait(false);
var volsArray = vols.ToArray();
await foreach (
@@ -54,7 +55,8 @@ public partial class ZipArchive
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
new SeekableZipFilePart(headerFactory.NotNull(), deh, s),
ReaderOptions
);
}
break;
@@ -71,13 +73,16 @@ public partial class ZipArchive
protected override async ValueTask SaveToAsync(
Stream stream,
WriterOptions options,
ZipWriterOptions options,
IAsyncEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries,
CancellationToken cancellationToken = default
)
{
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
using var writer = new ZipWriter(
stream,
options as ZipWriterOptions ?? new ZipWriterOptions(options)
);
await foreach (
var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false)
)

View File

@@ -9,22 +9,29 @@ using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Writers.Zip;
namespace SharpCompress.Archives.Zip;
public partial class ZipArchive
#if NET8_0_OR_GREATER
: IWritableArchiveOpenable,
IMultiArchiveOpenable<IWritableArchive, IWritableAsyncArchive>
: IWritableArchiveOpenable<ZipWriterOptions>,
IMultiArchiveOpenable<
IWritableArchive<ZipWriterOptions>,
IWritableAsyncArchive<ZipWriterOptions>
>
#endif
{
public static IWritableArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
public static IWritableArchive<ZipWriterOptions> OpenArchive(
string filePath,
ReaderOptions? readerOptions = null
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenArchive(new FileInfo(filePath), readerOptions);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<ZipWriterOptions> OpenArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null
)
@@ -39,7 +46,7 @@ public partial class ZipArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<ZipWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@@ -55,7 +62,7 @@ public partial class ZipArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<ZipWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
@@ -71,7 +78,10 @@ public partial class ZipArchive
);
}
public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive<ZipWriterOptions> OpenArchive(
Stream stream,
ReaderOptions? readerOptions = null
)
{
stream.NotNull(nameof(stream));
@@ -85,54 +95,54 @@ public partial class ZipArchive
);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(path, readerOptions);
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(path, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(stream, readerOptions);
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(stream, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions);
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(fileInfo, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(streams, readerOptions);
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(streams, readerOptions));
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions);
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(fileInfos, readerOptions));
}
public static bool IsZipFile(string filePath, string? password = null) =>
@@ -218,7 +228,8 @@ public partial class ZipArchive
var header = await headerFactory
.ReadStreamHeaderAsync(stream)
.Where(x => x.ZipHeaderType != ZipHeaderType.Split)
.FirstOrDefaultAsync(cancellationToken);
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
if (header is null)
{
return false;
@@ -235,9 +246,10 @@ public partial class ZipArchive
}
}
public static IWritableArchive CreateArchive() => new ZipArchive();
public static IWritableArchive<ZipWriterOptions> CreateArchive() => new ZipArchive();
public static IWritableAsyncArchive CreateAsyncArchive() => new ZipArchive();
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> CreateAsyncArchive() =>
new(new ZipArchive());
public static async ValueTask<bool> IsZipMultiAsync(
Stream stream,
@@ -261,6 +273,7 @@ public partial class ZipArchive
await foreach (
var h in z.ReadSeekableHeaderAsync(stream)
.WithCancellation(cancellationToken)
.ConfigureAwait(false)
)
{
x = h;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors.Deflate;
@@ -16,7 +17,8 @@ using SharpCompress.Writers.Zip;
namespace SharpCompress.Archives.Zip;
public partial class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
public partial class ZipArchive
: AbstractWritableArchive<ZipArchiveEntry, ZipVolume, ZipWriterOptions>
{
private readonly SeekableZipHeaderFactory? headerFactory;
@@ -94,7 +96,8 @@ public partial class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVo
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
new SeekableZipFilePart(headerFactory.NotNull(), deh, s),
ReaderOptions
);
}
break;
@@ -109,16 +112,20 @@ public partial class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVo
}
}
public void SaveTo(Stream stream) => SaveTo(stream, new WriterOptions(CompressionType.Deflate));
public void SaveTo(Stream stream) =>
SaveTo(stream, new ZipWriterOptions(CompressionType.Deflate));
protected override void SaveTo(
Stream stream,
WriterOptions options,
ZipWriterOptions options,
IEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries
)
{
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
using var writer = new ZipWriter(
stream,
options as ZipWriterOptions ?? new ZipWriterOptions(options)
);
foreach (var entry in oldEntries.Concat(newEntries))
{
if (entry.IsDirectory)

View File

@@ -15,7 +15,9 @@ public partial class ZipArchiveEntry
var part = Parts.Single();
if (part is SeekableZipFilePart seekablePart)
{
return (await seekablePart.GetCompressedStreamAsync(cancellationToken)).NotNull();
return (
await seekablePart.GetCompressedStreamAsync(cancellationToken).ConfigureAwait(false)
).NotNull();
}
return OpenEntryStream();
}

View File

@@ -1,15 +1,20 @@
using System.IO;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Options;
using SharpCompress.Common.Zip;
namespace SharpCompress.Archives.Zip;
public partial class ZipArchiveEntry : ZipEntry, IArchiveEntry
{
internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part)
: base(part) => Archive = archive;
internal ZipArchiveEntry(
ZipArchive archive,
SeekableZipFilePart? part,
IReaderOptions readerOptions
)
: base(part, readerOptions) => Archive = archive;
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();

View File

@@ -21,7 +21,7 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
DateTime? lastModified,
bool closeStream
)
: base(archive, null)
: base(archive, null, archive.ReaderOptions)
{
this.stream = stream;
Key = path;
@@ -36,7 +36,7 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
string directoryPath,
DateTime? lastModified
)
: base(archive, null)
: base(archive, null, archive.ReaderOptions)
{
stream = null;
Key = directoryPath;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.Ace.Headers;
using SharpCompress.Common.Options;
namespace SharpCompress.Common.Ace;
@@ -12,7 +13,8 @@ public class AceEntry : Entry
{
private readonly AceFilePart _filePart;
internal AceEntry(AceFilePart filePart)
internal AceEntry(AceFilePart filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
_filePart = filePart;
}

View File

@@ -18,7 +18,7 @@ public sealed partial class AceFileHeader
CancellationToken cancellationToken = default
)
{
var headerData = await ReadHeaderAsync(stream, cancellationToken);
var headerData = await ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
if (headerData.Length == 0)
{
return null;

View File

@@ -19,7 +19,9 @@ public abstract partial class AceHeader
{
// Read header CRC (2 bytes) and header size (2 bytes)
var headerBytes = new byte[4];
if (!await stream.ReadFullyAsync(headerBytes, 0, 4, cancellationToken))
if (
!await stream.ReadFullyAsync(headerBytes, 0, 4, cancellationToken).ConfigureAwait(false)
)
{
return Array.Empty<byte>();
}
@@ -33,7 +35,11 @@ public abstract partial class AceHeader
// Read the header data
var body = new byte[HeaderSize];
if (!await stream.ReadFullyAsync(body, 0, HeaderSize, cancellationToken))
if (
!await stream
.ReadFullyAsync(body, 0, HeaderSize, cancellationToken)
.ConfigureAwait(false)
)
{
return Array.Empty<byte>();
}
@@ -59,7 +65,7 @@ public abstract partial class AceHeader
)
{
var bytes = new byte[14];
if (!await stream.ReadFullyAsync(bytes, 0, 14, cancellationToken))
if (!await stream.ReadFullyAsync(bytes, 0, 14, cancellationToken).ConfigureAwait(false))
{
return false;
}

View File

@@ -19,7 +19,7 @@ public sealed partial class AceMainHeader
CancellationToken cancellationToken = default
)
{
var headerData = await ReadHeaderAsync(stream, cancellationToken);
var headerData = await ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
if (headerData.Length == 0)
{
return null;

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.GZip;
using SharpCompress.Common.Options;
using SharpCompress.Common.Tar;
namespace SharpCompress.Common.Arc;
@@ -13,7 +14,8 @@ public class ArcEntry : Entry
{
private readonly ArcFilePart? _filePart;
internal ArcEntry(ArcFilePart? filePart)
internal ArcEntry(ArcFilePart? filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
_filePart = filePart;
}

View File

@@ -41,8 +41,9 @@ public class ArcEntryHeader
{
byte[] headerBytes = new byte[29];
if (
await stream.ReadAsync(headerBytes, 0, headerBytes.Length, cancellationToken)
!= headerBytes.Length
await stream
.ReadAsync(headerBytes, 0, headerBytes.Length, cancellationToken)
.ConfigureAwait(false) != headerBytes.Length
)
{
return null;

View File

@@ -31,11 +31,9 @@ public partial class ArcFilePart
compressedStream = new RunLength90Stream(_stream, (int)Header.CompressedSize);
break;
case CompressionType.Squeezed:
compressedStream = await SqueezeStream.CreateAsync(
_stream,
(int)Header.CompressedSize,
cancellationToken
);
compressedStream = await SqueezeStream
.CreateAsync(_stream, (int)Header.CompressedSize, cancellationToken)
.ConfigureAwait(false);
break;
case CompressionType.Crunched:
if (Header.OriginalSize > 128 * 1024)

View File

@@ -10,4 +10,5 @@ public enum ArchiveType
Arc,
Arj,
Ace,
Lzw,
}

View File

@@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.Arc;
using SharpCompress.Common.Arj.Headers;
using SharpCompress.Common.Options;
namespace SharpCompress.Common.Arj;
@@ -12,7 +13,8 @@ public class ArjEntry : Entry
{
private readonly ArjFilePart _filePart;
internal ArjEntry(ArjFilePart filePart)
internal ArjEntry(ArjFilePart filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
_filePart = filePart;
}
@@ -41,9 +43,9 @@ public class ArjEntry : Entry
public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime;
public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime;
public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated?.DateTime;
public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime;
public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed?.DateTime;
public override DateTime? ArchivedTime => null;

View File

@@ -21,7 +21,7 @@ public abstract partial class ArjHeader
{
// check for magic bytes
var magic = new byte[2];
if (await stream.ReadAsync(magic, 0, 2, cancellationToken) != 2)
if (await stream.ReadAsync(magic, 0, 2, cancellationToken).ConfigureAwait(false) != 2)
{
return Array.Empty<byte>();
}
@@ -33,7 +33,7 @@ public abstract partial class ArjHeader
// read header_size
byte[] headerBytes = new byte[2];
await stream.ReadAsync(headerBytes, 0, 2, cancellationToken);
await stream.ReadAsync(headerBytes, 0, 2, cancellationToken).ConfigureAwait(false);
var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8);
if (headerSize < 1)
{
@@ -41,14 +41,16 @@ public abstract partial class ArjHeader
}
var body = new byte[headerSize];
var read = await stream.ReadAsync(body, 0, headerSize, cancellationToken);
var read = await stream
.ReadAsync(body, 0, headerSize, cancellationToken)
.ConfigureAwait(false);
if (read < headerSize)
{
return Array.Empty<byte>();
}
byte[] crc = new byte[4];
read = await stream.ReadAsync(crc, 0, 4, cancellationToken);
await stream.ReadFullyAsync(crc, 0, 4, cancellationToken).ConfigureAwait(false);
var checksum = Crc32Stream.Compute(body);
// Compute the hash value
if (checksum != BitConverter.ToUInt32(crc, 0))
@@ -68,7 +70,9 @@ public abstract partial class ArjHeader
while (true)
{
int bytesRead = await reader.ReadAsync(buffer, 0, 2, cancellationToken);
int bytesRead = await reader
.ReadAsync(buffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
if (bytesRead < 2)
{
throw new EndOfStreamException(
@@ -83,7 +87,9 @@ public abstract partial class ArjHeader
}
byte[] header = new byte[extHeaderSize];
bytesRead = await reader.ReadAsync(header, 0, extHeaderSize, cancellationToken);
bytesRead = await reader
.ReadAsync(header, 0, extHeaderSize, cancellationToken)
.ConfigureAwait(false);
if (bytesRead < extHeaderSize)
{
throw new EndOfStreamException(
@@ -92,7 +98,9 @@ public abstract partial class ArjHeader
}
byte[] crcextended = new byte[4];
bytesRead = await reader.ReadAsync(crcextended, 0, 4, cancellationToken);
bytesRead = await reader
.ReadAsync(crcextended, 0, 4, cancellationToken)
.ConfigureAwait(false);
if (bytesRead < 4)
{
throw new EndOfStreamException(
@@ -122,7 +130,7 @@ public abstract partial class ArjHeader
)
{
var bytes = new byte[2];
if (await stream.ReadAsync(bytes, 0, 2, cancellationToken) != 2)
if (await stream.ReadAsync(bytes, 0, 2, cancellationToken).ConfigureAwait(false) != 2)
{
return false;
}

View File

@@ -11,10 +11,10 @@ public partial class ArjLocalHeader
CancellationToken cancellationToken = default
)
{
var body = await ReadHeaderAsync(stream, cancellationToken);
var body = await ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
if (body.Length > 0)
{
await ReadExtendedHeadersAsync(stream, cancellationToken);
await ReadExtendedHeadersAsync(stream, cancellationToken).ConfigureAwait(false);
var header = LoadFrom(body);
header.DataStartPosition = stream.Position;
return header;

View File

@@ -27,8 +27,8 @@ public partial class ArjLocalHeader : ArjHeader
public byte FirstChapter { get; set; }
public byte LastChapter { get; set; }
public long ExtendedFilePosition { get; set; }
public DosDateTime DateTimeAccessed { get; set; } = new DosDateTime(0);
public DosDateTime DateTimeCreated { get; set; } = new DosDateTime(0);
public DosDateTime? DateTimeAccessed { get; set; }
public DosDateTime? DateTimeCreated { get; set; }
public long OriginalSizeEvenForVolumes { get; set; }
public string Name { get; set; } = string.Empty;
public string Comment { get; set; } = string.Empty;
@@ -119,11 +119,9 @@ public partial class ArjLocalHeader : ArjHeader
if (headerSize >= R9HdrSize)
{
rawTimestamp = ReadInt32();
DateTimeAccessed =
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
DateTimeAccessed = rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : null;
rawTimestamp = ReadInt32();
DateTimeCreated =
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
DateTimeCreated = rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : null;
OriginalSizeEvenForVolumes = ReadInt32();
}
}

View File

@@ -11,8 +11,8 @@ public partial class ArjMainHeader
CancellationToken cancellationToken = default
)
{
var body = await ReadHeaderAsync(stream, cancellationToken);
await ReadExtendedHeadersAsync(stream, cancellationToken);
var body = await ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false);
await ReadExtendedHeadersAsync(stream, cancellationToken).ConfigureAwait(false);
return LoadFrom(body);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SharpCompress.Common.Options;
namespace SharpCompress.Common;
@@ -87,4 +88,14 @@ public abstract class Entry : IEntry
/// Entry file attribute.
/// </summary>
public virtual int? Attrib => throw new NotImplementedException();
/// <summary>
/// The options used when opening this entry's source (reader or archive).
/// </summary>
public IReaderOptions Options { get; protected set; }
protected Entry(IReaderOptions readerOptions)
{
Options = readerOptions;
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.Common;
@@ -10,8 +11,7 @@ internal static partial class ExtractionMethods
public static async ValueTask WriteEntryToDirectoryAsync(
IEntry entry,
string destinationDirectory,
ExtractionOptions? options,
Func<string, ExtractionOptions?, CancellationToken, ValueTask> writeAsync,
Func<string, CancellationToken, ValueTask> writeAsync,
CancellationToken cancellationToken = default
)
{
@@ -34,11 +34,9 @@ internal static partial class ExtractionMethods
);
}
options ??= new ExtractionOptions() { Overwrite = true };
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
file = Utility.ReplaceInvalidFileNameChars(file);
if (options.ExtractFullPath)
if (entry.Options.ExtractFullPath)
{
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
.NotNull("Directory is null");
@@ -72,9 +70,9 @@ internal static partial class ExtractionMethods
"Entry is trying to write a file outside of the destination directory."
);
}
await writeAsync(destinationFileName, options, cancellationToken).ConfigureAwait(false);
await writeAsync(destinationFileName, cancellationToken).ConfigureAwait(false);
}
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
else if (entry.Options.ExtractFullPath && !Directory.Exists(destinationFileName))
{
Directory.CreateDirectory(destinationFileName);
}
@@ -83,34 +81,34 @@ internal static partial class ExtractionMethods
public static async ValueTask WriteEntryToFileAsync(
IEntry entry,
string destinationFileName,
ExtractionOptions? options,
Func<string, FileMode, CancellationToken, ValueTask> openAndWriteAsync,
CancellationToken cancellationToken = default
)
{
if (entry.LinkTarget != null)
{
if (options?.WriteSymbolicLink is null)
if (entry.Options.SymbolicLinkHandler is not null)
{
throw new ExtractionException(
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
);
entry.Options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
else
{
ReaderOptions.DefaultSymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
return;
}
else
{
var fm = FileMode.Create;
options ??= new ExtractionOptions() { Overwrite = true };
if (!options.Overwrite)
if (!entry.Options.Overwrite)
{
fm = FileMode.CreateNew;
}
await openAndWriteAsync(destinationFileName, fm, cancellationToken)
.ConfigureAwait(false);
entry.PreserveExtractionOptions(destinationFileName, options);
entry.PreserveExtractionOptions(destinationFileName);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.Common;
@@ -23,8 +24,7 @@ internal static partial class ExtractionMethods
public static void WriteEntryToDirectory(
IEntry entry,
string destinationDirectory,
ExtractionOptions? options,
Action<string, ExtractionOptions?> write
Action<string> write
)
{
string destinationFileName;
@@ -46,11 +46,9 @@ internal static partial class ExtractionMethods
);
}
options ??= new ExtractionOptions() { Overwrite = true };
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
file = Utility.ReplaceInvalidFileNameChars(file);
if (options.ExtractFullPath)
if (entry.Options.ExtractFullPath)
{
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
.NotNull("Directory is null");
@@ -84,9 +82,9 @@ internal static partial class ExtractionMethods
"Entry is trying to write a file outside of the destination directory."
);
}
write(destinationFileName, options);
write(destinationFileName);
}
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
else if (entry.Options.ExtractFullPath && !Directory.Exists(destinationFileName))
{
Directory.CreateDirectory(destinationFileName);
}
@@ -95,32 +93,32 @@ internal static partial class ExtractionMethods
public static void WriteEntryToFile(
IEntry entry,
string destinationFileName,
ExtractionOptions? options,
Action<string, FileMode> openAndWrite
)
{
if (entry.LinkTarget != null)
{
if (options?.WriteSymbolicLink is null)
if (entry.Options.SymbolicLinkHandler is not null)
{
throw new ExtractionException(
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
);
entry.Options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
else
{
ReaderOptions.DefaultSymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
return;
}
else
{
var fm = FileMode.Create;
options ??= new ExtractionOptions() { Overwrite = true };
if (!options.Overwrite)
if (!entry.Options.Overwrite)
{
fm = FileMode.CreateNew;
}
openAndWrite(destinationFileName, fm);
entry.PreserveExtractionOptions(destinationFileName, options);
entry.PreserveExtractionOptions(destinationFileName);
}
}
}

View File

@@ -1,40 +0,0 @@
using System;
namespace SharpCompress.Common;
public class ExtractionOptions
{
/// <summary>
/// overwrite target if it exists
/// </summary>
public bool Overwrite { get; set; }
/// <summary>
/// extract with internal directory structure
/// </summary>
public bool ExtractFullPath { get; set; }
/// <summary>
/// preserve file time
/// </summary>
public bool PreserveFileTime { get; set; }
/// <summary>
/// preserve windows file attributes
/// </summary>
public bool PreserveAttributes { get; set; }
/// <summary>
/// Delegate for writing symbolic links to disk.
/// sourcePath is where the symlink is created.
/// targetPath is what the symlink refers to.
/// </summary>
public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath);
public SymbolicLinkWriterDelegate WriteSymbolicLink = (sourcePath, targetPath) =>
{
Console.WriteLine(
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
);
};
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using SharpCompress.Readers;
namespace SharpCompress.Common.GZip;
@@ -7,9 +8,12 @@ public partial class GZipEntry
{
internal static async IAsyncEnumerable<GZipEntry> GetEntriesAsync(
Stream stream,
OptionsBase options
ReaderOptions options
)
{
yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding));
yield return new GZipEntry(
await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding).ConfigureAwait(false),
options
);
}
}

View File

@@ -1,6 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Options;
using SharpCompress.Readers;
namespace SharpCompress.Common.GZip;
@@ -8,7 +10,11 @@ public partial class GZipEntry : Entry
{
private readonly GZipFilePart? _filePart;
internal GZipEntry(GZipFilePart? filePart) => _filePart = filePart;
internal GZipEntry(GZipFilePart? filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
_filePart = filePart;
}
public override CompressionType CompressionType => CompressionType.GZip;
@@ -38,9 +44,9 @@ public partial class GZipEntry : Entry
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, OptionsBase options)
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, ReaderOptions options)
{
yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding));
yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding), options);
}
// Async methods moved to GZipEntry.Async.cs

View File

@@ -19,12 +19,12 @@ internal sealed partial class GZipFilePart
{
var part = new GZipFilePart(stream, archiveEncoding);
await part.ReadAndValidateGzipHeaderAsync(cancellationToken);
await part.ReadAndValidateGzipHeaderAsync(cancellationToken).ConfigureAwait(false);
if (stream.CanSeek)
{
var position = stream.Position;
stream.Position = stream.Length - 8;
await part.ReadTrailerAsync(cancellationToken);
await part.ReadTrailerAsync(cancellationToken).ConfigureAwait(false);
stream.Position = position;
part.EntryStartPosition = position;
}
@@ -41,7 +41,7 @@ internal sealed partial class GZipFilePart
{
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
var trailer = new byte[8];
_ = await _stream.ReadFullyAsync(trailer, 0, 8, cancellationToken);
_ = await _stream.ReadFullyAsync(trailer, 0, 8, cancellationToken).ConfigureAwait(false);
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.AsSpan().Slice(4));
@@ -53,7 +53,7 @@ internal sealed partial class GZipFilePart
{
// read the header on the first read
var header = new byte[10];
var n = await _stream.ReadAsync(header, 0, 10, cancellationToken);
var n = await _stream.ReadAsync(header, 0, 10, cancellationToken).ConfigureAwait(false);
// workitem 8501: handle edge case (decompress empty stream)
if (n == 0)
@@ -77,28 +77,29 @@ internal sealed partial class GZipFilePart
{
// read and discard extra field
var lengthField = new byte[2];
_ = await _stream.ReadAsync(lengthField, 0, 2, cancellationToken);
_ = await _stream.ReadAsync(lengthField, 0, 2, cancellationToken).ConfigureAwait(false);
var extraLength = (short)(lengthField[0] + (lengthField[1] * 256));
var extra = new byte[extraLength];
if (!await _stream.ReadFullyAsync(extra, cancellationToken))
if (!await _stream.ReadFullyAsync(extra, cancellationToken).ConfigureAwait(false))
{
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
}
}
if ((header[3] & 0x08) == 0x08)
{
_name = await ReadZeroTerminatedStringAsync(_stream, cancellationToken);
_name = await ReadZeroTerminatedStringAsync(_stream, cancellationToken)
.ConfigureAwait(false);
}
if ((header[3] & 0x10) == 0x010)
{
await ReadZeroTerminatedStringAsync(_stream, cancellationToken);
await ReadZeroTerminatedStringAsync(_stream, cancellationToken).ConfigureAwait(false);
}
if ((header[3] & 0x02) == 0x02)
{
var buf = new byte[1];
_ = await _stream.ReadAsync(buf, 0, 1, cancellationToken); // CRC16, ignore
_ = await _stream.ReadAsync(buf, 0, 1, cancellationToken).ConfigureAwait(false); // CRC16, ignore
}
}
@@ -113,7 +114,7 @@ internal sealed partial class GZipFilePart
do
{
// workitem 7740
var n = await stream.ReadAsync(buf1, 0, 1, cancellationToken);
var n = await stream.ReadAsync(buf1, 0, 1, cancellationToken).ConfigureAwait(false);
if (n != 1)
{
throw new ZlibException("Unexpected EOF reading GZIP header.");

View File

@@ -9,7 +9,7 @@ public class GZipVolume : Volume
: base(stream, options, index) { }
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
: base(fileInfo.OpenRead(), options) => options.LeaveStreamOpen = false;
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
public override bool IsFirstVolume => true;

View File

@@ -4,13 +4,9 @@ namespace SharpCompress.Common;
internal static class EntryExtensions
{
internal static void PreserveExtractionOptions(
this IEntry entry,
string destinationFileName,
ExtractionOptions options
)
internal static void PreserveExtractionOptions(this IEntry entry, string destinationFileName)
{
if (options.PreserveFileTime || options.PreserveAttributes)
if (entry.Options.PreserveFileTime || entry.Options.PreserveAttributes)
{
var nf = new FileInfo(destinationFileName);
if (!nf.Exists)
@@ -19,7 +15,7 @@ internal static class EntryExtensions
}
// update file time to original packed time
if (options.PreserveFileTime)
if (entry.Options.PreserveFileTime)
{
if (entry.CreatedTime.HasValue)
{
@@ -37,7 +33,7 @@ internal static class EntryExtensions
}
}
if (options.PreserveAttributes)
if (entry.Options.PreserveAttributes)
{
if (entry.Attrib.HasValue)
{

View File

@@ -1,4 +1,5 @@
using System;
using SharpCompress.Common.Options;
namespace SharpCompress.Common;
@@ -21,4 +22,9 @@ public interface IEntry
DateTime? LastModifiedTime { get; }
long Size { get; }
int? Attrib { get; }
/// <summary>
/// The options used when opening this entry's source (reader or archive).
/// </summary>
IReaderOptions Options { get; }
}

View File

@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// This file is required for init-only properties to work on older target frameworks (.NET Framework 4.8, .NET Standard 2.0)
// The IsExternalInit type is used by the compiler for records and init-only properties
#if NETFRAMEWORK || NETSTANDARD2_0
using System.ComponentModel;
namespace System.Runtime.CompilerServices;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit { }
#endif

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using SharpCompress.Readers;
namespace SharpCompress.Common.Lzw;
public partial class LzwEntry
{
internal static async IAsyncEnumerable<LzwEntry> GetEntriesAsync(
Stream stream,
ReaderOptions options,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
yield return new LzwEntry(
await LzwFilePart.CreateAsync(stream, options.ArchiveEncoding, cancellationToken),
options
);
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Options;
using SharpCompress.Readers;
namespace SharpCompress.Common.Lzw;
public partial class LzwEntry : Entry
{
private readonly LzwFilePart? _filePart;
internal LzwEntry(LzwFilePart? filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
_filePart = filePart;
}
public override CompressionType CompressionType => CompressionType.Lzw;
public override long Crc => 0;
public override string? Key => _filePart?.FilePartName;
public override string? LinkTarget => null;
public override long CompressedSize => 0;
public override long Size => 0;
public override DateTime? LastModifiedTime => null;
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
internal static IEnumerable<LzwEntry> GetEntries(Stream stream, ReaderOptions options)
{
yield return new LzwEntry(LzwFilePart.Create(stream, options.ArchiveEncoding), options);
}
// Async methods moved to LzwEntry.Async.cs
}

View File

@@ -0,0 +1,23 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SharpCompress.Common.Lzw;
internal sealed partial class LzwFilePart
{
internal static async ValueTask<LzwFilePart> CreateAsync(
Stream stream,
IArchiveEncoding archiveEncoding,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
var part = new LzwFilePart(stream, archiveEncoding);
// For non-seekable streams, we can't track position, so use 0 since the stream will be
// read sequentially from its current position.
part.EntryStartPosition = stream.CanSeek ? stream.Position : 0;
return part;
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using SharpCompress.Compressors.Lzw;
namespace SharpCompress.Common.Lzw;
internal sealed partial class LzwFilePart : FilePart
{
private readonly Stream _stream;
private readonly string? _name;
internal static LzwFilePart Create(Stream stream, IArchiveEncoding archiveEncoding)
{
var part = new LzwFilePart(stream, archiveEncoding);
// For non-seekable streams, we can't track position, so use 0 since the stream will be
// read sequentially from its current position.
part.EntryStartPosition = stream.CanSeek ? stream.Position : 0;
return part;
}
private LzwFilePart(Stream stream, IArchiveEncoding archiveEncoding)
: base(archiveEncoding)
{
_stream = stream;
_name = DeriveFileName(stream);
}
internal long EntryStartPosition { get; private set; }
internal override string? FilePartName => _name;
internal override Stream GetCompressedStream() =>
new LzwStream(_stream) { IsStreamOwner = false };
internal override Stream GetRawStream() => _stream;
private static string? DeriveFileName(Stream stream)
{
// Unwrap SharpCompressStream to get to the underlying FileStream
var unwrappedStream = stream;
if (stream is SharpCompress.IO.IStreamStack streamStack)
{
unwrappedStream = streamStack.BaseStream();
}
// Try to derive filename from FileStream
if (unwrappedStream is FileStream fileStream && !string.IsNullOrEmpty(fileStream.Name))
{
var fileName = Path.GetFileName(fileStream.Name);
// Strip .Z extension if present
if (fileName.EndsWith(".Z", System.StringComparison.OrdinalIgnoreCase))
{
return fileName.Substring(0, fileName.Length - 2);
}
return fileName;
}
// Default name for non-file streams
return "data";
}
}

View File

@@ -0,0 +1,17 @@
using System.IO;
using SharpCompress.Readers;
namespace SharpCompress.Common.Lzw;
public class LzwVolume : Volume
{
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 }) { }
public override bool IsFirstVolume => true;
public override bool IsMultiVolume => false;
}

View File

@@ -0,0 +1,6 @@
namespace SharpCompress.Common.Options;
public interface IEncodingOptions
{
IArchiveEncoding ArchiveEncoding { get; init; }
}

View File

@@ -0,0 +1,39 @@
using System;
namespace SharpCompress.Common.Options;
/// <summary>
/// Options for configuring extraction behavior when extracting archive entries to the filesystem.
/// </summary>
public interface IExtractionOptions
{
/// <summary>
/// Overwrite target if it exists.
/// <para><b>Breaking change:</b> Default changed from false to true in version 0.40.0.</para>
/// </summary>
bool Overwrite { get; init; }
/// <summary>
/// Extract with internal directory structure.
/// <para><b>Breaking change:</b> Default changed from false to true in version 0.40.0.</para>
/// </summary>
bool ExtractFullPath { get; init; }
/// <summary>
/// Preserve file time.
/// <para><b>Breaking change:</b> Default changed from false to true in version 0.40.0.</para>
/// </summary>
bool PreserveFileTime { get; init; }
/// <summary>
/// Preserve windows file attributes.
/// </summary>
bool PreserveAttributes { get; init; }
/// <summary>
/// Delegate for writing symbolic links to disk.
/// The first parameter is the source path (where the symlink is created).
/// The second parameter is the target path (what the symlink refers to).
/// </summary>
Action<string, string>? SymbolicLinkHandler { get; init; }
}

View File

@@ -0,0 +1,8 @@
using System;
namespace SharpCompress.Common.Options;
public interface IProgressOptions
{
IProgress<ProgressReport>? Progress { get; init; }
}

View File

@@ -0,0 +1,15 @@
namespace SharpCompress.Common.Options;
public interface IReaderOptions
: IStreamOptions,
IEncodingOptions,
IProgressOptions,
IExtractionOptions
{
bool LookForHeader { get; init; }
string? Password { get; init; }
bool DisableCheckIncomplete { get; init; }
int BufferSize { get; init; }
string? ExtensionHint { get; init; }
int? RewindableBufferSize { get; init; }
}

View File

@@ -0,0 +1,6 @@
namespace SharpCompress.Common.Options;
public interface IStreamOptions
{
bool LeaveStreamOpen { get; init; }
}

View File

@@ -0,0 +1,9 @@
using SharpCompress.Common;
namespace SharpCompress.Common.Options;
public interface IWriterOptions : IStreamOptions, IEncodingOptions, IProgressOptions
{
CompressionType CompressionType { get; init; }
int CompressionLevel { get; init; }
}

View File

@@ -1,11 +0,0 @@
namespace SharpCompress.Common;
public class OptionsBase
{
/// <summary>
/// SharpCompress will keep the supplied streams open. Default is true.
/// </summary>
public bool LeaveStreamOpen { get; set; } = true;
public IArchiveEncoding ArchiveEncoding { get; set; } = new ArchiveEncoding();
}

View File

@@ -26,7 +26,9 @@ internal sealed class AsyncRarCryptoBinaryReader : AsyncRarCrcBinaryReader
var binary = new AsyncRarCryptoBinaryReader(stream);
if (salt == null)
{
salt = await binary.ReadBytesAsyncBase(EncryptionConstV5.SIZE_SALT30);
salt = await binary
.ReadBytesAsyncBase(EncryptionConstV5.SIZE_SALT30)
.ConfigureAwait(false);
binary._readCount += EncryptionConstV5.SIZE_SALT30;
}
binary._rijndael = new BlockTransformer(cryptKey.Transformer(salt));

View File

@@ -1,3 +1,5 @@
#nullable disable
using System.Security.Cryptography;
using System.Text;
using SharpCompress.Common.Rar.Headers;
@@ -10,7 +12,7 @@ internal class CryptKey3 : ICryptKey
private string _password;
public CryptKey3(string? password) => _password = password ?? "";
public CryptKey3(string password) => _password = password ?? "";
public ICryptoTransform Transformer(byte[] salt)
{

View File

@@ -1,3 +1,5 @@
#nullable disable
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Rar;
@@ -25,6 +27,6 @@ internal sealed partial class ArchiveCryptHeader
CancellationToken cancellationToken = default
)
{
CryptInfo = await Rar5CryptoInfo.CreateAsync(reader, false);
CryptInfo = await Rar5CryptoInfo.CreateAsync(reader, false).ConfigureAwait(false);
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
using SharpCompress.Common.Rar;
using SharpCompress.IO;

View File

@@ -329,7 +329,9 @@ internal partial class FileHeader
if (HasFlag(FileFlagsV4.SALT))
{
R4Salt = await reader.ReadBytesAsync(EncryptionConstV5.SIZE_SALT30, cancellationToken);
R4Salt = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_SALT30, cancellationToken)
.ConfigureAwait(false);
}
if (HasFlag(FileFlagsV4.EXT_TIME))
{

View File

@@ -64,19 +64,19 @@ public partial class RarHeaderFactory
if (_isRar5 && _cryptInfo != null)
{
await _cryptInfo.ReadInitVAsync(new AsyncMarkingBinaryReader(stream));
var _headerKey = new CryptKey5(Options.Password.NotNull(), _cryptInfo);
await _cryptInfo
.ReadInitVAsync(new AsyncMarkingBinaryReader(stream))
.ConfigureAwait(false);
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
reader = await AsyncRarCryptoBinaryReader.Create(
stream,
_headerKey,
_cryptInfo.Salt
);
reader = await AsyncRarCryptoBinaryReader
.Create(stream, _headerKey, _cryptInfo.Salt)
.ConfigureAwait(false);
}
else
{
var key = new CryptKey3(Options.Password);
reader = await AsyncRarCryptoBinaryReader.Create(stream, key);
reader = await AsyncRarCryptoBinaryReader.Create(stream, key).ConfigureAwait(false);
}
}
@@ -189,7 +189,7 @@ public partial class RarHeaderFactory
Options.Password,
fh.Rar5CryptoInfo.NotNull()
)
: new CryptKey3(Options.Password.NotNull())
: new CryptKey3(Options.Password)
);
}
}

View File

@@ -62,7 +62,7 @@ public partial class RarHeaderFactory
if (_isRar5 && _cryptInfo != null)
{
_cryptInfo.ReadInitV(new MarkingBinaryReader(stream));
var _headerKey = new CryptKey5(Options.Password.NotNull(), _cryptInfo);
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
reader = RarCryptoBinaryReader.Create(stream, _headerKey, _cryptInfo.Salt);
}

View File

@@ -57,47 +57,46 @@ internal class Rar5CryptoInfo
)
{
var cryptoInfo = new Rar5CryptoInfo();
var cryptVersion = await reader.ReadRarVIntUInt32Async(
cancellationToken: CancellationToken.None
);
var cryptVersion = await reader
.ReadRarVIntUInt32Async(cancellationToken: CancellationToken.None)
.ConfigureAwait(false);
if (cryptVersion > EncryptionConstV5.VERSION)
{
throw new CryptographicException($"Unsupported crypto version of {cryptVersion}");
}
var encryptionFlags = await reader.ReadRarVIntUInt32Async(
cancellationToken: CancellationToken.None
);
var encryptionFlags = await reader
.ReadRarVIntUInt32Async(cancellationToken: CancellationToken.None)
.ConfigureAwait(false);
cryptoInfo.UsePswCheck = FlagUtility.HasFlag(
encryptionFlags,
EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK
);
cryptoInfo.LG2Count = (int)
await reader.ReadRarVIntUInt32Async(cancellationToken: CancellationToken.None);
await reader
.ReadRarVIntUInt32Async(cancellationToken: CancellationToken.None)
.ConfigureAwait(false);
if (cryptoInfo.LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
{
throw new CryptographicException($"Unsupported LG2 count of {cryptoInfo.LG2Count}.");
}
cryptoInfo.Salt = await reader.ReadBytesAsync(
EncryptionConstV5.SIZE_SALT50,
CancellationToken.None
);
cryptoInfo.Salt = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_SALT50, CancellationToken.None)
.ConfigureAwait(false);
if (readInitV)
{
await cryptoInfo.ReadInitVAsync(reader);
await cryptoInfo.ReadInitVAsync(reader).ConfigureAwait(false);
}
if (cryptoInfo.UsePswCheck)
{
cryptoInfo.PswCheck = await reader.ReadBytesAsync(
EncryptionConstV5.SIZE_PSWCHECK,
CancellationToken.None
);
var _pswCheckCsm = await reader.ReadBytesAsync(
EncryptionConstV5.SIZE_PSWCHECK_CSUM,
CancellationToken.None
);
cryptoInfo.PswCheck = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_PSWCHECK, CancellationToken.None)
.ConfigureAwait(false);
var _pswCheckCsm = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_PSWCHECK_CSUM, CancellationToken.None)
.ConfigureAwait(false);
var sha = SHA256.Create();
cryptoInfo.UsePswCheck = sha.ComputeHash(cryptoInfo.PswCheck)
@@ -111,7 +110,9 @@ internal class Rar5CryptoInfo
InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV);
public async ValueTask ReadInitVAsync(AsyncMarkingBinaryReader reader) =>
InitV = await reader.ReadBytesAsync(EncryptionConstV5.SIZE_INITV, CancellationToken.None);
InitV = await reader
.ReadBytesAsync(EncryptionConstV5.SIZE_INITV, CancellationToken.None)
.ConfigureAwait(false);
public bool UsePswCheck = false;

View File

@@ -1,4 +1,5 @@
using System;
using SharpCompress.Common.Options;
using SharpCompress.Common.Rar.Headers;
namespace SharpCompress.Common.Rar;
@@ -7,6 +8,9 @@ public abstract class RarEntry : Entry
{
internal abstract FileHeader FileHeader { get; }
protected RarEntry(IReaderOptions readerOptions)
: base(readerOptions) { }
/// <summary>
/// As the V2017 port isn't complete, add this check to use the legacy Rar code.
/// </summary>

View File

@@ -118,7 +118,8 @@ public abstract class RarVolume : Volume
var buffer = new byte[fh.CompressedSize];
await fh
.PackedStream.NotNull()
.ReadFullyAsync(buffer, cancellationToken);
.ReadFullyAsync(buffer, cancellationToken)
.ConfigureAwait(false);
Comment = Encoding.UTF8.GetString(buffer, 0, buffer.Length - 1);
}
}
@@ -184,7 +185,7 @@ public abstract class RarVolume : Volume
public async ValueTask<bool> IsSolidArchiveAsync(CancellationToken cancellationToken = default)
{
await EnsureArchiveHeaderLoadedAsync(cancellationToken);
await EnsureArchiveHeaderLoadedAsync(cancellationToken).ConfigureAwait(false);
return ArchiveHeader?.IsSolid ?? false;
}
@@ -248,7 +249,7 @@ public abstract class RarVolume : Volume
}
// we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream
await GetVolumeFilePartsAsync(cancellationToken).FirstAsync();
await GetVolumeFilePartsAsync(cancellationToken).FirstAsync().ConfigureAwait(false);
Stream.Position = 0;
}
}

Some files were not shown because too many files have changed in this diff Show More