Merge remote-tracking branch 'origin/master' into adam/add-alternate-compressions

# Conflicts:
#	src/SharpCompress/Archives/GZip/GZipArchive.Async.cs
#	src/SharpCompress/Archives/GZip/GZipArchive.cs
#	src/SharpCompress/Archives/Zip/ZipArchive.Async.cs
#	src/SharpCompress/Archives/Zip/ZipArchive.cs
#	src/SharpCompress/Common/GZip/GZipEntry.Async.cs
#	src/SharpCompress/Common/GZip/GZipEntry.cs
#	src/SharpCompress/Common/Options/IReaderOptions.cs
#	src/SharpCompress/Readers/ReaderOptions.cs
#	src/SharpCompress/Readers/Zip/ZipReader.Async.cs
#	src/SharpCompress/Readers/Zip/ZipReader.cs
#	src/SharpCompress/Writers/GZip/GZipWriterOptions.cs
This commit is contained in:
Adam Hathcock 2026-02-10 15:21:13 +00:00
commit a9f2d3cf7f
219 changed files with 3874 additions and 1921 deletions

View file

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

View file

@ -0,0 +1,50 @@
name: Performance Benchmarks
on:
push:
branches:
- 'master'
- 'release'
pull_request:
branches:
- 'master'
- 'release'
workflow_dispatch:
permissions:
contents: read
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
- name: Build Performance Project
run: dotnet build tests/SharpCompress.Performance/SharpCompress.Performance.csproj --configuration Release
- name: Run Benchmarks
run: dotnet run --project tests/SharpCompress.Performance/SharpCompress.Performance.csproj --configuration Release --no-build -- --filter "*" --exporters json markdown --artifacts benchmark-results
continue-on-error: true
- name: Display Benchmark Results
if: always()
run: dotnet run --project build/build.csproj -- display-benchmark-results
- name: Compare with Baseline
if: always()
run: dotnet run --project build/build.csproj -- compare-benchmark-results
- name: Upload Benchmark Results
if: always()
uses: actions/upload-artifact@v6
with:
name: benchmark-results
path: benchmark-results/

4
.gitignore vendored
View file

@ -17,6 +17,10 @@ tests/TestArchives/*/Scratch2
tools
.idea/
artifacts/
BenchmarkDotNet.Artifacts/
baseline-artifacts/
profiler-snapshots/
.DS_Store
*.snupkg
benchmark-results/

View file

@ -1,5 +1,6 @@
<Project>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="Bullseye" Version="6.1.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
<PackageVersion Include="Glob" Version="1.1.9" />

View file

@ -19,6 +19,9 @@ const string Publish = "publish";
const string DetermineVersion = "determine-version";
const string UpdateVersion = "update-version";
const string PushToNuGet = "push-to-nuget";
const string DisplayBenchmarkResults = "display-benchmark-results";
const string CompareBenchmarkResults = "compare-benchmark-results";
const string GenerateBaseline = "generate-baseline";
Target(
Clean,
@ -210,6 +213,249 @@ Target(
}
);
Target(
DisplayBenchmarkResults,
() =>
{
var githubStepSummary = Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY");
var resultsDir = "benchmark-results/results";
if (!Directory.Exists(resultsDir))
{
Console.WriteLine("No benchmark results found.");
return;
}
var markdownFiles = Directory
.GetFiles(resultsDir, "*-report-github.md")
.OrderBy(f => f)
.ToList();
if (markdownFiles.Count == 0)
{
Console.WriteLine("No benchmark markdown reports found.");
return;
}
var output = new List<string> { "## Benchmark Results", "" };
foreach (var file in markdownFiles)
{
Console.WriteLine($"Processing {Path.GetFileName(file)}");
var content = File.ReadAllText(file);
output.Add(content);
output.Add("");
}
// Write to GitHub Step Summary if available
if (!string.IsNullOrEmpty(githubStepSummary))
{
File.AppendAllLines(githubStepSummary, output);
Console.WriteLine($"Benchmark results written to GitHub Step Summary");
}
else
{
// Write to console if not in GitHub Actions
foreach (var line in output)
{
Console.WriteLine(line);
}
}
}
);
Target(
CompareBenchmarkResults,
() =>
{
var githubStepSummary = Environment.GetEnvironmentVariable("GITHUB_STEP_SUMMARY");
var baselinePath = "tests/SharpCompress.Performance/baseline-results.md";
var resultsDir = "benchmark-results/results";
var output = new List<string> { "## Comparison with Baseline", "" };
if (!File.Exists(baselinePath))
{
Console.WriteLine("Baseline file not found");
output.Add("⚠️ Baseline file not found. Run `generate-baseline` to create it.");
WriteOutput(output, githubStepSummary);
return;
}
if (!Directory.Exists(resultsDir))
{
Console.WriteLine("No current benchmark results found.");
output.Add("⚠️ No current benchmark results found. Showing baseline only.");
output.Add("");
output.Add("### Baseline Results");
output.AddRange(File.ReadAllLines(baselinePath));
WriteOutput(output, githubStepSummary);
return;
}
var markdownFiles = Directory
.GetFiles(resultsDir, "*-report-github.md")
.OrderBy(f => f)
.ToList();
if (markdownFiles.Count == 0)
{
Console.WriteLine("No current benchmark markdown reports found.");
output.Add("⚠️ No current benchmark results found. Showing baseline only.");
output.Add("");
output.Add("### Baseline Results");
output.AddRange(File.ReadAllLines(baselinePath));
WriteOutput(output, githubStepSummary);
return;
}
Console.WriteLine("Parsing baseline results...");
var baselineMetrics = ParseBenchmarkResults(File.ReadAllText(baselinePath));
Console.WriteLine("Parsing current results...");
var currentText = string.Join("\n", markdownFiles.Select(f => File.ReadAllText(f)));
var currentMetrics = ParseBenchmarkResults(currentText);
Console.WriteLine("Comparing results...");
output.Add("### Performance Comparison");
output.Add("");
output.Add(
"| Benchmark | Baseline Mean | Current Mean | Change | Baseline Memory | Current Memory | Change |"
);
output.Add(
"|-----------|---------------|--------------|--------|-----------------|----------------|--------|"
);
var hasRegressions = false;
var hasImprovements = false;
foreach (var method in currentMetrics.Keys.Union(baselineMetrics.Keys).OrderBy(k => k))
{
var hasCurrent = currentMetrics.TryGetValue(method, out var current);
var hasBaseline = baselineMetrics.TryGetValue(method, out var baseline);
if (!hasCurrent)
{
output.Add(
$"| {method} | {baseline!.Mean} | ❌ Missing | N/A | {baseline.Memory} | N/A | N/A |"
);
continue;
}
if (!hasBaseline)
{
output.Add(
$"| {method} | ❌ New | {current!.Mean} | N/A | N/A | {current.Memory} | N/A |"
);
continue;
}
var timeChange = CalculateChange(baseline!.MeanValue, current!.MeanValue);
var memChange = CalculateChange(baseline.MemoryValue, current.MemoryValue);
var timeIcon =
timeChange > 25 ? "🔴"
: timeChange < -25 ? "🟢"
: "⚪";
var memIcon =
memChange > 25 ? "🔴"
: memChange < -25 ? "🟢"
: "⚪";
if (timeChange > 25 || memChange > 25)
hasRegressions = true;
if (timeChange < -25 || memChange < -25)
hasImprovements = true;
output.Add(
$"| {method} | {baseline.Mean} | {current.Mean} | {timeIcon} {timeChange:+0.0;-0.0;0}% | {baseline.Memory} | {current.Memory} | {memIcon} {memChange:+0.0;-0.0;0}% |"
);
}
output.Add("");
output.Add("**Legend:**");
output.Add("- 🔴 Regression (>25% slower/more memory)");
output.Add("- 🟢 Improvement (>25% faster/less memory)");
output.Add("- ⚪ No significant change");
if (hasRegressions)
{
output.Add("");
output.Add(
"⚠️ **Warning**: Performance regressions detected. Review the changes carefully."
);
}
else if (hasImprovements)
{
output.Add("");
output.Add("✅ Performance improvements detected!");
}
else
{
output.Add("");
output.Add("✅ Performance is stable compared to baseline.");
}
WriteOutput(output, githubStepSummary);
}
);
Target(
GenerateBaseline,
() =>
{
var perfProject = "tests/SharpCompress.Performance/SharpCompress.Performance.csproj";
var baselinePath = "tests/SharpCompress.Performance/baseline-results.md";
var artifactsDir = "baseline-artifacts";
Console.WriteLine("Building performance project...");
Run("dotnet", $"build {perfProject} --configuration Release");
Console.WriteLine("Running benchmarks to generate baseline...");
Run(
"dotnet",
$"run --project {perfProject} --configuration Release --no-build -- --filter \"*\" --exporters markdown --artifacts {artifactsDir}"
);
var resultsDir = Path.Combine(artifactsDir, "results");
if (!Directory.Exists(resultsDir))
{
Console.WriteLine("ERROR: No benchmark results generated.");
return;
}
var markdownFiles = Directory
.GetFiles(resultsDir, "*-report-github.md")
.OrderBy(f => f)
.ToList();
if (markdownFiles.Count == 0)
{
Console.WriteLine("ERROR: No markdown reports found.");
return;
}
Console.WriteLine($"Combining {markdownFiles.Count} benchmark reports...");
var baselineContent = new List<string>();
foreach (var file in markdownFiles)
{
var lines = File.ReadAllLines(file);
baselineContent.AddRange(lines.Select(l => l.Trim()).Where(l => l.StartsWith('|')));
}
File.WriteAllText(baselinePath, string.Join(Environment.NewLine, baselineContent));
Console.WriteLine($"Baseline written to {baselinePath}");
// Clean up artifacts directory
if (Directory.Exists(artifactsDir))
{
Directory.Delete(artifactsDir, true);
Console.WriteLine("Cleaned up artifacts directory.");
}
}
);
Target("default", [Publish], () => Console.WriteLine("Done!"));
await RunTargetsAndExitAsync(args);
@ -302,3 +548,142 @@ static async Task<string> GetGitOutput(string command, string args)
throw new Exception($"Git command failed: git {command} {args}\n{ex.Message}", ex);
}
}
static void WriteOutput(List<string> output, string? githubStepSummary)
{
if (!string.IsNullOrEmpty(githubStepSummary))
{
File.AppendAllLines(githubStepSummary, output);
Console.WriteLine("Comparison written to GitHub Step Summary");
}
else
{
foreach (var line in output)
{
Console.WriteLine(line);
}
}
}
static Dictionary<string, BenchmarkMetric> ParseBenchmarkResults(string markdown)
{
var metrics = new Dictionary<string, BenchmarkMetric>();
var lines = markdown.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Look for table rows with benchmark data
if (line.StartsWith("|") && line.Contains("&#39;") && i > 0)
{
var parts = line.Split('|', StringSplitOptions.TrimEntries);
if (parts.Length >= 5)
{
var method = parts[1].Replace("&#39;", "'");
var meanStr = parts[2];
// Find Allocated column - it's usually the last column or labeled "Allocated"
string memoryStr = "N/A";
for (int j = parts.Length - 2; j >= 2; j--)
{
if (
parts[j].Contains("KB")
|| parts[j].Contains("MB")
|| parts[j].Contains("GB")
|| parts[j].Contains("B")
)
{
memoryStr = parts[j];
break;
}
}
if (
!method.Equals("Method", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(method)
)
{
var metric = new BenchmarkMetric
{
Method = method,
Mean = meanStr,
MeanValue = ParseTimeValue(meanStr),
Memory = memoryStr,
MemoryValue = ParseMemoryValue(memoryStr),
};
metrics[method] = metric;
}
}
}
}
return metrics;
}
static double ParseTimeValue(string timeStr)
{
if (string.IsNullOrWhiteSpace(timeStr) || timeStr == "N/A" || timeStr == "NA")
return 0;
// Remove thousands separators and parse
timeStr = timeStr.Replace(",", "").Trim();
var match = Regex.Match(timeStr, @"([\d.]+)\s*(\w+)");
if (!match.Success)
return 0;
var value = double.Parse(match.Groups[1].Value);
var unit = match.Groups[2].Value.ToLower();
// Convert to microseconds for comparison
return unit switch
{
"s" => value * 1_000_000,
"ms" => value * 1_000,
"μs" or "us" => value,
"ns" => value / 1_000,
_ => value,
};
}
static double ParseMemoryValue(string memStr)
{
if (string.IsNullOrWhiteSpace(memStr) || memStr == "N/A" || memStr == "NA")
return 0;
memStr = memStr.Replace(",", "").Trim();
var match = Regex.Match(memStr, @"([\d.]+)\s*(\w+)");
if (!match.Success)
return 0;
var value = double.Parse(match.Groups[1].Value);
var unit = match.Groups[2].Value.ToUpper();
// Convert to KB for comparison
return unit switch
{
"GB" => value * 1_024 * 1_024,
"MB" => value * 1_024,
"KB" => value,
"B" => value / 1_024,
_ => value,
};
}
static double CalculateChange(double baseline, double current)
{
if (baseline == 0)
return 0;
return ((current - baseline) / baseline) * 100;
}
record BenchmarkMetric
{
public string Method { get; init; } = "";
public string Mean { get; init; } = "";
public double MeanValue { get; init; }
public string Memory { get; init; } = "";
public double MemoryValue { get; init; }
}

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())
@ -95,7 +99,6 @@ using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip"))
{
await asyncArchive.WriteToDirectoryAsync(
@"C:\output",
new ExtractionOptions { ExtractFullPath = true, Overwrite = true },
cancellationToken: cancellationToken
);
}
@ -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");
@ -367,7 +413,6 @@ try
{
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

@ -6,13 +6,13 @@ using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Writers;
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 =>
@ -39,7 +39,7 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
if (!removedEntries.Contains(entry))
{
removedEntries.Add(entry);
await RebuildModifiedCollectionAsync();
await RebuildModifiedCollectionAsync().ConfigureAwait(false);
}
}
@ -86,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;
}
@ -106,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,
IWriterOptions options,
TOptions options,
CancellationToken cancellationToken = default
)
{
@ -121,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

@ -11,18 +11,19 @@ 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;
@ -151,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)
{
@ -175,7 +178,7 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
return entry;
}
public void SaveTo(Stream stream, IWriterOptions options)
public void SaveTo(Stream stream, TOptions options)
{
//reset streams of new entries
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
@ -211,19 +214,11 @@ public abstract partial class AbstractWritableArchive<TEntry, TVolume>
protected abstract void SaveTo(
Stream stream,
IWriterOptions options,
TOptions options,
IEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries
);
protected abstract ValueTask SaveToAsync(
Stream stream,
IWriterOptions options,
IAsyncEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries,
CancellationToken cancellationToken = default
);
public override void Dispose()
{
base.Dispose();

View file

@ -19,8 +19,9 @@ public static partial class ArchiveFactory
CancellationToken cancellationToken = default
)
{
readerOptions ??= new ReaderOptions();
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken);
readerOptions ??= ReaderOptions.ForExternalStream;
var factory = await FindFactoryAsync<IArchiveFactory>(stream, cancellationToken)
.ConfigureAwait(false);
return factory.OpenAsyncArchive(stream, readerOptions);
}
@ -40,9 +41,10 @@ public static partial class ArchiveFactory
CancellationToken cancellationToken = default
)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
options ??= ReaderOptions.ForOwnedFile;
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken);
var factory = await FindFactoryAsync<IArchiveFactory>(fileInfo, cancellationToken)
.ConfigureAwait(false);
return factory.OpenAsyncArchive(fileInfo, options);
}
@ -62,14 +64,16 @@ 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 factory.OpenAsyncArchive(filesArray, options);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
@ -89,13 +93,15 @@ 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);
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken)
.ConfigureAwait(false);
return factory.OpenAsyncArchive(streamsArray, options);
}
@ -117,7 +123,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 +146,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

@ -5,6 +5,7 @@ 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;
@ -15,22 +16,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 +43,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 +67,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 +88,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 +96,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

@ -6,7 +6,6 @@ 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;
using SharpCompress.Writers;
@ -25,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,
IWriterOptions options,
GZipWriterOptions options,
IAsyncEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries,
CancellationToken cancellationToken = default
@ -41,7 +40,7 @@ public partial class GZipArchive
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
using var writer = new GZipWriter(
await using var writer = new GZipWriter(
stream,
options as GZipWriterOptions ?? new GZipWriterOptions(options)
);
@ -51,7 +50,9 @@ public partial class GZipArchive
{
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"),
@ -63,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);
@ -81,14 +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,
ReaderOptions.CompressionProviders
)
await GZipFilePart
.CreateAsync(stream, ReaderOptions.ArchiveEncoding)
.ConfigureAwait(false),
ReaderOptions
);
}
}

View file

@ -5,43 +5,41 @@ 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 IWritableAsyncArchive<GZipWriterOptions> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return (IWritableAsyncArchive)OpenArchive(
new FileInfo(path),
readerOptions ?? new ReaderOptions()
);
return (IWritableAsyncArchive<GZipWriterOptions>)
OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions());
}
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 +54,7 @@ public partial class GZipArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<GZipWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@ -72,7 +70,7 @@ public partial class GZipArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<GZipWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
@ -88,7 +86,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 +103,30 @@ public partial class GZipArchive
);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<GZipWriterOptions> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(stream, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(stream, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<GZipWriterOptions> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(fileInfo, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<GZipWriterOptions> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(streams, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<GZipWriterOptions>)OpenArchive(streams, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<GZipWriterOptions> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions);
}
ReaderOptions? readerOptions = null
) => (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 IWritableAsyncArchive<GZipWriterOptions> CreateAsyncArchive() =>
new GZipArchive();
public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath));

View file

@ -2,8 +2,6 @@ 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;
@ -15,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) { }
@ -34,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(
@ -59,7 +58,7 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
protected override void SaveTo(
Stream stream,
IWriterOptions options,
GZipWriterOptions options,
IEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries
)
@ -88,11 +87,8 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
var stream = volumes.Single().Stream;
yield return new GZipArchiveEntry(
this,
GZipFilePart.Create(
stream,
ReaderOptions.ArchiveEncoding,
ReaderOptions.CompressionProviders
)
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

@ -20,20 +20,17 @@ public interface IArchiveOpenable<TSync, TASync>
public static abstract TASync OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
);
public static abstract TASync OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
);
public static abstract TASync OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
);
}

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

@ -50,10 +50,8 @@ public interface IMultiArchiveFactory : IFactory
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions">reading options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncArchive OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
);
}

View file

@ -22,14 +22,12 @@ public interface IMultiArchiveOpenable<TSync, TASync>
public static abstract TASync OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
);
public static abstract TASync OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
);
}
#endif

View file

@ -28,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, IWriterOptions 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,
IWriterOptions 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>
@ -76,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

@ -58,17 +58,23 @@ public static class IWritableArchiveExtensions
fileInfo.LastWriteTime
);
}
}
public void SaveTo(string filePath, IWriterOptions? options = null) =>
writableArchive.SaveTo(
new FileInfo(filePath),
options ?? new WriterOptions(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, IWriterOptions? options = null)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
writableArchive.SaveTo(stream, options ?? new WriterOptions(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,13 @@
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 IWritableAsyncArchive<TOptions> CreateAsyncArchive();
}
#endif

View file

@ -25,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);
}
}
}
@ -60,32 +62,26 @@ public static class IWritableAsyncArchiveExtensions
fileInfo.LastWriteTime
);
}
}
public ValueTask SaveToAsync(
string filePath,
IWriterOptions? options = null,
CancellationToken cancellationToken = default
) =>
writableArchive.SaveToAsync(
new FileInfo(filePath),
options ?? new WriterOptions(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,
IWriterOptions? options = null,
CancellationToken cancellationToken = default
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
await writableArchive
.SaveToAsync(
stream,
options ?? new WriterOptions(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

@ -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

@ -22,11 +22,9 @@ public partial class RarArchive
{
public static IRarAsyncArchive OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty(nameof(path));
return (IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions);
}
@ -102,41 +100,33 @@ public partial class RarArchive
public static IRarAsyncArchive OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(stream, readerOptions);
}
public static IRarAsyncArchive OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(fileInfo, readerOptions);
}
public static IRarAsyncArchive OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(streams, readerOptions);
}
public static IRarAsyncArchive OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IRarAsyncArchive)OpenArchive(fileInfos, readerOptions);
}

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,13 +16,8 @@ public partial class SevenZipArchive
IMultiArchiveOpenable<IArchive, IAsyncArchive>
#endif
{
public static IAsyncArchive OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
public static IAsyncArchive OpenAsyncArchive(string path, ReaderOptions? readerOptions = null)
{
cancellationToken.ThrowIfCancellationRequested();
path.NotNullOrEmpty("path");
return (IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions());
}
@ -91,43 +86,32 @@ public partial class SevenZipArchive
);
}
public static IAsyncArchive OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
public static IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(stream, readerOptions);
}
public static IAsyncArchive OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(fileInfo, readerOptions);
}
public static IAsyncArchive OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(streams, readerOptions);
}
public static IAsyncArchive OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
ReaderOptions? readerOptions = null
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IAsyncArchive)OpenArchive(fileInfos, readerOptions);
}
@ -163,7 +147,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 (

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

@ -19,7 +19,7 @@ public partial class TarArchive
{
protected override async ValueTask SaveToAsync(
Stream stream,
IWriterOptions options,
TarWriterOptions options,
IAsyncEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries,
CancellationToken cancellationToken = default
@ -96,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;
@ -127,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;
@ -135,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();
@ -151,7 +152,8 @@ public partial class TarArchive
yield return new TarArchiveEntry(
this,
new TarFilePart(header, stream),
CompressionType.None
CompressionType.None,
ReaderOptions
);
}
}

View file

@ -9,22 +9,29 @@ using SharpCompress.Common;
using SharpCompress.Common.Tar.Headers;
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
)
@ -39,7 +46,7 @@ public partial class TarArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<TarWriterOptions> OpenArchive(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
@ -55,7 +62,7 @@ public partial class TarArchive
);
}
public static IWritableArchive OpenArchive(
public static IWritableArchive<TarWriterOptions> OpenArchive(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
@ -71,7 +78,10 @@ public partial class TarArchive
);
}
public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
public static IWritableArchive<TarWriterOptions> OpenArchive(
Stream stream,
ReaderOptions? readerOptions = null
)
{
stream.NotNull(nameof(stream));
@ -85,55 +95,30 @@ public partial class TarArchive
);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<TarWriterOptions> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(stream, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<TarWriterOptions>)OpenArchive(stream, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<TarWriterOptions> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(new FileInfo(path), readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<TarWriterOptions>)OpenArchive(new FileInfo(path), readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<TarWriterOptions> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<TarWriterOptions>)OpenArchive(fileInfo, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<TarWriterOptions> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(streams, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<TarWriterOptions>)OpenArchive(streams, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<TarWriterOptions> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<TarWriterOptions>)OpenArchive(fileInfos, readerOptions);
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
@ -181,7 +166,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 +181,7 @@ 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 IWritableAsyncArchive<TarWriterOptions> CreateAsyncArchive() => new TarArchive();
}

View file

@ -16,7 +16,8 @@ using SharpCompress.Writers.Tar;
namespace SharpCompress.Archives.Tar;
public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
public partial class TarArchive
: AbstractWritableArchive<TarArchiveEntry, TarVolume, TarWriterOptions>
{
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream sourceStream)
{
@ -59,7 +60,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;
@ -81,7 +83,8 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
yield return new TarArchiveEntry(
this,
new TarFilePart(header, stream),
CompressionType.None
CompressionType.None,
ReaderOptions
);
}
}
@ -116,7 +119,7 @@ public partial class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVo
protected override void SaveTo(
Stream stream,
IWriterOptions options,
TarWriterOptions options,
IEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries
)

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

@ -21,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 (
@ -55,12 +55,8 @@ public partial class ZipArchive
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(
headerFactory.NotNull(),
deh,
s,
ReaderOptions.CompressionProviders
)
new SeekableZipFilePart(headerFactory.NotNull(), deh, s),
ReaderOptions
);
}
break;
@ -77,7 +73,7 @@ public partial class ZipArchive
protected override async ValueTask SaveToAsync(
Stream stream,
IWriterOptions options,
ZipWriterOptions options,
IAsyncEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries,
CancellationToken cancellationToken = default

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,55 +95,30 @@ public partial class ZipArchive
);
}
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<ZipWriterOptions> OpenAsyncArchive(
string path,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(path, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(path, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<ZipWriterOptions> OpenAsyncArchive(
Stream stream,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(stream, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(stream, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<ZipWriterOptions> OpenAsyncArchive(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(fileInfo, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<ZipWriterOptions> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(streams, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(streams, readerOptions);
public static IWritableAsyncArchive OpenAsyncArchive(
public static IWritableAsyncArchive<ZipWriterOptions> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions);
}
ReaderOptions? readerOptions = null
) => (IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(fileInfos, readerOptions);
public static bool IsZipFile(string filePath, string? password = null) =>
IsZipFile(new FileInfo(filePath), password);
@ -218,7 +203,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 +221,9 @@ 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 IWritableAsyncArchive<ZipWriterOptions> CreateAsyncArchive() => new ZipArchive();
public static async ValueTask<bool> IsZipMultiAsync(
Stream stream,
@ -261,6 +247,7 @@ public partial class ZipArchive
await foreach (
var h in z.ReadSeekableHeaderAsync(stream)
.WithCancellation(cancellationToken)
.ConfigureAwait(false)
)
{
x = h;

View file

@ -17,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;
@ -95,12 +96,8 @@ public partial class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVo
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(
headerFactory.NotNull(),
deh,
s,
ReaderOptions.CompressionProviders
)
new SeekableZipFilePart(headerFactory.NotNull(), deh, s),
ReaderOptions
);
}
break;
@ -115,11 +112,12 @@ 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,
IWriterOptions options,
ZipWriterOptions options,
IEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries
)

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,36 +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?.SymbolicLinkHandler is not null)
if (entry.Options.SymbolicLinkHandler is not null)
{
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
entry.Options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
else
{
ExtractionOptions.DefaultSymbolicLinkHandler(destinationFileName, entry.LinkTarget);
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,34 +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?.SymbolicLinkHandler is not null)
if (entry.Options.SymbolicLinkHandler is not null)
{
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
entry.Options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
}
else
{
ExtractionOptions.DefaultSymbolicLinkHandler(destinationFileName, entry.LinkTarget);
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,108 +0,0 @@
using System;
namespace SharpCompress.Common;
/// <summary>
/// Options for configuring extraction behavior when extracting archive entries.
/// </summary>
/// <remarks>
/// This class is immutable. Use the <c>with</c> expression to create modified copies:
/// <code>
/// var options = new ExtractionOptions { Overwrite = false };
/// options = options with { PreserveFileTime = true };
/// </code>
/// </remarks>
public sealed record ExtractionOptions
{
/// <summary>
/// Overwrite target if it exists.
/// <para><b>Breaking change:</b> Default changed from false to true in version 0.40.0.</para>
/// </summary>
public bool Overwrite { get; init; } = true;
/// <summary>
/// Extract with internal directory structure.
/// <para><b>Breaking change:</b> Default changed from false to true in version 0.40.0.</para>
/// </summary>
public bool ExtractFullPath { get; init; } = true;
/// <summary>
/// Preserve file time.
/// <para><b>Breaking change:</b> Default changed from false to true in version 0.40.0.</para>
/// </summary>
public bool PreserveFileTime { get; init; } = true;
/// <summary>
/// Preserve windows file attributes.
/// </summary>
public 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>
/// <remarks>
/// <b>Breaking change:</b> Changed from field to init-only property in version 0.40.0.
/// The default handler logs a warning message.
/// </remarks>
public Action<string, string>? SymbolicLinkHandler { get; init; }
/// <summary>
/// Creates a new ExtractionOptions instance with default values.
/// </summary>
public ExtractionOptions() { }
/// <summary>
/// Creates a new ExtractionOptions instance with the specified overwrite behavior.
/// </summary>
/// <param name="overwrite">Whether to overwrite existing files.</param>
public ExtractionOptions(bool overwrite)
{
Overwrite = overwrite;
}
/// <summary>
/// Creates a new ExtractionOptions instance with the specified extraction path and overwrite behavior.
/// </summary>
/// <param name="extractFullPath">Whether to preserve directory structure.</param>
/// <param name="overwrite">Whether to overwrite existing files.</param>
public ExtractionOptions(bool extractFullPath, bool overwrite)
{
ExtractFullPath = extractFullPath;
Overwrite = overwrite;
}
/// <summary>
/// Creates a new ExtractionOptions instance with the specified extraction path, overwrite behavior, and file time preservation.
/// </summary>
/// <param name="extractFullPath">Whether to preserve directory structure.</param>
/// <param name="overwrite">Whether to overwrite existing files.</param>
/// <param name="preserveFileTime">Whether to preserve file modification times.</param>
public ExtractionOptions(bool extractFullPath, bool overwrite, bool preserveFileTime)
{
ExtractFullPath = extractFullPath;
Overwrite = overwrite;
PreserveFileTime = preserveFileTime;
}
/// <summary>
/// Gets an ExtractionOptions instance configured for safe extraction (no overwrite).
/// </summary>
public static ExtractionOptions SafeExtract => new(overwrite: false);
/// <summary>
/// Gets an ExtractionOptions instance configured for flat extraction (no directory structure).
/// </summary>
public static ExtractionOptions FlatExtract => new(extractFullPath: false, overwrite: true);
/// <summary>
/// Default symbolic link handler that logs a warning message.
/// </summary>
public static void DefaultSymbolicLinkHandler(string sourcePath, string targetPath)
{
Console.WriteLine(
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
);
}
}

View file

@ -12,11 +12,8 @@ public partial class GZipEntry
)
{
yield return new GZipEntry(
await GZipFilePart.CreateAsync(
stream,
options.ArchiveEncoding,
options.CompressionProviders
)
await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding).ConfigureAwait(false),
options
);
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Options;
using SharpCompress.Readers;
namespace SharpCompress.Common.GZip;
@ -9,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;
@ -41,9 +46,7 @@ public partial class GZipEntry : Entry
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, ReaderOptions options)
{
yield return new GZipEntry(
GZipFilePart.Create(stream, options.ArchiveEncoding, options.CompressionProviders)
);
yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding), options);
}
// Async methods moved to GZipEntry.Async.cs

View file

@ -30,12 +30,12 @@ internal sealed partial class GZipFilePart
{
var part = new GZipFilePart(stream, archiveEncoding, compressionProviders);
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;
}
@ -52,7 +52,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));
@ -64,7 +64,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)
@ -88,28 +88,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
}
}
@ -124,7 +125,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

@ -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,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,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

@ -2,10 +2,11 @@ using SharpCompress.Compressors;
namespace SharpCompress.Common.Options;
/// <summary>
/// Options for configuring reader behavior when opening archives.
/// </summary>
public interface IReaderOptions : IStreamOptions, IEncodingOptions, IProgressOptions
public interface IReaderOptions
: IStreamOptions,
IEncodingOptions,
IProgressOptions,
IExtractionOptions
{
/// <summary>
/// Look for RarArchive (Check for self-extracting archives or cases where RarArchive isn't at the start of the file)

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

@ -27,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

@ -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));
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);
}
}

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;
}
}

View file

@ -29,7 +29,7 @@ internal sealed partial class ArchiveReader
{
// TODO: Check Signature!
_header = new byte[0x20];
await stream.ReadExactAsync(_header, 0, 0x20, cancellationToken);
await stream.ReadExactAsync(_header, 0, 0x20, cancellationToken).ConfigureAwait(false);
if (
!lookForHeader
@ -107,7 +107,9 @@ internal sealed partial class ArchiveReader
_stream.Seek(nextHeaderOffset, SeekOrigin.Current);
var header = new byte[nextHeaderSize];
await _stream.ReadExactAsync(header, 0, header.Length, cancellationToken);
await _stream
.ReadExactAsync(header, 0, header.Length, cancellationToken)
.ConfigureAwait(false);
if (Crc.Finish(Crc.Update(Crc.INIT_CRC, header, 0, header.Length)) != nextHeaderCrc)
{

View file

@ -1,11 +1,16 @@
using System;
using System;
using System.Collections.Generic;
using SharpCompress.Common.Options;
namespace SharpCompress.Common.SevenZip;
public class SevenZipEntry : Entry
{
internal SevenZipEntry(SevenZipFilePart filePart) => FilePart = filePart;
internal SevenZipEntry(SevenZipFilePart filePart, IReaderOptions readerOptions)
: base(readerOptions)
{
FilePart = filePart;
}
internal SevenZipFilePart FilePart { get; }

View file

@ -68,12 +68,9 @@ internal class SevenZipFilePart : FilePart
{
return Stream.Null;
}
var folderStream = await _database.GetFolderStreamAsync(
_stream,
Folder!,
_database.PasswordProvider,
cancellationToken
);
var folderStream = await _database
.GetFolderStreamAsync(_stream, Folder!, _database.PasswordProvider, cancellationToken)
.ConfigureAwait(false);
var firstFileIndex = _database._folderStartFileIndex[_database._folders.IndexOf(Folder!)];
var skipCount = Index - firstFileIndex;
@ -84,7 +81,7 @@ internal class SevenZipFilePart : FilePart
}
if (skipSize > 0)
{
await folderStream.SkipAsync(skipSize, cancellationToken);
await folderStream.SkipAsync(skipSize, cancellationToken).ConfigureAwait(false);
}
return new ReadOnlySubStream(folderStream, Header.Size, leaveOpen: false);
}

View file

@ -20,10 +20,10 @@ internal sealed partial class TarHeader
switch (WriteFormat)
{
case TarHeaderWriteFormat.GNU_TAR_LONG_LINK:
await WriteGnuTarLongLinkAsync(output, cancellationToken);
await WriteGnuTarLongLinkAsync(output, cancellationToken).ConfigureAwait(false);
break;
case TarHeaderWriteFormat.USTAR:
await WriteUstarAsync(output, cancellationToken);
await WriteUstarAsync(output, cancellationToken).ConfigureAwait(false);
break;
default:
throw new Exception("This should be impossible...");
@ -162,13 +162,13 @@ internal sealed partial class TarHeader
if (nameByteCount > 100)
{
await WriteLongFilenameHeaderAsync(output, cancellationToken);
await WriteLongFilenameHeaderAsync(output, cancellationToken).ConfigureAwait(false);
Name = ArchiveEncoding.Decode(
ArchiveEncoding.Encode(Name.NotNull("Name is null")),
0,
100 - ArchiveEncoding.GetEncoding().GetMaxByteCount(1)
);
await WriteGnuTarLongLinkAsync(output, cancellationToken);
await WriteGnuTarLongLinkAsync(output, cancellationToken).ConfigureAwait(false);
}
}
@ -203,7 +203,7 @@ internal sealed partial class TarHeader
do
{
buffer = await ReadBlockAsync(reader);
buffer = await ReadBlockAsync(reader).ConfigureAwait(false);
if (buffer.Length == 0)
{
@ -216,12 +216,12 @@ internal sealed partial class TarHeader
// to apply to the header that follows them.
if (entryType == EntryType.LongName)
{
longName = await ReadLongNameAsync(reader, buffer);
longName = await ReadLongNameAsync(reader, buffer).ConfigureAwait(false);
continue;
}
else if (entryType == EntryType.LongLink)
{
longLinkName = await ReadLongNameAsync(reader, buffer);
longLinkName = await ReadLongNameAsync(reader, buffer).ConfigureAwait(false);
continue;
}
@ -282,7 +282,7 @@ internal sealed partial class TarHeader
var buffer = ArrayPool<byte>.Shared.Rent(BLOCK_SIZE);
try
{
await reader.ReadBytesAsync(buffer, 0, BLOCK_SIZE);
await reader.ReadBytesAsync(buffer, 0, BLOCK_SIZE).ConfigureAwait(false);
if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE)
{
@ -313,7 +313,7 @@ internal sealed partial class TarHeader
var nameBytes = ArrayPool<byte>.Shared.Rent(nameLength);
try
{
await reader.ReadBytesAsync(nameBytes, 0, nameLength);
await reader.ReadBytesAsync(nameBytes, 0, nameLength).ConfigureAwait(false);
var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE);
// Read the rest of the block and discard the data
@ -322,7 +322,9 @@ internal sealed partial class TarHeader
var remainingBytes = ArrayPool<byte>.Shared.Rent(remainingBytesToRead);
try
{
await reader.ReadBytesAsync(remainingBytes, 0, remainingBytesToRead);
await reader
.ReadBytesAsync(remainingBytes, 0, remainingBytesToRead)
.ConfigureAwait(false);
}
finally
{

View file

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Options;
using SharpCompress.IO;
namespace SharpCompress.Common.Tar;
@ -10,7 +11,8 @@ public partial class TarEntry
StreamingMode mode,
Stream stream,
CompressionType compressionType,
IArchiveEncoding archiveEncoding
IArchiveEncoding archiveEncoding,
IReaderOptions readerOptions
)
{
await foreach (
@ -21,11 +23,19 @@ public partial class TarEntry
{
if (mode == StreamingMode.Seekable)
{
yield return new TarEntry(new TarFilePart(header, stream), compressionType);
yield return new TarEntry(
new TarFilePart(header, stream),
compressionType,
readerOptions
);
}
else
{
yield return new TarEntry(new TarFilePart(header, null), compressionType);
yield return new TarEntry(
new TarFilePart(header, null),
compressionType,
readerOptions
);
}
}
else

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Common.Options;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.IO;
@ -10,7 +11,8 @@ public partial class TarEntry : Entry
{
private readonly TarFilePart? _filePart;
internal TarEntry(TarFilePart? filePart, CompressionType type)
internal TarEntry(TarFilePart? filePart, CompressionType type, IReaderOptions readerOptions)
: base(readerOptions)
{
_filePart = filePart;
CompressionType = type;
@ -54,7 +56,8 @@ public partial class TarEntry : Entry
StreamingMode mode,
Stream stream,
CompressionType compressionType,
IArchiveEncoding archiveEncoding
IArchiveEncoding archiveEncoding,
IReaderOptions readerOptions
)
{
foreach (var header in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding))
@ -63,11 +66,19 @@ public partial class TarEntry : Entry
{
if (mode == StreamingMode.Seekable)
{
yield return new TarEntry(new TarFilePart(header, stream), compressionType);
yield return new TarEntry(
new TarFilePart(header, stream),
compressionType,
readerOptions
);
}
else
{
yield return new TarEntry(new TarFilePart(header, null), compressionType);
yield return new TarEntry(
new TarFilePart(header, null),
compressionType,
readerOptions
);
}
}
else

View file

@ -25,7 +25,7 @@ internal static partial class TarHeaderFactory
try
{
header = new TarHeader(archiveEncoding);
if (!await header.ReadAsync(reader))
if (!await header.ReadAsync(reader).ConfigureAwait(false))
{
yield break;
}

View file

@ -8,14 +8,14 @@ internal partial class DirectoryEndHeader
{
internal override async ValueTask Read(AsyncBinaryReader reader)
{
VolumeNumber = await reader.ReadUInt16Async();
FirstVolumeWithDirectory = await reader.ReadUInt16Async();
TotalNumberOfEntriesInDisk = await reader.ReadUInt16Async();
TotalNumberOfEntries = await reader.ReadUInt16Async();
DirectorySize = await reader.ReadUInt32Async();
DirectoryStartOffsetRelativeToDisk = await reader.ReadUInt32Async();
CommentLength = await reader.ReadUInt16Async();
VolumeNumber = await reader.ReadUInt16Async().ConfigureAwait(false);
FirstVolumeWithDirectory = await reader.ReadUInt16Async().ConfigureAwait(false);
TotalNumberOfEntriesInDisk = await reader.ReadUInt16Async().ConfigureAwait(false);
TotalNumberOfEntries = await reader.ReadUInt16Async().ConfigureAwait(false);
DirectorySize = await reader.ReadUInt32Async().ConfigureAwait(false);
DirectoryStartOffsetRelativeToDisk = await reader.ReadUInt32Async().ConfigureAwait(false);
CommentLength = await reader.ReadUInt16Async().ConfigureAwait(false);
Comment = new byte[CommentLength];
await reader.ReadBytesAsync(Comment, 0, CommentLength);
await reader.ReadBytesAsync(Comment, 0, CommentLength).ConfigureAwait(false);
}
}

View file

@ -10,28 +10,33 @@ internal partial class DirectoryEntryHeader
{
internal override async ValueTask Read(AsyncBinaryReader reader)
{
Version = await reader.ReadUInt16Async();
VersionNeededToExtract = await reader.ReadUInt16Async();
Flags = (HeaderFlags)await reader.ReadUInt16Async();
CompressionMethod = (ZipCompressionMethod)await reader.ReadUInt16Async();
OriginalLastModifiedTime = LastModifiedTime = await reader.ReadUInt16Async();
OriginalLastModifiedDate = LastModifiedDate = await reader.ReadUInt16Async();
Crc = await reader.ReadUInt32Async();
CompressedSize = await reader.ReadUInt32Async();
UncompressedSize = await reader.ReadUInt32Async();
var nameLength = await reader.ReadUInt16Async();
var extraLength = await reader.ReadUInt16Async();
var commentLength = await reader.ReadUInt16Async();
DiskNumberStart = await reader.ReadUInt16Async();
InternalFileAttributes = await reader.ReadUInt16Async();
ExternalFileAttributes = await reader.ReadUInt32Async();
RelativeOffsetOfEntryHeader = await reader.ReadUInt32Async();
Version = await reader.ReadUInt16Async().ConfigureAwait(false);
VersionNeededToExtract = await reader.ReadUInt16Async().ConfigureAwait(false);
Flags = (HeaderFlags)await reader.ReadUInt16Async().ConfigureAwait(false);
CompressionMethod = (ZipCompressionMethod)
await reader.ReadUInt16Async().ConfigureAwait(false);
OriginalLastModifiedTime = LastModifiedTime = await reader
.ReadUInt16Async()
.ConfigureAwait(false);
OriginalLastModifiedDate = LastModifiedDate = await reader
.ReadUInt16Async()
.ConfigureAwait(false);
Crc = await reader.ReadUInt32Async().ConfigureAwait(false);
CompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
UncompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
var nameLength = await reader.ReadUInt16Async().ConfigureAwait(false);
var extraLength = await reader.ReadUInt16Async().ConfigureAwait(false);
var commentLength = await reader.ReadUInt16Async().ConfigureAwait(false);
DiskNumberStart = await reader.ReadUInt16Async().ConfigureAwait(false);
InternalFileAttributes = await reader.ReadUInt16Async().ConfigureAwait(false);
ExternalFileAttributes = await reader.ReadUInt32Async().ConfigureAwait(false);
RelativeOffsetOfEntryHeader = await reader.ReadUInt32Async().ConfigureAwait(false);
var name = new byte[nameLength];
var extra = new byte[extraLength];
var comment = new byte[commentLength];
await reader.ReadBytesAsync(name, 0, nameLength);
await reader.ReadBytesAsync(extra, 0, extraLength);
await reader.ReadBytesAsync(comment, 0, commentLength);
await reader.ReadBytesAsync(name, 0, nameLength).ConfigureAwait(false);
await reader.ReadBytesAsync(extra, 0, extraLength).ConfigureAwait(false);
await reader.ReadBytesAsync(comment, 0, commentLength).ConfigureAwait(false);
ProcessReadData(name, extra, comment);
}

View file

@ -9,20 +9,25 @@ internal partial class LocalEntryHeader
{
internal override async ValueTask Read(AsyncBinaryReader reader)
{
Version = await reader.ReadUInt16Async();
Flags = (HeaderFlags)await reader.ReadUInt16Async();
CompressionMethod = (ZipCompressionMethod)await reader.ReadUInt16Async();
OriginalLastModifiedTime = LastModifiedTime = await reader.ReadUInt16Async();
OriginalLastModifiedDate = LastModifiedDate = await reader.ReadUInt16Async();
Crc = await reader.ReadUInt32Async();
CompressedSize = await reader.ReadUInt32Async();
UncompressedSize = await reader.ReadUInt32Async();
var nameLength = await reader.ReadUInt16Async();
var extraLength = await reader.ReadUInt16Async();
Version = await reader.ReadUInt16Async().ConfigureAwait(false);
Flags = (HeaderFlags)await reader.ReadUInt16Async().ConfigureAwait(false);
CompressionMethod = (ZipCompressionMethod)
await reader.ReadUInt16Async().ConfigureAwait(false);
OriginalLastModifiedTime = LastModifiedTime = await reader
.ReadUInt16Async()
.ConfigureAwait(false);
OriginalLastModifiedDate = LastModifiedDate = await reader
.ReadUInt16Async()
.ConfigureAwait(false);
Crc = await reader.ReadUInt32Async().ConfigureAwait(false);
CompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
UncompressedSize = await reader.ReadUInt32Async().ConfigureAwait(false);
var nameLength = await reader.ReadUInt16Async().ConfigureAwait(false);
var extraLength = await reader.ReadUInt16Async().ConfigureAwait(false);
var name = new byte[nameLength];
var extra = new byte[extraLength];
await reader.ReadBytesAsync(name, 0, nameLength);
await reader.ReadBytesAsync(extra, 0, extraLength);
await reader.ReadBytesAsync(name, 0, nameLength).ConfigureAwait(false);
await reader.ReadBytesAsync(extra, 0, extraLength).ConfigureAwait(false);
ProcessReadData(name, extra);
}

View file

@ -8,19 +8,20 @@ internal partial class Zip64DirectoryEndHeader
{
internal override async ValueTask Read(AsyncBinaryReader reader)
{
SizeOfDirectoryEndRecord = (long)await reader.ReadUInt64Async();
VersionMadeBy = await reader.ReadUInt16Async();
VersionNeededToExtract = await reader.ReadUInt16Async();
VolumeNumber = await reader.ReadUInt32Async();
FirstVolumeWithDirectory = await reader.ReadUInt32Async();
TotalNumberOfEntriesInDisk = (long)await reader.ReadUInt64Async();
TotalNumberOfEntries = (long)await reader.ReadUInt64Async();
DirectorySize = (long)await reader.ReadUInt64Async();
DirectoryStartOffsetRelativeToDisk = (long)await reader.ReadUInt64Async();
SizeOfDirectoryEndRecord = (long)await reader.ReadUInt64Async().ConfigureAwait(false);
VersionMadeBy = await reader.ReadUInt16Async().ConfigureAwait(false);
VersionNeededToExtract = await reader.ReadUInt16Async().ConfigureAwait(false);
VolumeNumber = await reader.ReadUInt32Async().ConfigureAwait(false);
FirstVolumeWithDirectory = await reader.ReadUInt32Async().ConfigureAwait(false);
TotalNumberOfEntriesInDisk = (long)await reader.ReadUInt64Async().ConfigureAwait(false);
TotalNumberOfEntries = (long)await reader.ReadUInt64Async().ConfigureAwait(false);
DirectorySize = (long)await reader.ReadUInt64Async().ConfigureAwait(false);
DirectoryStartOffsetRelativeToDisk = (long)
await reader.ReadUInt64Async().ConfigureAwait(false);
var size = (int)(
SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS
);
DataSector = new byte[size];
await reader.ReadBytesAsync(DataSector, 0, size);
await reader.ReadBytesAsync(DataSector, 0, size).ConfigureAwait(false);
}
}

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