diff --git a/docs/API.md b/docs/API.md index eb8f007a7..d2a944ad6 100644 --- a/docs/API.md +++ b/docs/API.md @@ -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(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 ); } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 2a3bb985e..9db75bbcb 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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 Entries { get; } - void WriteToDirectory(string destinationDirectory, - ExtractionOptions options = null); + void WriteToDirectory(string destinationDirectory); IEntry FirstOrDefault(Func 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(); diff --git a/docs/ENCODING.md b/docs/ENCODING.md index 8200e756f..5ae0de096 100644 --- a/docs/ENCODING.md +++ b/docs/ENCODING.md @@ -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 ``` diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index 51b2f9206..604fd2cf4 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -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 ); } diff --git a/docs/USAGE.md b/docs/USAGE.md index 52d690a32..df863d329 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -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(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 ); } diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 989eca70c..7d89067fa 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -20,7 +20,7 @@ public abstract partial class AbstractArchive : IArchive, IAsyn private readonly LazyAsyncReadOnlyCollection _lazyVolumesAsync; private readonly LazyAsyncReadOnlyCollection _lazyEntriesAsync; - protected ReaderOptions ReaderOptions { get; } + public ReaderOptions ReaderOptions { get; protected set; } internal AbstractArchive(ArchiveType type, SourceStream sourceStream) { diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.Async.cs b/src/SharpCompress/Archives/AbstractWritableArchive.Async.cs index 80f3f2092..f48b53440 100644 --- a/src/SharpCompress/Archives/AbstractWritableArchive.Async.cs +++ b/src/SharpCompress/Archives/AbstractWritableArchive.Async.cs @@ -5,13 +5,14 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; -using SharpCompress.Writers; +using SharpCompress.Common.Options; namespace SharpCompress.Archives; -public abstract partial class AbstractWritableArchive +public abstract partial class AbstractWritableArchive where TEntry : IArchiveEntry where TVolume : IVolume + where TOptions : IWriterOptions { // Async property moved from main file private IAsyncEnumerable OldEntriesAsync => @@ -111,7 +112,7 @@ public async ValueTask AddDirectoryEntryAsync( public async ValueTask SaveToAsync( Stream stream, - WriterOptions options, + TOptions options, CancellationToken cancellationToken = default ) { @@ -120,4 +121,12 @@ public async ValueTask SaveToAsync( await SaveToAsync(stream, options, OldEntriesAsync, newEntries, cancellationToken) .ConfigureAwait(false); } + + protected abstract ValueTask SaveToAsync( + Stream stream, + TOptions options, + IAsyncEnumerable oldEntries, + IEnumerable newEntries, + CancellationToken cancellationToken = default + ); } diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.cs b/src/SharpCompress/Archives/AbstractWritableArchive.cs index 8a1090eee..5a32ef0ee 100644 --- a/src/SharpCompress/Archives/AbstractWritableArchive.cs +++ b/src/SharpCompress/Archives/AbstractWritableArchive.cs @@ -5,23 +5,25 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.IO; using SharpCompress.Writers; namespace SharpCompress.Archives; -public abstract partial class AbstractWritableArchive +public abstract partial class AbstractWritableArchive : AbstractArchive, - IWritableArchive, - IWritableAsyncArchive + IWritableArchive, + IWritableAsyncArchive where TEntry : IArchiveEntry where TVolume : IVolume + where TOptions : IWriterOptions { private class RebuildPauseDisposable : IDisposable { - private readonly AbstractWritableArchive archive; + private readonly AbstractWritableArchive archive; - public RebuildPauseDisposable(AbstractWritableArchive archive) + public RebuildPauseDisposable(AbstractWritableArchive archive) { this.archive = archive; archive.pauseRebuilding = true; @@ -174,7 +176,7 @@ public TEntry AddDirectoryEntry(string key, DateTime? modified = null) return entry; } - public void SaveTo(Stream stream, WriterOptions options) + public void SaveTo(Stream stream, TOptions options) { //reset streams of new entries newEntries.Cast().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin)); @@ -210,19 +212,11 @@ bool closeStream protected abstract void SaveTo( Stream stream, - WriterOptions options, + TOptions options, IEnumerable oldEntries, IEnumerable newEntries ); - protected abstract ValueTask SaveToAsync( - Stream stream, - WriterOptions options, - IAsyncEnumerable oldEntries, - IEnumerable newEntries, - CancellationToken cancellationToken = default - ); - public override void Dispose() { base.Dispose(); diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs index 59e16c31d..c6a69d111 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -19,7 +19,7 @@ public static async ValueTask OpenAsyncArchive( CancellationToken cancellationToken = default ) { - readerOptions ??= new ReaderOptions(); + readerOptions ??= ReaderOptions.ForExternalStream; var factory = await FindFactoryAsync(stream, cancellationToken); return factory.OpenAsyncArchive(stream, readerOptions); } @@ -40,7 +40,7 @@ public static async ValueTask OpenAsyncArchive( CancellationToken cancellationToken = default ) { - options ??= new ReaderOptions { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForOwnedFile; var factory = await FindFactoryAsync(fileInfo, cancellationToken); return factory.OpenAsyncArchive(fileInfo, options); @@ -66,7 +66,7 @@ public static async ValueTask OpenAsyncArchive( } fileInfo.NotNull(nameof(fileInfo)); - options ??= new ReaderOptions { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForOwnedFile; var factory = await FindFactoryAsync(fileInfo, cancellationToken); return factory.OpenAsyncArchive(filesArray, options, cancellationToken); @@ -93,7 +93,7 @@ public static async ValueTask OpenAsyncArchive( } firstStream.NotNull(nameof(firstStream)); - options ??= new ReaderOptions(); + options ??= ReaderOptions.ForExternalStream; var factory = await FindFactoryAsync(firstStream, cancellationToken); return factory.OpenAsyncArchive(streamsArray, options); diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index 33a1ecc58..4da070b5f 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -5,6 +5,7 @@ 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(stream).OpenArchive(stream, readerOptions); } - public static IWritableArchive CreateArchive(ArchiveType type) + public static IWritableArchive CreateArchive() + where TOptions : IWriterOptions { var factory = Factory - .Factories.OfType() - .FirstOrDefault(item => item.KnownArchiveType == type); + .Factories.OfType>() + .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 IArchive OpenArchive(string filePath, ReaderOptions? options = nul public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null) { - options ??= new ReaderOptions { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForOwnedFile; return FindFactory(fileInfo).OpenArchive(fileInfo, options); } @@ -65,7 +67,7 @@ public static IArchive OpenArchive( } fileInfo.NotNull(nameof(fileInfo)); - options ??= new ReaderOptions { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForOwnedFile; return FindFactory(fileInfo).OpenArchive(filesArray, options); } @@ -86,7 +88,7 @@ public static IArchive OpenArchive(IEnumerable streams, ReaderOptions? o } firstStream.NotNull(nameof(firstStream)); - options ??= new ReaderOptions(); + options ??= ReaderOptions.ForExternalStream; return FindFactory(firstStream).OpenArchive(streamsArray, options); } @@ -94,11 +96,11 @@ public static IArchive OpenArchive(IEnumerable streams, ReaderOptions? o 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(string path) diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs index a087e1569..82b468d8d 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Async.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Common.GZip; -using SharpCompress.IO; +using SharpCompress.Common.Options; using SharpCompress.Readers; using SharpCompress.Readers.GZip; using SharpCompress.Writers; @@ -24,13 +24,13 @@ public async ValueTask SaveToAsync( ) { using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - await SaveToAsync(stream, new WriterOptions(CompressionType.GZip), cancellationToken) + await SaveToAsync(stream, new GZipWriterOptions(CompressionType.GZip), cancellationToken) .ConfigureAwait(false); } protected override async ValueTask SaveToAsync( Stream stream, - WriterOptions options, + GZipWriterOptions options, IAsyncEnumerable oldEntries, IEnumerable newEntries, CancellationToken cancellationToken = default @@ -40,14 +40,17 @@ protected override async ValueTask SaveToAsync( { throw new InvalidFormatException("Only one entry is allowed in a GZip Archive"); } - using var writer = new GZipWriter(stream, new GZipWriterOptions(options)); + await using var writer = new GZipWriter( + stream, + options as GZipWriterOptions ?? new GZipWriterOptions(options) + ); await foreach ( var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false) ) { if (!entry.IsDirectory) { - using var entryStream = entry.OpenEntryStream(); + using var entryStream = await entry.OpenEntryStreamAsync(cancellationToken); await writer .WriteAsync( entry.Key.NotNull("Entry Key is null"), @@ -59,7 +62,7 @@ await writer } foreach (var entry in newEntries.Where(x => !x.IsDirectory)) { - using var entryStream = entry.OpenEntryStream(); + using var entryStream = await entry.OpenEntryStreamAsync(cancellationToken); await writer .WriteAsync(entry.Key.NotNull("Entry Key is null"), entryStream, cancellationToken) .ConfigureAwait(false); @@ -80,7 +83,8 @@ IAsyncEnumerable volumes var stream = (await volumes.SingleAsync()).Stream; yield return new GZipArchiveEntry( this, - await GZipFilePart.CreateAsync(stream, ReaderOptions.ArchiveEncoding) + await GZipFilePart.CreateAsync(stream, ReaderOptions.ArchiveEncoding), + ReaderOptions ); } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs index e793cd9b7..ab6dc7869 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs @@ -5,23 +5,22 @@ 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 + : IWritableArchiveOpenable, + IMultiArchiveOpenable< + IWritableArchive, + IWritableAsyncArchive + > #endif { - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( string path, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -29,19 +28,20 @@ public static IWritableAsyncArchive OpenAsyncArchive( { cancellationToken.ThrowIfCancellationRequested(); path.NotNullOrEmpty(nameof(path)); - return (IWritableAsyncArchive)OpenArchive( - new FileInfo(path), - readerOptions ?? new ReaderOptions() - ); + return (IWritableAsyncArchive) + OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions()); } - public static IWritableArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null) + public static IWritableArchive 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 OpenArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null ) @@ -56,7 +56,7 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( IEnumerable fileInfos, ReaderOptions? readerOptions = null ) @@ -72,7 +72,7 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( IEnumerable streams, ReaderOptions? readerOptions = null ) @@ -88,7 +88,10 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + public static IWritableArchive OpenArchive( + Stream stream, + ReaderOptions? readerOptions = null + ) { stream.NotNull(nameof(stream)); @@ -102,49 +105,50 @@ public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerO ); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); } - public static IWritableArchive CreateArchive() => new GZipArchive(); + public static IWritableArchive CreateArchive() => new GZipArchive(); - public static IWritableAsyncArchive CreateAsyncArchive() => new GZipArchive(); + public static IWritableAsyncArchive CreateAsyncArchive() => + new GZipArchive(); public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index a7dfb427b..56f61fa59 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Common.GZip; +using SharpCompress.Common.Options; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.GZip; @@ -14,7 +13,8 @@ namespace SharpCompress.Archives.GZip; -public partial class GZipArchive : AbstractWritableArchive +public partial class GZipArchive + : AbstractWritableArchive { private GZipArchive(SourceStream sourceStream) : base(ArchiveType.GZip, sourceStream) { } @@ -33,7 +33,7 @@ protected override IEnumerable LoadVolumes(SourceStream sourceStream public void SaveTo(FileInfo fileInfo) { using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - SaveTo(stream, new WriterOptions(CompressionType.GZip)); + SaveTo(stream, new GZipWriterOptions(CompressionType.GZip)); } protected override GZipArchiveEntry CreateEntryInternal( @@ -58,7 +58,7 @@ protected override GZipArchiveEntry CreateDirectoryEntry( protected override void SaveTo( Stream stream, - WriterOptions options, + GZipWriterOptions options, IEnumerable oldEntries, IEnumerable newEntries ) @@ -67,7 +67,10 @@ IEnumerable newEntries { throw new InvalidFormatException("Only one entry is allowed in a GZip Archive"); } - using var writer = new GZipWriter(stream, new GZipWriterOptions(options)); + using var writer = new GZipWriter( + stream, + options as GZipWriterOptions ?? new GZipWriterOptions(options) + ); foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory)) { using var entryStream = entry.OpenEntryStream(); @@ -84,7 +87,8 @@ protected override IEnumerable LoadEntries(IEnumerable Archive = archive; + internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part, IReaderOptions readerOptions) + : base(part, readerOptions) => Archive = archive; public virtual Stream OpenEntryStream() { diff --git a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs index 729ad529d..8171b8724 100644 --- a/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipWritableArchiveEntry.cs @@ -19,7 +19,7 @@ internal GZipWritableArchiveEntry( DateTime? lastModified, bool closeStream ) - : base(archive, null) + : base(archive, null, archive.ReaderOptions) { this.stream = stream; Key = path; diff --git a/src/SharpCompress/Archives/IArchive.cs b/src/SharpCompress/Archives/IArchive.cs index 6016214b7..725480730 100644 --- a/src/SharpCompress/Archives/IArchive.cs +++ b/src/SharpCompress/Archives/IArchive.cs @@ -12,6 +12,11 @@ public interface IArchive : IDisposable ArchiveType Type { get; } + /// + /// The options used when opening this archive, including extraction behavior settings. + /// + ReaderOptions ReaderOptions { get; } + /// /// 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 diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index be352cdb7..3a5bc5612 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -100,15 +100,11 @@ private static Stream WrapWithProgress( /// /// Extract to specific directory, retaining filename /// - public void WriteToDirectory( - string destinationDirectory, - ExtractionOptions? options = null - ) => + public void WriteToDirectory(string destinationDirectory) => ExtractionMethods.WriteEntryToDirectory( entry, destinationDirectory, - options, - entry.WriteToFile + (path) => entry.WriteToFile(path) ); /// @@ -116,15 +112,14 @@ public void WriteToDirectory( /// 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 +127,10 @@ await ExtractionMethods /// /// Extract to specific file /// - 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 +143,12 @@ public void WriteToFile(string destinationFileName, ExtractionOptions? options = /// 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); diff --git a/src/SharpCompress/Archives/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs index 80857a25c..a5bfd0b76 100644 --- a/src/SharpCompress/Archives/IArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveExtensions.cs @@ -14,28 +14,25 @@ public static class IArchiveExtensions /// Extract to specific directory with progress reporting /// /// The folder to extract into. - /// Extraction options. /// Optional progress reporter for tracking extraction progress. public void WriteToDirectory( string destinationDirectory, - ExtractionOptions? options = null, IProgress? 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? progress ) { @@ -61,7 +58,7 @@ private void WriteToDirectoryInternal( continue; } - entry.WriteToDirectory(destinationDirectory, options); + entry.WriteToDirectory(destinationDirectory); bytesRead += entry.Size; progress?.Report( diff --git a/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs b/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs index 7a930bb07..dece366d6 100644 --- a/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs @@ -17,12 +17,10 @@ public static class IAsyncArchiveExtensions /// /// The archive to extract. /// The folder to extract into. - /// Extraction options. /// Optional progress reporter for tracking extraction progress. /// Optional cancellation token. public async ValueTask WriteToDirectoryAsync( string destinationDirectory, - ExtractionOptions? options = null, IProgress? progress = null, CancellationToken cancellationToken = default ) @@ -30,17 +28,14 @@ public async ValueTask WriteToDirectoryAsync( if (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip) { await using var reader = await archive.ExtractAllEntriesAsync(); - await reader.WriteAllToDirectoryAsync( - destinationDirectory, - options, - cancellationToken - ); + await reader + .WriteAllToDirectoryAsync(destinationDirectory, cancellationToken) + .ConfigureAwait(false); } else { await archive.WriteToDirectoryAsyncInternal( destinationDirectory, - options, progress, cancellationToken ); @@ -49,7 +44,6 @@ await archive.WriteToDirectoryAsyncInternal( private async ValueTask WriteToDirectoryAsyncInternal( string destinationDirectory, - ExtractionOptions? options, IProgress? progress, CancellationToken cancellationToken ) @@ -79,7 +73,7 @@ CancellationToken cancellationToken } await entry - .WriteToDirectoryAsync(destinationDirectory, options, cancellationToken) + .WriteToDirectoryAsync(destinationDirectory, cancellationToken) .ConfigureAwait(false); bytesRead += entry.Size; diff --git a/src/SharpCompress/Archives/IWritableArchive.cs b/src/SharpCompress/Archives/IWritableArchive.cs index 1451069f8..0d17a9377 100644 --- a/src/SharpCompress/Archives/IWritableArchive.cs +++ b/src/SharpCompress/Archives/IWritableArchive.cs @@ -2,6 +2,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using SharpCompress.Common.Options; using SharpCompress.Writers; namespace SharpCompress.Archives; @@ -27,28 +28,23 @@ IArchiveEntry AddEntry( IArchiveEntry AddDirectoryEntry(string key, DateTime? modified = null); - /// - /// Saves the archive to the specified stream using the given writer options. - /// - void SaveTo(Stream stream, WriterOptions options); - /// /// Removes the specified entry from the archive. /// void RemoveEntry(IArchiveEntry entry); } -public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon +public interface IWritableArchive : IWritableArchive + where TOptions : IWriterOptions { /// - /// 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. /// - ValueTask SaveToAsync( - Stream stream, - WriterOptions options, - CancellationToken cancellationToken = default - ); + void SaveTo(Stream stream, TOptions options); +} +public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon +{ /// /// Asynchronously adds an entry to the archive with the specified key, source stream, and options. /// @@ -75,3 +71,16 @@ ValueTask AddDirectoryEntryAsync( /// ValueTask RemoveEntryAsync(IArchiveEntry entry); } + +public interface IWritableAsyncArchive : IWritableAsyncArchive + where TOptions : IWriterOptions +{ + /// + /// Asynchronously saves the archive to the specified stream using the given writer options. + /// + ValueTask SaveToAsync( + Stream stream, + TOptions options, + CancellationToken cancellationToken = default + ); +} diff --git a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs index f383ec870..6012dda53 100644 --- a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs @@ -1,6 +1,7 @@ using System; using System.IO; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Writers; namespace SharpCompress.Archives; @@ -57,14 +58,23 @@ public IArchiveEntry AddEntry(string key, FileInfo fileInfo) fileInfo.LastWriteTime ); } + } - public void SaveTo(string filePath, WriterOptions? options = null) => - writableArchive.SaveTo(new FileInfo(filePath), options ?? new(CompressionType.Deflate)); + public static void SaveTo( + this IWritableArchive writableArchive, + string filePath, + TOptions options + ) + where TOptions : IWriterOptions => writableArchive.SaveTo(new FileInfo(filePath), options); - public void SaveTo(FileInfo fileInfo, WriterOptions? options = null) - { - using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - writableArchive.SaveTo(stream, options ?? new(CompressionType.Deflate)); - } + public static void SaveTo( + this IWritableArchive writableArchive, + FileInfo fileInfo, + TOptions options + ) + where TOptions : IWriterOptions + { + using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); + writableArchive.SaveTo(stream, options); } } diff --git a/src/SharpCompress/Archives/IWritableArchiveOpenable.cs b/src/SharpCompress/Archives/IWritableArchiveOpenable.cs index b0b761a2f..f523e1d25 100644 --- a/src/SharpCompress/Archives/IWritableArchiveOpenable.cs +++ b/src/SharpCompress/Archives/IWritableArchiveOpenable.cs @@ -1,10 +1,13 @@ +using SharpCompress.Common.Options; + #if NET8_0_OR_GREATER namespace SharpCompress.Archives; -public interface IWritableArchiveOpenable - : IArchiveOpenable +public interface IWritableArchiveOpenable + : IArchiveOpenable, IWritableAsyncArchive> + where TOptions : IWriterOptions { - public static abstract IWritableArchive CreateArchive(); - public static abstract IWritableAsyncArchive CreateAsyncArchive(); + public static abstract IWritableArchive CreateArchive(); + public static abstract IWritableAsyncArchive CreateAsyncArchive(); } #endif diff --git a/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs index 933fdc8f9..365cf8529 100644 --- a/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Writers; namespace SharpCompress.Archives; @@ -59,28 +60,26 @@ public ValueTask AddEntryAsync(string key, FileInfo fileInfo) fileInfo.LastWriteTime ); } + } - public ValueTask SaveToAsync( - string filePath, - WriterOptions? options = null, - CancellationToken cancellationToken = default - ) => - writableArchive.SaveToAsync( - new FileInfo(filePath), - options ?? new(CompressionType.Deflate), - cancellationToken - ); + public static ValueTask SaveToAsync( + this IWritableAsyncArchive writableArchive, + string filePath, + TOptions options, + CancellationToken cancellationToken = default + ) + where TOptions : IWriterOptions => + writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken); - public async ValueTask SaveToAsync( - FileInfo fileInfo, - WriterOptions? options = null, - CancellationToken cancellationToken = default - ) - { - using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - await writableArchive - .SaveToAsync(stream, options ?? new(CompressionType.Deflate), cancellationToken) - .ConfigureAwait(false); - } + public static async ValueTask SaveToAsync( + this IWritableAsyncArchive 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); } } diff --git a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs index f1de642d0..40a01fac9 100644 --- a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs +++ b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs @@ -1,3 +1,5 @@ +using SharpCompress.Common.Options; + namespace SharpCompress.Archives; /// @@ -10,11 +12,12 @@ namespace SharpCompress.Archives; /// /// /// -public interface IWriteableArchiveFactory : Factories.IFactory +public interface IWriteableArchiveFactory : Factories.IFactory + where TOptions : IWriterOptions { /// /// Creates a new, empty archive, ready to be written. /// /// - IWritableArchive CreateArchive(); + IWritableArchive CreateArchive(); } diff --git a/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs b/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs index f0754f67a..788d1f393 100644 --- a/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs +++ b/src/SharpCompress/Archives/Rar/FileInfoRarArchiveVolume.cs @@ -24,8 +24,7 @@ internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int private static ReaderOptions FixOptions(ReaderOptions options) { //make sure we're closing streams with fileinfo - options.LeaveStreamOpen = false; - return options; + return options with { LeaveStreamOpen = false }; } internal ReadOnlyCollection FileParts { get; } diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index 3d1a74d2e..d147264ea 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -23,6 +23,7 @@ internal RarArchiveEntry( IEnumerable parts, ReaderOptions readerOptions ) + : base(readerOptions) { this.parts = parts.ToList(); this.archive = archive; diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Async.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Async.cs index f860b62c0..fb430ece8 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Async.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Async.cs @@ -49,7 +49,8 @@ IAsyncEnumerable volumes 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)) diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index e9b27d6ad..a3c65a851 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -55,7 +55,8 @@ IEnumerable volumes i, file, ReaderOptions.ArchiveEncoding - ) + ), + ReaderOptions ); } foreach ( diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs index 2248090e1..9d81d04f9 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs @@ -1,14 +1,19 @@ -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(); diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Async.cs b/src/SharpCompress/Archives/Tar/TarArchive.Async.cs index b83036e2f..24a548a8f 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Async.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Async.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Tar; using SharpCompress.Common.Tar.Headers; using SharpCompress.IO; @@ -18,13 +19,16 @@ public partial class TarArchive { protected override async ValueTask SaveToAsync( Stream stream, - WriterOptions options, + TarWriterOptions options, IAsyncEnumerable oldEntries, IEnumerable newEntries, CancellationToken cancellationToken = default ) { - using var writer = new TarWriter(stream, new TarWriterOptions(options)); + using var writer = new TarWriter( + stream, + options as TarWriterOptions ?? new TarWriterOptions(options) + ); await foreach ( var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false) ) @@ -123,7 +127,8 @@ var header in TarHeaderFactory.ReadHeaderAsync( var entry = new TarArchiveEntry( this, new TarFilePart(previousHeader, stream), - CompressionType.None + CompressionType.None, + ReaderOptions ); var oldStreamPos = stream.Position; @@ -147,7 +152,8 @@ var header in TarHeaderFactory.ReadHeaderAsync( yield return new TarArchiveEntry( this, new TarFilePart(header, stream), - CompressionType.None + CompressionType.None, + ReaderOptions ); } } diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs index 959b4ab44..f3faac499 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -9,22 +9,29 @@ 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 + : IWritableArchiveOpenable, + IMultiArchiveOpenable< + IWritableArchive, + IWritableAsyncArchive + > #endif { - public static IWritableArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null) + public static IWritableArchive OpenArchive( + string filePath, + ReaderOptions? readerOptions = null + ) { filePath.NotNullOrEmpty(nameof(filePath)); return OpenArchive(new FileInfo(filePath), readerOptions); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null ) @@ -39,7 +46,7 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( IEnumerable fileInfos, ReaderOptions? readerOptions = null ) @@ -55,7 +62,7 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( IEnumerable streams, ReaderOptions? readerOptions = null ) @@ -71,7 +78,10 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + public static IWritableArchive OpenArchive( + Stream stream, + ReaderOptions? readerOptions = null + ) { stream.NotNull(nameof(stream)); @@ -85,54 +95,55 @@ public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerO ); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( string path, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(new FileInfo(path), readerOptions); + return (IWritableAsyncArchive) + OpenArchive(new FileInfo(path), readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); } public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); @@ -196,7 +207,7 @@ public static async ValueTask IsTarFileAsync( } } - public static IWritableArchive CreateArchive() => new TarArchive(); + public static IWritableArchive CreateArchive() => new TarArchive(); - public static IWritableAsyncArchive CreateAsyncArchive() => new TarArchive(); + public static IWritableAsyncArchive CreateAsyncArchive() => new TarArchive(); } diff --git a/src/SharpCompress/Archives/Tar/TarArchive.cs b/src/SharpCompress/Archives/Tar/TarArchive.cs index f174f34d6..70dc93d2b 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Tar; using SharpCompress.Common.Tar.Headers; using SharpCompress.IO; @@ -15,7 +16,8 @@ namespace SharpCompress.Archives.Tar; -public partial class TarArchive : AbstractWritableArchive +public partial class TarArchive + : AbstractWritableArchive { protected override IEnumerable LoadVolumes(SourceStream sourceStream) { @@ -58,7 +60,8 @@ var header in TarHeaderFactory.ReadHeader( var entry = new TarArchiveEntry( this, new TarFilePart(previousHeader, stream), - CompressionType.None + CompressionType.None, + ReaderOptions ); var oldStreamPos = stream.Position; @@ -80,7 +83,8 @@ var header in TarHeaderFactory.ReadHeader( yield return new TarArchiveEntry( this, new TarFilePart(header, stream), - CompressionType.None + CompressionType.None, + ReaderOptions ); } } @@ -115,12 +119,15 @@ protected override TarArchiveEntry CreateDirectoryEntry( protected override void SaveTo( Stream stream, - WriterOptions options, + TarWriterOptions options, IEnumerable oldEntries, IEnumerable newEntries ) { - using var writer = new TarWriter(stream, new TarWriterOptions(options)); + using var writer = new TarWriter( + stream, + options as TarWriterOptions ?? new TarWriterOptions(options) + ); foreach (var entry in oldEntries.Concat(newEntries)) { if (entry.IsDirectory) diff --git a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs index 921452e9e..23471d389 100644 --- a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs @@ -1,16 +1,22 @@ -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(); diff --git a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs index 99254369c..84f8ff44f 100644 --- a/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarWritableArchiveEntry.cs @@ -21,7 +21,7 @@ internal TarWritableArchiveEntry( DateTime? lastModified, bool closeStream ) - : base(archive, null, compressionType) + : base(archive, null, compressionType, archive.ReaderOptions) { this.stream = stream; Key = path; @@ -36,7 +36,7 @@ internal TarWritableArchiveEntry( string directoryPath, DateTime? lastModified ) - : base(archive, null, CompressionType.None) + : base(archive, null, CompressionType.None, archive.ReaderOptions) { stream = null; Key = directoryPath; diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs index 4cb3991ee..431a4afdd 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Async.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; using SharpCompress.IO; @@ -54,7 +55,8 @@ var h in headerFactory.NotNull().ReadSeekableHeaderAsync(volsArray.Last().Stream yield return new ZipArchiveEntry( this, - new SeekableZipFilePart(headerFactory.NotNull(), deh, s) + new SeekableZipFilePart(headerFactory.NotNull(), deh, s), + ReaderOptions ); } break; @@ -71,13 +73,16 @@ var h in headerFactory.NotNull().ReadSeekableHeaderAsync(volsArray.Last().Stream protected override async ValueTask SaveToAsync( Stream stream, - WriterOptions options, + ZipWriterOptions options, IAsyncEnumerable oldEntries, IEnumerable newEntries, CancellationToken cancellationToken = default ) { - using var writer = new ZipWriter(stream, new ZipWriterOptions(options)); + using var writer = new ZipWriter( + stream, + options as ZipWriterOptions ?? new ZipWriterOptions(options) + ); await foreach ( var entry in oldEntries.WithCancellation(cancellationToken).ConfigureAwait(false) ) diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs index 4904decb9..56f383cc2 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs @@ -9,22 +9,29 @@ 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 + : IWritableArchiveOpenable, + IMultiArchiveOpenable< + IWritableArchive, + IWritableAsyncArchive + > #endif { - public static IWritableArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null) + public static IWritableArchive OpenArchive( + string filePath, + ReaderOptions? readerOptions = null + ) { filePath.NotNullOrEmpty(nameof(filePath)); return OpenArchive(new FileInfo(filePath), readerOptions); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null ) @@ -39,7 +46,7 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( IEnumerable fileInfos, ReaderOptions? readerOptions = null ) @@ -55,7 +62,7 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive( + public static IWritableArchive OpenArchive( IEnumerable streams, ReaderOptions? readerOptions = null ) @@ -71,7 +78,10 @@ public static IWritableArchive OpenArchive( ); } - public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + public static IWritableArchive OpenArchive( + Stream stream, + ReaderOptions? readerOptions = null + ) { stream.NotNull(nameof(stream)); @@ -85,54 +95,54 @@ public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerO ); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( string path, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(path, readerOptions); + return (IWritableAsyncArchive)OpenArchive(path, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( IReadOnlyList streams, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); } - public static IWritableAsyncArchive OpenAsyncArchive( + public static IWritableAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); } public static bool IsZipFile(string filePath, string? password = null) => @@ -235,9 +245,9 @@ public static async ValueTask IsZipFileAsync( } } - public static IWritableArchive CreateArchive() => new ZipArchive(); + public static IWritableArchive CreateArchive() => new ZipArchive(); - public static IWritableAsyncArchive CreateAsyncArchive() => new ZipArchive(); + public static IWritableAsyncArchive CreateAsyncArchive() => new ZipArchive(); public static async ValueTask IsZipMultiAsync( Stream stream, diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 0777f1e61..8fc423bd1 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; using SharpCompress.Compressors.Deflate; @@ -16,7 +17,8 @@ namespace SharpCompress.Archives.Zip; -public partial class ZipArchive : AbstractWritableArchive +public partial class ZipArchive + : AbstractWritableArchive { private readonly SeekableZipHeaderFactory? headerFactory; @@ -94,7 +96,8 @@ protected override IEnumerable LoadEntries(IEnumerable LoadEntries(IEnumerable SaveTo(stream, new WriterOptions(CompressionType.Deflate)); + public void SaveTo(Stream stream) => + SaveTo(stream, new ZipWriterOptions(CompressionType.Deflate)); protected override void SaveTo( Stream stream, - WriterOptions options, + ZipWriterOptions options, IEnumerable oldEntries, IEnumerable newEntries ) { - using var writer = new ZipWriter(stream, new ZipWriterOptions(options)); + using var writer = new ZipWriter( + stream, + options as ZipWriterOptions ?? new ZipWriterOptions(options) + ); foreach (var entry in oldEntries.Concat(newEntries)) { if (entry.IsDirectory) diff --git a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs index bf3e67ac2..b4f9e8bd6 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchiveEntry.cs @@ -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(); diff --git a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs index 168f3a4fd..b74fea049 100644 --- a/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs +++ b/src/SharpCompress/Archives/Zip/ZipWritableArchiveEntry.cs @@ -21,7 +21,7 @@ internal ZipWritableArchiveEntry( DateTime? lastModified, bool closeStream ) - : base(archive, null) + : base(archive, null, archive.ReaderOptions) { this.stream = stream; Key = path; @@ -36,7 +36,7 @@ internal ZipWritableArchiveEntry( string directoryPath, DateTime? lastModified ) - : base(archive, null) + : base(archive, null, archive.ReaderOptions) { stream = null; Key = directoryPath; diff --git a/src/SharpCompress/Common/Ace/AceEntry.cs b/src/SharpCompress/Common/Ace/AceEntry.cs index 5aa94fb4d..419329dec 100644 --- a/src/SharpCompress/Common/Ace/AceEntry.cs +++ b/src/SharpCompress/Common/Ace/AceEntry.cs @@ -5,6 +5,7 @@ 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; } diff --git a/src/SharpCompress/Common/Arc/ArcEntry.cs b/src/SharpCompress/Common/Arc/ArcEntry.cs index 0a94ae0c8..cb7b262cc 100644 --- a/src/SharpCompress/Common/Arc/ArcEntry.cs +++ b/src/SharpCompress/Common/Arc/ArcEntry.cs @@ -5,6 +5,7 @@ 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; } diff --git a/src/SharpCompress/Common/Arj/ArjEntry.cs b/src/SharpCompress/Common/Arj/ArjEntry.cs index fe4a525b4..cf5e1c9f9 100644 --- a/src/SharpCompress/Common/Arj/ArjEntry.cs +++ b/src/SharpCompress/Common/Arj/ArjEntry.cs @@ -5,6 +5,7 @@ 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 override CompressionType CompressionType 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; diff --git a/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs index 4ace325de..d37121a9e 100644 --- a/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs +++ b/src/SharpCompress/Common/Arj/Headers/ArjLocalHeader.cs @@ -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 @@ long ReadInt32() 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(); } } diff --git a/src/SharpCompress/Common/Entry.cs b/src/SharpCompress/Common/Entry.cs index 6209b3de5..1942ba46b 100644 --- a/src/SharpCompress/Common/Entry.cs +++ b/src/SharpCompress/Common/Entry.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SharpCompress.Common.Options; namespace SharpCompress.Common; @@ -87,4 +88,14 @@ internal virtual void Close() { } /// Entry file attribute. /// public virtual int? Attrib => throw new NotImplementedException(); + + /// + /// The options used when opening this entry's source (reader or archive). + /// + public IReaderOptions Options { get; protected set; } + + protected Entry(IReaderOptions readerOptions) + { + Options = readerOptions; + } } diff --git a/src/SharpCompress/Common/ExtractionMethods.Async.cs b/src/SharpCompress/Common/ExtractionMethods.Async.cs index b56538773..75fede3d2 100644 --- a/src/SharpCompress/Common/ExtractionMethods.Async.cs +++ b/src/SharpCompress/Common/ExtractionMethods.Async.cs @@ -2,6 +2,7 @@ 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 writeAsync, + Func writeAsync, CancellationToken cancellationToken = default ) { @@ -34,11 +34,9 @@ public static async ValueTask WriteEntryToDirectoryAsync( ); } - 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 @@ public static async ValueTask WriteEntryToDirectoryAsync( "Entry is trying to write a file outside of the destination directory." ); } - await writeAsync(destinationFileName, options, cancellationToken).ConfigureAwait(false); + await writeAsync(destinationFileName, cancellationToken).ConfigureAwait(false); } - else if (options.ExtractFullPath && !Directory.Exists(destinationFileName)) + else if (entry.Options.ExtractFullPath && !Directory.Exists(destinationFileName)) { Directory.CreateDirectory(destinationFileName); } @@ -83,34 +81,34 @@ public static async ValueTask WriteEntryToDirectoryAsync( public static async ValueTask WriteEntryToFileAsync( IEntry entry, string destinationFileName, - ExtractionOptions? options, Func openAndWriteAsync, CancellationToken cancellationToken = default ) { if (entry.LinkTarget != null) { - if (options?.WriteSymbolicLink is null) + if (entry.Options.SymbolicLinkHandler is not null) { - throw new ExtractionException( - "Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null" - ); + entry.Options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget); + } + else + { + ReaderOptions.DefaultSymbolicLinkHandler(destinationFileName, entry.LinkTarget); } - options.WriteSymbolicLink(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); } } } diff --git a/src/SharpCompress/Common/ExtractionMethods.cs b/src/SharpCompress/Common/ExtractionMethods.cs index db6719f7a..6c6ced80e 100644 --- a/src/SharpCompress/Common/ExtractionMethods.cs +++ b/src/SharpCompress/Common/ExtractionMethods.cs @@ -3,6 +3,7 @@ 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 write + Action write ) { string destinationFileName; @@ -46,11 +46,9 @@ public static void WriteEntryToDirectory( ); } - 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 @@ public static void WriteEntryToDirectory( "Entry is trying to write a file outside of the destination directory." ); } - write(destinationFileName, options); + write(destinationFileName); } - else if (options.ExtractFullPath && !Directory.Exists(destinationFileName)) + else if (entry.Options.ExtractFullPath && !Directory.Exists(destinationFileName)) { Directory.CreateDirectory(destinationFileName); } @@ -95,32 +93,32 @@ public static void WriteEntryToDirectory( public static void WriteEntryToFile( IEntry entry, string destinationFileName, - ExtractionOptions? options, Action openAndWrite ) { if (entry.LinkTarget != null) { - if (options?.WriteSymbolicLink is null) + if (entry.Options.SymbolicLinkHandler is not null) { - throw new ExtractionException( - "Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null" - ); + entry.Options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget); + } + else + { + ReaderOptions.DefaultSymbolicLinkHandler(destinationFileName, entry.LinkTarget); } - options.WriteSymbolicLink(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); } } } diff --git a/src/SharpCompress/Common/ExtractionOptions.cs b/src/SharpCompress/Common/ExtractionOptions.cs deleted file mode 100644 index 18c0f0ac9..000000000 --- a/src/SharpCompress/Common/ExtractionOptions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace SharpCompress.Common; - -public class ExtractionOptions -{ - /// - /// overwrite target if it exists - /// - public bool Overwrite { get; set; } - - /// - /// extract with internal directory structure - /// - public bool ExtractFullPath { get; set; } - - /// - /// preserve file time - /// - public bool PreserveFileTime { get; set; } - - /// - /// preserve windows file attributes - /// - public bool PreserveAttributes { get; set; } - - /// - /// Delegate for writing symbolic links to disk. - /// sourcePath is where the symlink is created. - /// targetPath is what the symlink refers to. - /// - public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath); - - public SymbolicLinkWriterDelegate WriteSymbolicLink = (sourcePath, targetPath) => - { - Console.WriteLine( - $"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271" - ); - }; -} diff --git a/src/SharpCompress/Common/GZip/GZipEntry.Async.cs b/src/SharpCompress/Common/GZip/GZipEntry.Async.cs index 6f304f947..dbfade4ab 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.Async.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.Async.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using SharpCompress.Readers; namespace SharpCompress.Common.GZip; @@ -7,9 +8,12 @@ public partial class GZipEntry { internal static async IAsyncEnumerable GetEntriesAsync( Stream stream, - OptionsBase options + ReaderOptions options ) { - yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding)); + yield return new GZipEntry( + await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding), + options + ); } } diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index 12d0d6e6e..f3d41c455 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -1,6 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.IO; +using SharpCompress.Common.Options; +using SharpCompress.Readers; namespace SharpCompress.Common.GZip; @@ -8,7 +10,11 @@ public partial class GZipEntry : Entry { private readonly GZipFilePart? _filePart; - internal GZipEntry(GZipFilePart? filePart) => _filePart = filePart; + internal GZipEntry(GZipFilePart? filePart, IReaderOptions readerOptions) + : base(readerOptions) + { + _filePart = filePart; + } public override CompressionType CompressionType => CompressionType.GZip; @@ -38,9 +44,9 @@ public partial class GZipEntry : Entry internal override IEnumerable Parts => _filePart.Empty(); - internal static IEnumerable GetEntries(Stream stream, OptionsBase options) + internal static IEnumerable GetEntries(Stream stream, ReaderOptions options) { - yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding)); + yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding), options); } // Async methods moved to GZipEntry.Async.cs diff --git a/src/SharpCompress/Common/GZip/GZipVolume.cs b/src/SharpCompress/Common/GZip/GZipVolume.cs index 600ba8e39..cb821acff 100644 --- a/src/SharpCompress/Common/GZip/GZipVolume.cs +++ b/src/SharpCompress/Common/GZip/GZipVolume.cs @@ -9,7 +9,7 @@ public GZipVolume(Stream stream, ReaderOptions? options, int index) : base(stream, options, index) { } public GZipVolume(FileInfo fileInfo, ReaderOptions options) - : base(fileInfo.OpenRead(), options) => options.LeaveStreamOpen = false; + : base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { } public override bool IsFirstVolume => true; diff --git a/src/SharpCompress/Common/IEntry.Extensions.cs b/src/SharpCompress/Common/IEntry.Extensions.cs index 7e9b79a34..d73f8a683 100644 --- a/src/SharpCompress/Common/IEntry.Extensions.cs +++ b/src/SharpCompress/Common/IEntry.Extensions.cs @@ -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 @@ ExtractionOptions options } // update file time to original packed time - if (options.PreserveFileTime) + if (entry.Options.PreserveFileTime) { if (entry.CreatedTime.HasValue) { @@ -37,7 +33,7 @@ ExtractionOptions options } } - if (options.PreserveAttributes) + if (entry.Options.PreserveAttributes) { if (entry.Attrib.HasValue) { diff --git a/src/SharpCompress/Common/IEntry.cs b/src/SharpCompress/Common/IEntry.cs index 56e1db818..741b4825a 100644 --- a/src/SharpCompress/Common/IEntry.cs +++ b/src/SharpCompress/Common/IEntry.cs @@ -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; } + + /// + /// The options used when opening this entry's source (reader or archive). + /// + IReaderOptions Options { get; } } diff --git a/src/SharpCompress/Common/IsExternalInit.cs b/src/SharpCompress/Common/IsExternalInit.cs new file mode 100644 index 000000000..a840c45cc --- /dev/null +++ b/src/SharpCompress/Common/IsExternalInit.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This file is required for init-only properties to work on older target frameworks (.NET Framework 4.8, .NET Standard 2.0) +// The IsExternalInit type is used by the compiler for records and init-only properties + +#if NETFRAMEWORK || NETSTANDARD2_0 +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +/// +/// Reserved to be used by the compiler for tracking metadata. +/// This class should not be used by developers in source code. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit { } +#endif diff --git a/src/SharpCompress/Common/Options/IEncodingOptions.cs b/src/SharpCompress/Common/Options/IEncodingOptions.cs new file mode 100644 index 000000000..d9020a468 --- /dev/null +++ b/src/SharpCompress/Common/Options/IEncodingOptions.cs @@ -0,0 +1,6 @@ +namespace SharpCompress.Common.Options; + +public interface IEncodingOptions +{ + IArchiveEncoding ArchiveEncoding { get; init; } +} diff --git a/src/SharpCompress/Common/Options/IExtractionOptions.cs b/src/SharpCompress/Common/Options/IExtractionOptions.cs new file mode 100644 index 000000000..3e88d2473 --- /dev/null +++ b/src/SharpCompress/Common/Options/IExtractionOptions.cs @@ -0,0 +1,39 @@ +using System; + +namespace SharpCompress.Common.Options; + +/// +/// Options for configuring extraction behavior when extracting archive entries to the filesystem. +/// +public interface IExtractionOptions +{ + /// + /// Overwrite target if it exists. + /// Breaking change: Default changed from false to true in version 0.40.0. + /// + bool Overwrite { get; init; } + + /// + /// Extract with internal directory structure. + /// Breaking change: Default changed from false to true in version 0.40.0. + /// + bool ExtractFullPath { get; init; } + + /// + /// Preserve file time. + /// Breaking change: Default changed from false to true in version 0.40.0. + /// + bool PreserveFileTime { get; init; } + + /// + /// Preserve windows file attributes. + /// + bool PreserveAttributes { get; init; } + + /// + /// 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). + /// + Action? SymbolicLinkHandler { get; init; } +} diff --git a/src/SharpCompress/Common/Options/IProgressOptions.cs b/src/SharpCompress/Common/Options/IProgressOptions.cs new file mode 100644 index 000000000..76bab1bf5 --- /dev/null +++ b/src/SharpCompress/Common/Options/IProgressOptions.cs @@ -0,0 +1,8 @@ +using System; + +namespace SharpCompress.Common.Options; + +public interface IProgressOptions +{ + IProgress? Progress { get; init; } +} diff --git a/src/SharpCompress/Common/Options/IReaderOptions.cs b/src/SharpCompress/Common/Options/IReaderOptions.cs new file mode 100644 index 000000000..d9f51880e --- /dev/null +++ b/src/SharpCompress/Common/Options/IReaderOptions.cs @@ -0,0 +1,15 @@ +namespace SharpCompress.Common.Options; + +public interface IReaderOptions + : IStreamOptions, + IEncodingOptions, + IProgressOptions, + IExtractionOptions +{ + bool LookForHeader { get; init; } + string? Password { get; init; } + bool DisableCheckIncomplete { get; init; } + int BufferSize { get; init; } + string? ExtensionHint { get; init; } + int? RewindableBufferSize { get; init; } +} diff --git a/src/SharpCompress/Common/Options/IStreamOptions.cs b/src/SharpCompress/Common/Options/IStreamOptions.cs new file mode 100644 index 000000000..be0bbb9fe --- /dev/null +++ b/src/SharpCompress/Common/Options/IStreamOptions.cs @@ -0,0 +1,6 @@ +namespace SharpCompress.Common.Options; + +public interface IStreamOptions +{ + bool LeaveStreamOpen { get; init; } +} diff --git a/src/SharpCompress/Common/Options/IWriterOptions.cs b/src/SharpCompress/Common/Options/IWriterOptions.cs new file mode 100644 index 000000000..2363ea8b8 --- /dev/null +++ b/src/SharpCompress/Common/Options/IWriterOptions.cs @@ -0,0 +1,9 @@ +using SharpCompress.Common; + +namespace SharpCompress.Common.Options; + +public interface IWriterOptions : IStreamOptions, IEncodingOptions, IProgressOptions +{ + CompressionType CompressionType { get; init; } + int CompressionLevel { get; init; } +} diff --git a/src/SharpCompress/Common/OptionsBase.cs b/src/SharpCompress/Common/OptionsBase.cs deleted file mode 100644 index ed13e146b..000000000 --- a/src/SharpCompress/Common/OptionsBase.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace SharpCompress.Common; - -public class OptionsBase -{ - /// - /// SharpCompress will keep the supplied streams open. Default is true. - /// - public bool LeaveStreamOpen { get; set; } = true; - - public IArchiveEncoding ArchiveEncoding { get; set; } = new ArchiveEncoding(); -} diff --git a/src/SharpCompress/Common/Rar/RarEntry.cs b/src/SharpCompress/Common/Rar/RarEntry.cs index c76c72b6f..9c0f7ae5f 100644 --- a/src/SharpCompress/Common/Rar/RarEntry.cs +++ b/src/SharpCompress/Common/Rar/RarEntry.cs @@ -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) { } + /// /// As the V2017 port isn't complete, add this check to use the legacy Rar code. /// diff --git a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs index 79df43a07..fb6d23fa7 100644 --- a/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs +++ b/src/SharpCompress/Common/SevenZip/SevenZipEntry.cs @@ -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; } diff --git a/src/SharpCompress/Common/Tar/TarEntry.Async.cs b/src/SharpCompress/Common/Tar/TarEntry.Async.cs index cfa453772..c066da56a 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.Async.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.Async.cs @@ -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 @@ internal static async IAsyncEnumerable GetEntriesAsync( StreamingMode mode, Stream stream, CompressionType compressionType, - IArchiveEncoding archiveEncoding + IArchiveEncoding archiveEncoding, + IReaderOptions readerOptions ) { await foreach ( @@ -21,11 +23,19 @@ var header in TarHeaderFactory.ReadHeaderAsync(mode, stream, archiveEncoding) { 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 diff --git a/src/SharpCompress/Common/Tar/TarEntry.cs b/src/SharpCompress/Common/Tar/TarEntry.cs index fbe93de85..28e7d45fb 100644 --- a/src/SharpCompress/Common/Tar/TarEntry.cs +++ b/src/SharpCompress/Common/Tar/TarEntry.cs @@ -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 @@ internal static IEnumerable GetEntries( 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 @@ IArchiveEncoding archiveEncoding { 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 diff --git a/src/SharpCompress/Common/Zip/ZipEntry.cs b/src/SharpCompress/Common/Zip/ZipEntry.cs index 19f9961b0..8c193f294 100644 --- a/src/SharpCompress/Common/Zip/ZipEntry.cs +++ b/src/SharpCompress/Common/Zip/ZipEntry.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SharpCompress.Common.Options; using SharpCompress.Common.Zip.Headers; namespace SharpCompress.Common.Zip; @@ -9,7 +10,8 @@ public class ZipEntry : Entry { private readonly ZipFilePart? _filePart; - internal ZipEntry(ZipFilePart? filePart) + internal ZipEntry(ZipFilePart? filePart, IReaderOptions readerOptions) + : base(readerOptions) { if (filePart == null) { diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index ae4ac8b87..68aa8e783 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -7,6 +8,7 @@ using SharpCompress.Archives.GZip; using SharpCompress.Archives.Tar; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.GZip; @@ -25,7 +27,7 @@ public class GZipFactory IMultiArchiveFactory, IReaderFactory, IWriterFactory, - IWriteableArchiveFactory + IWriteableArchiveFactory { #region IFactory @@ -156,27 +158,33 @@ public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = nu #region IWriterFactory /// - public IWriter OpenWriter(Stream stream, WriterOptions writerOptions) + public IWriter OpenWriter(Stream stream, IWriterOptions writerOptions) { if (writerOptions.CompressionType != CompressionType.GZip) { throw new InvalidFormatException("GZip archives only support GZip compression type."); } - return new GZipWriter(stream, new GZipWriterOptions(writerOptions)); + + GZipWriterOptions gzipOptions = writerOptions switch + { + GZipWriterOptions gwo => gwo, + WriterOptions wo => new GZipWriterOptions(wo), + _ => throw new ArgumentException( + $"Expected WriterOptions or GZipWriterOptions, got {writerOptions.GetType().Name}", + nameof(writerOptions) + ), + }; + return new GZipWriter(stream, gzipOptions); } /// public IAsyncWriter OpenAsyncWriter( Stream stream, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - if (writerOptions.CompressionType != CompressionType.GZip) - { - throw new InvalidFormatException("GZip archives only support GZip compression type."); - } return (IAsyncWriter)OpenWriter(stream, writerOptions); } @@ -185,7 +193,7 @@ public IAsyncWriter OpenAsyncWriter( #region IWriteableArchiveFactory /// - public IWritableArchive CreateArchive() => GZipArchive.CreateArchive(); + public IWritableArchive CreateArchive() => GZipArchive.CreateArchive(); #endregion } diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 8857cca77..f399b9846 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -7,6 +7,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Tar; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Tar; @@ -24,7 +25,7 @@ public class TarFactory IMultiArchiveFactory, IReaderFactory, IWriterFactory, - IWriteableArchiveFactory + IWriteableArchiveFactory { #region IFactory @@ -216,13 +217,24 @@ public async ValueTask OpenAsyncReader( #region IWriterFactory /// - public IWriter OpenWriter(Stream stream, WriterOptions writerOptions) => - new TarWriter(stream, new TarWriterOptions(writerOptions)); + public IWriter OpenWriter(Stream stream, IWriterOptions writerOptions) + { + TarWriterOptions tarOptions = writerOptions switch + { + TarWriterOptions two => two, + WriterOptions wo => new TarWriterOptions(wo), + _ => throw new ArgumentException( + $"Expected WriterOptions or TarWriterOptions, got {writerOptions.GetType().Name}", + nameof(writerOptions) + ), + }; + return new TarWriter(stream, tarOptions); + } /// public IAsyncWriter OpenAsyncWriter( Stream stream, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { @@ -235,7 +247,7 @@ public IAsyncWriter OpenAsyncWriter( #region IWriteableArchiveFactory /// - public IWritableArchive CreateArchive() => TarArchive.CreateArchive(); + public IWritableArchive CreateArchive() => TarArchive.CreateArchive(); #endregion } diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index 573c95bf8..f22cdf2e9 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -5,6 +6,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.IO; using SharpCompress.Readers; using SharpCompress.Readers.Zip; @@ -22,7 +24,7 @@ public class ZipFactory IMultiArchiveFactory, IReaderFactory, IWriterFactory, - IWriteableArchiveFactory + IWriteableArchiveFactory { #region IFactory @@ -185,13 +187,24 @@ public ValueTask OpenAsyncReader( #region IWriterFactory /// - public IWriter OpenWriter(Stream stream, WriterOptions writerOptions) => - new ZipWriter(stream, new ZipWriterOptions(writerOptions)); + public IWriter OpenWriter(Stream stream, IWriterOptions writerOptions) + { + ZipWriterOptions zipOptions = writerOptions switch + { + ZipWriterOptions zwo => zwo, + WriterOptions wo => new ZipWriterOptions(wo), + _ => throw new ArgumentException( + $"Expected WriterOptions or ZipWriterOptions, got {writerOptions.GetType().Name}", + nameof(writerOptions) + ), + }; + return new ZipWriter(stream, zipOptions); + } /// public IAsyncWriter OpenAsyncWriter( Stream stream, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { @@ -204,7 +217,7 @@ public IAsyncWriter OpenAsyncWriter( #region IWriteableArchiveFactory /// - public IWritableArchive CreateArchive() => ZipArchive.CreateArchive(); + public IWritableArchive CreateArchive() => ZipArchive.CreateArchive(); #endregion } diff --git a/src/SharpCompress/Readers/Ace/AceReader.cs b/src/SharpCompress/Readers/Ace/AceReader.cs index 46f045b5e..854af4ed3 100644 --- a/src/SharpCompress/Readers/Ace/AceReader.cs +++ b/src/SharpCompress/Readers/Ace/AceReader.cs @@ -74,7 +74,7 @@ protected override IEnumerable GetEntries(Stream stream) break; } - yield return new AceEntry(new AceFilePart((AceFileHeader)localHeader, stream)); + yield return new AceEntry(new AceFilePart((AceFileHeader)localHeader, stream), Options); } } @@ -114,7 +114,7 @@ protected override async IAsyncEnumerable GetEntriesAsync(Stream strea break; } - yield return new AceEntry(new AceFilePart((AceFileHeader)localHeader, stream)); + yield return new AceEntry(new AceFilePart((AceFileHeader)localHeader, stream), Options); } } diff --git a/src/SharpCompress/Readers/Arc/ArcReader.Async.cs b/src/SharpCompress/Readers/Arc/ArcReader.Async.cs index ebb0e269c..dc843e9e8 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.Async.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.Async.cs @@ -15,7 +15,7 @@ protected override async IAsyncEnumerable GetEntriesAsync(Stream strea (header = await headerReader.ReadHeaderAsync(stream, CancellationToken.None)) != null ) { - yield return new ArcEntry(new ArcFilePart(header, stream)); + yield return new ArcEntry(new ArcFilePart(header, stream), Options); } } } diff --git a/src/SharpCompress/Readers/Arc/ArcReader.cs b/src/SharpCompress/Readers/Arc/ArcReader.cs index ee9d5fa31..d641b554b 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.cs @@ -34,7 +34,7 @@ protected override IEnumerable GetEntries(Stream stream) ArcEntryHeader? header; while ((header = headerReader.ReadHeader(stream)) != null) { - yield return new ArcEntry(new ArcFilePart(header, stream)); + yield return new ArcEntry(new ArcFilePart(header, stream), Options); } } } diff --git a/src/SharpCompress/Readers/Arj/ArjReader.cs b/src/SharpCompress/Readers/Arj/ArjReader.cs index 9a589925c..5c751cebc 100644 --- a/src/SharpCompress/Readers/Arj/ArjReader.cs +++ b/src/SharpCompress/Readers/Arj/ArjReader.cs @@ -90,7 +90,10 @@ protected override IEnumerable GetEntries(Stream stream) continue; } - yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream)); + yield return new ArjEntry( + new ArjFilePart((ArjLocalHeader)localHeader, stream), + Options + ); } } @@ -135,7 +138,10 @@ protected override async IAsyncEnumerable GetEntriesAsync(Stream strea continue; } - yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream)); + yield return new ArjEntry( + new ArjFilePart((ArjLocalHeader)localHeader, stream), + Options + ); } } diff --git a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs index be53d6301..cb1e0fbbf 100644 --- a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs +++ b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs @@ -14,15 +14,14 @@ public static class IAsyncReaderExtensions /// public async ValueTask WriteEntryToDirectoryAsync( string destinationDirectory, - ExtractionOptions? options = null, CancellationToken cancellationToken = default ) => await ExtractionMethods .WriteEntryToDirectoryAsync( reader.Entry, destinationDirectory, - options, - reader.WriteEntryToFileAsync, + async (path, ct) => + await reader.WriteEntryToFileAsync(path, ct).ConfigureAwait(false), cancellationToken ) .ConfigureAwait(false); @@ -32,14 +31,12 @@ await ExtractionMethods /// public async ValueTask WriteEntryToFileAsync( string destinationFileName, - ExtractionOptions? options = null, CancellationToken cancellationToken = default ) => await ExtractionMethods .WriteEntryToFileAsync( reader.Entry, destinationFileName, - options, async (x, fm, ct) => { using var fs = File.Open(destinationFileName, fm); @@ -54,28 +51,25 @@ await ExtractionMethods /// public async ValueTask WriteAllToDirectoryAsync( string destinationDirectory, - ExtractionOptions? options = null, CancellationToken cancellationToken = default ) { while (await reader.MoveToNextEntryAsync(cancellationToken)) { await reader - .WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken) + .WriteEntryToDirectoryAsync(destinationDirectory, cancellationToken) .ConfigureAwait(false); } } public async ValueTask WriteEntryToAsync( string destinationFileName, - ExtractionOptions? options = null, CancellationToken cancellationToken = default ) => await ExtractionMethods .WriteEntryToFileAsync( reader.Entry, destinationFileName, - options, async (x, fm, ct) => { using var fs = File.Open(destinationFileName, fm); @@ -87,11 +81,10 @@ await ExtractionMethods public async ValueTask WriteEntryToAsync( FileInfo destinationFileInfo, - ExtractionOptions? options = null, CancellationToken cancellationToken = default ) => await reader - .WriteEntryToAsync(destinationFileInfo.FullName, options, cancellationToken) + .WriteEntryToAsync(destinationFileInfo.FullName, cancellationToken) .ConfigureAwait(false); } } diff --git a/src/SharpCompress/Readers/IReaderExtensions.cs b/src/SharpCompress/Readers/IReaderExtensions.cs index cfa7c13a2..436696bf6 100644 --- a/src/SharpCompress/Readers/IReaderExtensions.cs +++ b/src/SharpCompress/Readers/IReaderExtensions.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using SharpCompress.Common; namespace SharpCompress.Readers; @@ -22,42 +22,31 @@ public void WriteEntryTo(FileInfo filePath) /// /// Extract all remaining unread entries to specific directory, retaining filename /// - public void WriteAllToDirectory( - string destinationDirectory, - ExtractionOptions? options = null - ) + public void WriteAllToDirectory(string destinationDirectory) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory(destinationDirectory, options); + reader.WriteEntryToDirectory(destinationDirectory); } } /// /// Extract to specific directory, retaining filename /// - public void WriteEntryToDirectory( - string destinationDirectory, - ExtractionOptions? options = null - ) => + public void WriteEntryToDirectory(string destinationDirectory) => ExtractionMethods.WriteEntryToDirectory( reader.Entry, destinationDirectory, - options, - reader.WriteEntryToFile + (path) => reader.WriteEntryToFile(path) ); /// /// Extract to specific file /// - public void WriteEntryToFile( - string destinationFileName, - ExtractionOptions? options = null - ) => + public void WriteEntryToFile(string destinationFileName) => ExtractionMethods.WriteEntryToFile( reader.Entry, destinationFileName, - options, (x, fm) => { using var fs = File.Open(destinationFileName, fm); diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index cd0aebc97..741f7155f 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -93,7 +93,7 @@ protected override IEnumerable GetEntries(Stream stream) foreach (var fp in volume.ReadFileParts()) { ValidateArchive(volume); - yield return new RarReaderEntry(volume.IsSolidArchive, fp); + yield return new RarReaderEntry(volume.IsSolidArchive, fp, Options); } } @@ -103,7 +103,7 @@ protected override async IAsyncEnumerable GetEntriesAsync(Stream await foreach (var fp in volume.ReadFilePartsAsync()) { ValidateArchive(volume); - yield return new RarReaderEntry(volume.IsSolidArchive, fp); + yield return new RarReaderEntry(volume.IsSolidArchive, fp, Options); } } diff --git a/src/SharpCompress/Readers/Rar/RarReaderEntry.cs b/src/SharpCompress/Readers/Rar/RarReaderEntry.cs index d6f7222f4..de55de91c 100644 --- a/src/SharpCompress/Readers/Rar/RarReaderEntry.cs +++ b/src/SharpCompress/Readers/Rar/RarReaderEntry.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Rar; using SharpCompress.Common.Rar.Headers; @@ -7,7 +8,8 @@ namespace SharpCompress.Readers.Rar; public class RarReaderEntry : RarEntry { - internal RarReaderEntry(bool solid, RarFilePart part) + internal RarReaderEntry(bool solid, RarFilePart part, IReaderOptions readerOptions) + : base(readerOptions) { Part = part; IsSolid = solid; diff --git a/src/SharpCompress/Readers/ReaderFactory.Async.cs b/src/SharpCompress/Readers/ReaderFactory.Async.cs index cf51e5601..58ca951d1 100644 --- a/src/SharpCompress/Readers/ReaderFactory.Async.cs +++ b/src/SharpCompress/Readers/ReaderFactory.Async.cs @@ -41,7 +41,7 @@ public static ValueTask OpenAsyncReader( CancellationToken cancellationToken = default ) { - options ??= new ReaderOptions { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForOwnedFile; return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken); } @@ -52,7 +52,7 @@ public static async ValueTask OpenAsyncReader( ) { stream.NotNull(nameof(stream)); - options ??= new ReaderOptions() { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForExternalStream; var sharpCompressStream = SharpCompressStream.Create( stream, diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 186efb64f..8ecb1b123 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -19,7 +19,7 @@ public static IReader OpenReader(string filePath, ReaderOptions? options = null) public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null) { - options ??= new ReaderOptions { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForOwnedFile; return OpenReader(fileInfo.OpenRead(), options); } @@ -32,7 +32,7 @@ public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = nul public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); - options ??= new ReaderOptions() { LeaveStreamOpen = false }; + options ??= ReaderOptions.ForExternalStream; var sharpCompressStream = SharpCompressStream.Create( stream, diff --git a/src/SharpCompress/Readers/ReaderOptions.cs b/src/SharpCompress/Readers/ReaderOptions.cs index 2ddb8ac40..0caac68df 100644 --- a/src/SharpCompress/Readers/ReaderOptions.cs +++ b/src/SharpCompress/Readers/ReaderOptions.cs @@ -1,9 +1,25 @@ using System; using SharpCompress.Common; +using SharpCompress.Common.Options; namespace SharpCompress.Readers; -public class ReaderOptions : OptionsBase +/// +/// Options for configuring reader behavior when opening archives. +/// +/// +/// This class is immutable. Use factory presets and fluent helpers for common configurations: +/// +/// var options = ReaderOptions.ForExternalStream() +/// .WithPassword("secret") +/// .WithLookForHeader(true); +/// +/// Or use object initializers for simple cases: +/// +/// var options = new ReaderOptions { Password = "secret", LeaveStreamOpen = false }; +/// +/// +public sealed record ReaderOptions : IReaderOptions { /// /// The default buffer size for stream operations. @@ -15,27 +31,46 @@ public class ReaderOptions : OptionsBase )] public const int DefaultBufferSize = 0x10000; + /// + /// SharpCompress will keep the supplied streams open. Default is true. + /// + public bool LeaveStreamOpen { get; init; } = true; + + /// + /// Encoding to use for archive entry names. + /// + public IArchiveEncoding ArchiveEncoding { get; init; } = new ArchiveEncoding(); + /// /// Look for RarArchive (Check for self-extracting archives or cases where RarArchive isn't at the start of the file) /// - public bool LookForHeader { get; set; } + public bool LookForHeader { get; init; } - public string? Password { get; set; } + /// + /// Password for encrypted archives. + /// + public string? Password { get; init; } - public bool DisableCheckIncomplete { get; set; } + /// + /// Disable checking for incomplete archives. + /// + public bool DisableCheckIncomplete { get; init; } - public int BufferSize { get; set; } = Constants.BufferSize; + /// + /// Buffer size for stream operations. + /// + public int BufferSize { get; init; } = Constants.BufferSize; /// /// Provide a hint for the extension of the archive being read, can speed up finding the correct decoder. Should be without the leading period in the form like: tar.gz or zip /// - public string? ExtensionHint { get; set; } + public string? ExtensionHint { get; init; } /// /// An optional progress reporter for tracking extraction operations. /// When set, progress updates will be reported as entries are extracted. /// - public IProgress? Progress { get; set; } + public IProgress? Progress { get; init; } /// /// Size of the rewindable buffer for non-seekable streams. @@ -78,5 +113,103 @@ public class ReaderOptions : OptionsBase /// using var reader = ReaderFactory.OpenReader(networkStream, options); /// /// - public int? RewindableBufferSize { get; set; } + public int? RewindableBufferSize { get; init; } + + /// + /// Overwrite target if it exists. + /// Breaking change: Default changed from false to true in version 0.40.0. + /// + public bool Overwrite { get; init; } = true; + + /// + /// Extract with internal directory structure. + /// Breaking change: Default changed from false to true in version 0.40.0. + /// + public bool ExtractFullPath { get; init; } = true; + + /// + /// Preserve file time. + /// Breaking change: Default changed from false to true in version 0.40.0. + /// + public bool PreserveFileTime { get; init; } = true; + + /// + /// Preserve windows file attributes. + /// + public bool PreserveAttributes { get; init; } + + /// + /// 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). + /// + /// + /// Breaking change: Changed from field to init-only property in version 0.40.0. + /// The default handler logs a warning message. + /// + public Action? SymbolicLinkHandler { get; init; } + + /// + /// Creates a new ReaderOptions instance with default values. + /// + public ReaderOptions() { } + + /// + /// Gets ReaderOptions configured for caller-provided streams. + /// + public static ReaderOptions ForExternalStream => new() { LeaveStreamOpen = true }; + + /// + /// Gets ReaderOptions configured for file-based overloads that open their own stream. + /// + public static ReaderOptions ForOwnedFile => new() { LeaveStreamOpen = false }; + + /// + /// Gets a ReaderOptions instance configured for safe extraction (no overwrite). + /// + public static ReaderOptions SafeExtract => new() { Overwrite = false }; + + /// + /// Gets a ReaderOptions instance configured for flat extraction (no directory structure). + /// + public static ReaderOptions FlatExtract => new() { ExtractFullPath = false, Overwrite = true }; + + /// + /// Creates ReaderOptions for reading encrypted archives. + /// + /// The password for encrypted archives. + public static ReaderOptions ForEncryptedArchive(string? password = null) => + new ReaderOptions().WithPassword(password); + + /// + /// Creates ReaderOptions for archives with custom character encoding. + /// + /// The encoding for archive entry names. + public static ReaderOptions ForEncoding(IArchiveEncoding encoding) => + new ReaderOptions().WithArchiveEncoding(encoding); + + /// + /// Creates ReaderOptions for self-extracting archives that require header search. + /// + public static ReaderOptions ForSelfExtractingArchive(string? password = null) => + new ReaderOptions() + .WithLookForHeader(true) + .WithPassword(password) + .WithRewindableBufferSize(1_048_576); // 1MB for SFX archives + + /// + /// Default symbolic link handler that logs a warning message. + /// + 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" + ); + } + + // Note: Parameterized constructors have been removed. + // Use fluent With*() helpers or object initializers instead: + // new ReaderOptions().WithPassword("secret").WithLookForHeader(true) + // or + // new ReaderOptions { Password = "secret", LookForHeader = true } } diff --git a/src/SharpCompress/Readers/ReaderOptionsExtensions.cs b/src/SharpCompress/Readers/ReaderOptionsExtensions.cs new file mode 100644 index 000000000..26a8dfbe0 --- /dev/null +++ b/src/SharpCompress/Readers/ReaderOptionsExtensions.cs @@ -0,0 +1,127 @@ +using System; +using SharpCompress.Common; +using SharpCompress.Common.Options; + +namespace SharpCompress.Readers; + +/// +/// Extension methods for fluent configuration of reader options. +/// +public static class ReaderOptionsExtensions +{ + /// + /// Creates a copy with the specified LeaveStreamOpen value. + /// + public static ReaderOptions WithLeaveStreamOpen( + this ReaderOptions options, + bool leaveStreamOpen + ) => options with { LeaveStreamOpen = leaveStreamOpen }; + + /// + /// Creates a copy with the specified password. + /// + public static ReaderOptions WithPassword(this ReaderOptions options, string? password) => + options with + { + Password = password, + }; + + /// + /// Creates a copy with the specified archive encoding. + /// + public static ReaderOptions WithArchiveEncoding( + this ReaderOptions options, + IArchiveEncoding encoding + ) => options with { ArchiveEncoding = encoding }; + + /// + /// Creates a copy with the specified LookForHeader value. + /// + public static ReaderOptions WithLookForHeader(this ReaderOptions options, bool lookForHeader) => + options with + { + LookForHeader = lookForHeader, + }; + + /// + /// Creates a copy with the specified DisableCheckIncomplete value. + /// + public static ReaderOptions WithDisableCheckIncomplete( + this ReaderOptions options, + bool disableCheckIncomplete + ) => options with { DisableCheckIncomplete = disableCheckIncomplete }; + + /// + /// Creates a copy with the specified buffer size. + /// + public static ReaderOptions WithBufferSize(this ReaderOptions options, int bufferSize) => + options with + { + BufferSize = bufferSize, + }; + + /// + /// Creates a copy with the specified extension hint. + /// + public static ReaderOptions WithExtensionHint( + this ReaderOptions options, + string? extensionHint + ) => options with { ExtensionHint = extensionHint }; + + /// + /// Creates a copy with the specified progress reporter. + /// + public static ReaderOptions WithProgress( + this ReaderOptions options, + IProgress? progress + ) => options with { Progress = progress }; + + /// + /// Creates a copy with the specified rewindable buffer size. + /// + public static ReaderOptions WithRewindableBufferSize( + this ReaderOptions options, + int? rewindableBufferSize + ) => options with { RewindableBufferSize = rewindableBufferSize }; + + /// + /// Creates a copy with the specified overwrite setting. + /// + public static ReaderOptions WithOverwrite(this ReaderOptions options, bool overwrite) => + options with + { + Overwrite = overwrite, + }; + + /// + /// Creates a copy with the specified extract full path setting. + /// + public static ReaderOptions WithExtractFullPath( + this ReaderOptions options, + bool extractFullPath + ) => options with { ExtractFullPath = extractFullPath }; + + /// + /// Creates a copy with the specified preserve file time setting. + /// + public static ReaderOptions WithPreserveFileTime( + this ReaderOptions options, + bool preserveFileTime + ) => options with { PreserveFileTime = preserveFileTime }; + + /// + /// Creates a copy with the specified preserve attributes setting. + /// + public static ReaderOptions WithPreserveAttributes( + this ReaderOptions options, + bool preserveAttributes + ) => options with { PreserveAttributes = preserveAttributes }; + + /// + /// Creates a copy with the specified symbolic link handler. + /// + public static ReaderOptions WithSymbolicLinkHandler( + this ReaderOptions options, + Action? handler + ) => options with { SymbolicLinkHandler = handler }; +} diff --git a/src/SharpCompress/Readers/Tar/TarReader.Async.cs b/src/SharpCompress/Readers/Tar/TarReader.Async.cs index 60026308b..6684bdabe 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.Async.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.Async.cs @@ -26,6 +26,7 @@ protected override IAsyncEnumerable GetEntriesAsync(Stream stream) => StreamingMode.Streaming, stream, compressionType, - Options.ArchiveEncoding + Options.ArchiveEncoding, + Options ); } diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index b29c820d1..2c561c150 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -124,7 +124,8 @@ protected override IEnumerable GetEntries(Stream stream) => StreamingMode.Streaming, stream, compressionType, - Options.ArchiveEncoding + Options.ArchiveEncoding, + Options ); // GetEntriesAsync moved to TarReader.Async.cs diff --git a/src/SharpCompress/Readers/Zip/ZipReader.Async.cs b/src/SharpCompress/Readers/Zip/ZipReader.Async.cs index b8c71eaa8..bb2496de1 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.Async.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.Async.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Zip; using SharpCompress.Common.Zip.Headers; @@ -18,16 +19,22 @@ private sealed class ZipEntryAsyncEnumerable : IAsyncEnumerable { private readonly StreamingZipHeaderFactory _headerFactory; private readonly Stream _stream; + private readonly IReaderOptions _options; - public ZipEntryAsyncEnumerable(StreamingZipHeaderFactory headerFactory, Stream stream) + public ZipEntryAsyncEnumerable( + StreamingZipHeaderFactory headerFactory, + Stream stream, + IReaderOptions options + ) { _headerFactory = headerFactory; _stream = stream; + _options = options; } public IAsyncEnumerator GetAsyncEnumerator( CancellationToken cancellationToken = default - ) => new ZipEntryAsyncEnumerator(_headerFactory, _stream, cancellationToken); + ) => new ZipEntryAsyncEnumerator(_headerFactory, _stream, _options, cancellationToken); } /// @@ -37,15 +44,18 @@ private sealed class ZipEntryAsyncEnumerator : IAsyncEnumerator, IDisp { private readonly Stream _stream; private readonly IAsyncEnumerator _headerEnumerator; + private readonly IReaderOptions _options; private ZipEntry? _current; public ZipEntryAsyncEnumerator( StreamingZipHeaderFactory headerFactory, Stream stream, + IReaderOptions options, CancellationToken cancellationToken ) { _stream = stream; + _options = options; _headerEnumerator = headerFactory .ReadStreamHeaderAsync(stream) .GetAsyncEnumerator(cancellationToken); @@ -67,7 +77,8 @@ public async ValueTask MoveNextAsync() { case ZipHeaderType.LocalEntry: _current = new ZipEntry( - new StreamingZipFilePart((LocalEntryHeader)header, _stream) + new StreamingZipFilePart((LocalEntryHeader)header, _stream), + _options ); return true; case ZipHeaderType.DirectoryEntry: diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index 83d0aa8c8..803dad0ca 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -74,7 +74,8 @@ protected override IEnumerable GetEntries(Stream stream) case ZipHeaderType.LocalEntry: { yield return new ZipEntry( - new StreamingZipFilePart((LocalEntryHeader)h, stream) + new StreamingZipFilePart((LocalEntryHeader)h, stream), + Options ); } break; @@ -99,7 +100,7 @@ protected override IEnumerable GetEntries(Stream stream) /// Returns entries asynchronously for streams that only support async reads. /// protected override IAsyncEnumerable GetEntriesAsync(Stream stream) => - new ZipEntryAsyncEnumerable(_headerFactory, stream); + new ZipEntryAsyncEnumerable(_headerFactory, stream, Options); // Async nested classes moved to ZipReader.Async.cs } diff --git a/src/SharpCompress/Writers/AbstractWriter.cs b/src/SharpCompress/Writers/AbstractWriter.cs index d9967ceb8..c653d73cd 100644 --- a/src/SharpCompress/Writers/AbstractWriter.cs +++ b/src/SharpCompress/Writers/AbstractWriter.cs @@ -1,14 +1,15 @@ -using System; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.IO; namespace SharpCompress.Writers; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -public abstract partial class AbstractWriter(ArchiveType type, WriterOptions writerOptions) +public abstract partial class AbstractWriter(ArchiveType type, IWriterOptions writerOptions) : IWriter, IAsyncWriter { @@ -23,7 +24,7 @@ public abstract partial class AbstractWriter(ArchiveType type, WriterOptions wri public ArchiveType WriterType { get; } = type; - protected WriterOptions WriterOptions { get; } = writerOptions; + protected IWriterOptions WriterOptions { get; } = writerOptions; /// /// Wraps the source stream with a progress-reporting stream if progress reporting is enabled. diff --git a/src/SharpCompress/Writers/CompressionLevelValidation.cs b/src/SharpCompress/Writers/CompressionLevelValidation.cs new file mode 100644 index 000000000..ab964191a --- /dev/null +++ b/src/SharpCompress/Writers/CompressionLevelValidation.cs @@ -0,0 +1,49 @@ +using System; +using SharpCompress.Common; + +namespace SharpCompress.Writers; + +internal static class CompressionLevelValidation +{ + public static void Validate(CompressionType compressionType, int compressionLevel) + { + switch (compressionType) + { + case CompressionType.Deflate: + case CompressionType.Deflate64: + case CompressionType.GZip: + EnsureRange(compressionLevel, 0, 9, compressionType); + break; + case CompressionType.ZStandard: + EnsureRange(compressionLevel, 1, 22, compressionType); + break; + default: + if (compressionLevel != 0) + { + throw new ArgumentOutOfRangeException( + nameof(compressionLevel), + compressionLevel, + $"Compression type {compressionType} does not support configurable compression levels. Use 0." + ); + } + break; + } + } + + private static void EnsureRange( + int compressionLevel, + int minInclusive, + int maxInclusive, + CompressionType compressionType + ) + { + if (compressionLevel < minInclusive || compressionLevel > maxInclusive) + { + throw new ArgumentOutOfRangeException( + nameof(compressionLevel), + compressionLevel, + $"Compression level for {compressionType} must be between {minInclusive} and {maxInclusive}." + ); + } + } +} diff --git a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs index 0b16b8371..3477cb3e2 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriterOptions.cs @@ -1,18 +1,121 @@ +using System; using SharpCompress.Common; +using SharpCompress.Common.Options; +using SharpCompress.Writers; using D = SharpCompress.Compressors.Deflate; namespace SharpCompress.Writers.GZip; -public class GZipWriterOptions : WriterOptions +/// +/// Options for configuring GZip writer behavior. +/// +/// +/// This class is immutable. Use factory methods for creation: +/// +/// var options = WriterOptions.ForGZip().WithLeaveStreamOpen(false).WithCompressionLevel(9); +/// +/// +public sealed record GZipWriterOptions : IWriterOptions { - public GZipWriterOptions() - : base(CompressionType.GZip, (int)(D.CompressionLevel.Default)) { } + private int _compressionLevel = (int)D.CompressionLevel.Default; - internal GZipWriterOptions(WriterOptions options) - : base(options.CompressionType, (int)(D.CompressionLevel.Default)) + /// + /// The compression type (always GZip for this writer). + /// + public CompressionType CompressionType { + get => CompressionType.GZip; + init + { + if (value != CompressionType.GZip) + { + throw new ArgumentOutOfRangeException( + nameof(CompressionType), + value, + "GZipWriterOptions only supports CompressionType.GZip." + ); + } + } + } + + /// + /// The compression level to be used (0-9 for Deflate). + /// + public int CompressionLevel + { + get => _compressionLevel; + init + { + CompressionLevelValidation.Validate(CompressionType.GZip, value); + _compressionLevel = value; + } + } + + /// + /// SharpCompress will keep the supplied streams open. Default is true. + /// + public bool LeaveStreamOpen { get; init; } = true; + + /// + /// Encoding to use for archive entry names. + /// + public IArchiveEncoding ArchiveEncoding { get; init; } = new ArchiveEncoding(); + + /// + /// An optional progress reporter for tracking compression operations. + /// + public IProgress? Progress { get; init; } + + /// + /// Creates a new GZipWriterOptions instance with default values. + /// + public GZipWriterOptions() { } + + /// + /// Creates a new GZipWriterOptions instance with the specified compression level. + /// + /// The compression level (0-9). + public GZipWriterOptions(int compressionLevel) + { + CompressionLevel = compressionLevel; + } + + /// + /// Creates a new GZipWriterOptions instance with the specified Deflate compression level. + /// + /// The Deflate compression level. + public GZipWriterOptions(D.CompressionLevel compressionLevel) + { + CompressionLevel = (int)compressionLevel; + } + + // Note: Constructor with boolean leaveStreamOpen parameter removed. + // Use the fluent WithLeaveStreamOpen() helper or object initializer instead: + // new GZipWriterOptions() { LeaveStreamOpen = false } + // or + // WriterOptions.ForGZip().WithLeaveStreamOpen(false) + + /// + /// Creates a new GZipWriterOptions instance from an existing WriterOptions instance. + /// + /// The WriterOptions to copy values from. + public GZipWriterOptions(WriterOptions options) + { + CompressionLevel = options.CompressionLevel; LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; + Progress = options.Progress; + } + + /// + /// Creates a new GZipWriterOptions instance from an existing IWriterOptions instance. + /// + /// The IWriterOptions to copy values from. + public GZipWriterOptions(IWriterOptions options) + { CompressionLevel = options.CompressionLevel; + LeaveStreamOpen = options.LeaveStreamOpen; + ArchiveEncoding = options.ArchiveEncoding; + Progress = options.Progress; } } diff --git a/src/SharpCompress/Writers/IWriterFactory.cs b/src/SharpCompress/Writers/IWriterFactory.cs index f8bd8bde6..fc2ef301b 100644 --- a/src/SharpCompress/Writers/IWriterFactory.cs +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -1,16 +1,17 @@ using System.IO; using System.Threading; +using SharpCompress.Common.Options; using SharpCompress.Factories; namespace SharpCompress.Writers; public interface IWriterFactory : IFactory { - IWriter OpenWriter(Stream stream, WriterOptions writerOptions); + IWriter OpenWriter(Stream stream, IWriterOptions writerOptions); IAsyncWriter OpenAsyncWriter( Stream stream, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ); } diff --git a/src/SharpCompress/Writers/IWriterOpenable.cs b/src/SharpCompress/Writers/IWriterOpenable.cs index 14b98f711..fe40cda72 100644 --- a/src/SharpCompress/Writers/IWriterOpenable.cs +++ b/src/SharpCompress/Writers/IWriterOpenable.cs @@ -1,11 +1,12 @@ #if NET8_0_OR_GREATER using System.IO; using System.Threading; +using SharpCompress.Common.Options; namespace SharpCompress.Writers; public interface IWriterOpenable - where TWriterOptions : WriterOptions + where TWriterOptions : IWriterOptions { public static abstract IWriter OpenWriter(string filePath, TWriterOptions writerOptions); diff --git a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs index 82a0f0c79..80bf6e635 100755 --- a/src/SharpCompress/Writers/Tar/TarWriterOptions.cs +++ b/src/SharpCompress/Writers/Tar/TarWriterOptions.cs @@ -1,33 +1,120 @@ +using System; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Common.Tar.Headers; namespace SharpCompress.Writers.Tar; -public class TarWriterOptions : WriterOptions +/// +/// Options for configuring Tar writer behavior. +/// +/// +/// This class is immutable. Use the with expression to create modified copies: +/// +/// var options = new TarWriterOptions(CompressionType.GZip, true); +/// options = options with { HeaderFormat = TarHeaderWriteFormat.V7 }; +/// +/// +public sealed record TarWriterOptions : IWriterOptions { + /// + /// The compression type to use for the archive. + /// + public CompressionType CompressionType { get; init; } + + /// + /// The compression level to be used when the compression type supports variable levels. + /// + public int CompressionLevel { get; init; } + + /// + /// SharpCompress will keep the supplied streams open. Default is true. + /// + public bool LeaveStreamOpen { get; init; } = true; + + /// + /// Encoding to use for archive entry names. + /// + public IArchiveEncoding ArchiveEncoding { get; init; } = new ArchiveEncoding(); + + /// + /// An optional progress reporter for tracking compression operations. + /// + public IProgress? Progress { get; init; } + /// /// Indicates if archive should be finalized (by 2 empty blocks) on close. /// - public bool FinalizeArchiveOnClose { get; } + public bool FinalizeArchiveOnClose { get; init; } = true; - public TarHeaderWriteFormat HeaderFormat { get; } + /// + /// The format to use when writing tar headers. + /// + public TarHeaderWriteFormat HeaderFormat { get; init; } = + TarHeaderWriteFormat.GNU_TAR_LONG_LINK; + /// + /// Creates a new TarWriterOptions instance with the specified compression type and finalization option. + /// + /// The compression type for the archive. + /// Whether to finalize the archive on close. + public TarWriterOptions(CompressionType compressionType, bool finalizeArchiveOnClose) + { + CompressionType = compressionType; + FinalizeArchiveOnClose = finalizeArchiveOnClose; + CompressionLevel = compressionType switch + { + CompressionType.ZStandard => 3, + _ => 0, + }; + } + + /// + /// Creates a new TarWriterOptions instance with the specified compression type, finalization option, and header format. + /// + /// The compression type for the archive. + /// Whether to finalize the archive on close. + /// The tar header format. public TarWriterOptions( CompressionType compressionType, bool finalizeArchiveOnClose, - TarHeaderWriteFormat headerFormat = TarHeaderWriteFormat.GNU_TAR_LONG_LINK + TarHeaderWriteFormat headerFormat ) - : base(compressionType) + : this(compressionType, finalizeArchiveOnClose) { - FinalizeArchiveOnClose = finalizeArchiveOnClose; HeaderFormat = headerFormat; } - internal TarWriterOptions(WriterOptions options) - : this(options.CompressionType, true) + /// + /// Creates a new TarWriterOptions instance from an existing WriterOptions instance. + /// + /// The WriterOptions to copy values from. + public TarWriterOptions(WriterOptions options) { + CompressionType = options.CompressionType; + CompressionLevel = options.CompressionLevel; LeaveStreamOpen = options.LeaveStreamOpen; + ArchiveEncoding = options.ArchiveEncoding; + Progress = options.Progress; + } + + /// + /// Creates a new TarWriterOptions instance from an existing IWriterOptions instance. + /// + /// The IWriterOptions to copy values from. + public TarWriterOptions(IWriterOptions options) + { + CompressionType = options.CompressionType; CompressionLevel = options.CompressionLevel; + LeaveStreamOpen = options.LeaveStreamOpen; ArchiveEncoding = options.ArchiveEncoding; + Progress = options.Progress; } + + /// + /// Implicit conversion from CompressionType to TarWriterOptions with finalize enabled. + /// + /// The compression type. + public static implicit operator TarWriterOptions(CompressionType compressionType) => + new(compressionType, true); } diff --git a/src/SharpCompress/Writers/WriterFactory.cs b/src/SharpCompress/Writers/WriterFactory.cs index 3d482541d..1d3927979 100644 --- a/src/SharpCompress/Writers/WriterFactory.cs +++ b/src/SharpCompress/Writers/WriterFactory.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using SharpCompress.Common; +using SharpCompress.Common.Options; namespace SharpCompress.Writers; @@ -11,7 +12,7 @@ public static class WriterFactory public static IWriter OpenWriter( string filePath, ArchiveType archiveType, - WriterOptions writerOptions + IWriterOptions writerOptions ) { filePath.NotNullOrEmpty(nameof(filePath)); @@ -21,7 +22,7 @@ WriterOptions writerOptions public static IWriter OpenWriter( FileInfo fileInfo, ArchiveType archiveType, - WriterOptions writerOptions + IWriterOptions writerOptions ) { fileInfo.NotNull(nameof(fileInfo)); @@ -31,7 +32,7 @@ WriterOptions writerOptions public static IAsyncWriter OpenAsyncWriter( string filePath, ArchiveType archiveType, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { @@ -47,7 +48,7 @@ public static IAsyncWriter OpenAsyncWriter( public static IAsyncWriter OpenAsyncWriter( FileInfo fileInfo, ArchiveType archiveType, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { @@ -63,7 +64,7 @@ public static IAsyncWriter OpenAsyncWriter( public static IWriter OpenWriter( Stream stream, ArchiveType archiveType, - WriterOptions writerOptions + IWriterOptions writerOptions ) { var factory = Factories @@ -89,7 +90,7 @@ WriterOptions writerOptions public static IAsyncWriter OpenAsyncWriter( Stream stream, ArchiveType archiveType, - WriterOptions writerOptions, + IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { diff --git a/src/SharpCompress/Writers/WriterOptions.cs b/src/SharpCompress/Writers/WriterOptions.cs index 46e145602..370379769 100644 --- a/src/SharpCompress/Writers/WriterOptions.cs +++ b/src/SharpCompress/Writers/WriterOptions.cs @@ -1,11 +1,72 @@ using System; using SharpCompress.Common; +using SharpCompress.Common.Options; using D = SharpCompress.Compressors.Deflate; namespace SharpCompress.Writers; -public class WriterOptions : OptionsBase +/// +/// Options for configuring writer behavior when creating archives. +/// +/// +/// This class is immutable. Use factory methods for creation: +/// +/// var options = WriterOptions.ForZip().WithLeaveStreamOpen(false).WithCompressionLevel(9); +/// +/// +public sealed record WriterOptions : IWriterOptions { + private CompressionType _compressionType; + private int _compressionLevel; + + /// + /// The compression type to use for the archive. + /// + public CompressionType CompressionType + { + get => _compressionType; + init => _compressionType = value; + } + + /// + /// The compression level to be used when the compression type supports variable levels. + /// Valid ranges depend on the compression algorithm: + /// - Deflate/GZip: 0-9 (0=no compression, 6=default, 9=best compression) + /// - ZStandard: 1-22 (1=fastest, 3=default, 22=best compression) + /// Note: BZip2 and LZMA do not support compression levels in this implementation. + /// Defaults are set automatically based on compression type in the constructor. + /// + public int CompressionLevel + { + get => _compressionLevel; + init + { + CompressionLevelValidation.Validate(CompressionType, value); + _compressionLevel = value; + } + } + + /// + /// SharpCompress will keep the supplied streams open. Default is true. + /// + public bool LeaveStreamOpen { get; init; } = true; + + /// + /// Encoding to use for archive entry names. + /// + public IArchiveEncoding ArchiveEncoding { get; init; } = new ArchiveEncoding(); + + /// + /// An optional progress reporter for tracking compression operations. + /// When set, progress updates will be reported as entries are written. + /// + public IProgress? Progress { get; init; } + + /// + /// Creates a new WriterOptions instance with the specified compression type. + /// Compression level is automatically set based on the compression type. + /// + /// The compression type for the archive. public WriterOptions(CompressionType compressionType) { CompressionType = compressionType; @@ -19,30 +80,46 @@ public WriterOptions(CompressionType compressionType) }; } + /// + /// Creates a new WriterOptions instance with the specified compression type and level. + /// + /// The compression type for the archive. + /// The compression level (algorithm-specific). public WriterOptions(CompressionType compressionType, int compressionLevel) { CompressionType = compressionType; CompressionLevel = compressionLevel; } - public CompressionType CompressionType { get; set; } + // Note: Constructors with boolean leaveStreamOpen parameter removed. + // Use the fluent WithLeaveStreamOpen() helper or object initializer instead: + // new WriterOptions(type) { LeaveStreamOpen = false } + // or + // WriterOptions.ForZip().WithLeaveStreamOpen(false) /// - /// The compression level to be used when the compression type supports variable levels. - /// Valid ranges depend on the compression algorithm: - /// - Deflate/GZip: 0-9 (0=no compression, 6=default, 9=best compression) - /// - ZStandard: 1-22 (1=fastest, 3=default, 22=best compression) - /// Note: BZip2 and LZMA do not support compression levels in this implementation. - /// Defaults are set automatically based on compression type in the constructor. + /// Implicit conversion from CompressionType to WriterOptions. /// - public int CompressionLevel { get; set; } + /// The compression type. + public static implicit operator WriterOptions(CompressionType compressionType) => + new(compressionType); /// - /// An optional progress reporter for tracking compression operations. - /// When set, progress updates will be reported as entries are written. + /// Creates a new ZipWriterOptions for writing ZIP archives. /// - public IProgress? Progress { get; set; } + /// The compression type for the archive. Defaults to Deflate. + public static WriterOptions ForZip(CompressionType compressionType = CompressionType.Deflate) => + new(compressionType); - public static implicit operator WriterOptions(CompressionType compressionType) => + /// + /// Creates a new WriterOptions for writing TAR archives. + /// + /// The compression type for the archive. Defaults to None. + public static WriterOptions ForTar(CompressionType compressionType = CompressionType.None) => new(compressionType); + + /// + /// Creates a new WriterOptions for writing GZip compressed files. + /// + public static WriterOptions ForGZip() => new(CompressionType.GZip); } diff --git a/src/SharpCompress/Writers/WriterOptionsExtensions.cs b/src/SharpCompress/Writers/WriterOptionsExtensions.cs new file mode 100644 index 000000000..d86b54060 --- /dev/null +++ b/src/SharpCompress/Writers/WriterOptionsExtensions.cs @@ -0,0 +1,55 @@ +using System; +using SharpCompress.Common; +using SharpCompress.Common.Options; + +namespace SharpCompress.Writers; + +/// +/// Extension methods for fluent configuration of writer options. +/// +public static class WriterOptionsExtensions +{ + /// + /// Creates a copy with the specified LeaveStreamOpen value. + /// + /// The source options. + /// Whether to leave the stream open. + /// A new options instance with the specified LeaveStreamOpen value. + public static WriterOptions WithLeaveStreamOpen( + this WriterOptions options, + bool leaveStreamOpen + ) => options with { LeaveStreamOpen = leaveStreamOpen }; + + /// + /// Creates a copy with the specified compression level. + /// + /// The source options. + /// The compression level (algorithm-specific). + /// A new options instance with the specified compression level. + public static WriterOptions WithCompressionLevel( + this WriterOptions options, + int compressionLevel + ) => options with { CompressionLevel = compressionLevel }; + + /// + /// Creates a copy with the specified archive encoding. + /// + /// The source options. + /// The archive encoding to use. + /// A new options instance with the specified archive encoding. + public static WriterOptions WithArchiveEncoding( + this WriterOptions options, + IArchiveEncoding archiveEncoding + ) => options with { ArchiveEncoding = archiveEncoding }; + + /// + /// Creates a copy with the specified progress reporter. + /// + /// The source options. + /// The progress reporter. + /// A new options instance with the specified progress reporter. + public static WriterOptions WithProgress( + this WriterOptions options, + IProgress progress + ) => options with { Progress = progress }; +} diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index fb21908e9..3edd2bb53 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -93,6 +93,7 @@ public void Write(string entryPath, Stream source, ZipWriterEntryOptions zipWrit public Stream WriteToStream(string entryPath, ZipWriterEntryOptions options) { + options.ValidateWithFallback(compressionType, compressionLevel); var compression = ToZipCompressionMethod(options.CompressionType ?? compressionType); entryPath = NormalizeFilename(entryPath); diff --git a/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs index dcadb21c8..d51f2bd13 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriterEntryOptions.cs @@ -1,12 +1,27 @@ using System; using SharpCompress.Common; using SharpCompress.Compressors.Deflate; +using SharpCompress.Writers; namespace SharpCompress.Writers.Zip; public class ZipWriterEntryOptions { - public CompressionType? CompressionType { get; set; } + private CompressionType? compressionType; + private int? compressionLevel; + + public CompressionType? CompressionType + { + get => compressionType; + set + { + if (value.HasValue && compressionLevel.HasValue) + { + CompressionLevelValidation.Validate(value.Value, compressionLevel.Value); + } + compressionType = value; + } + } /// /// The compression level to be used when the compression type supports variable levels. @@ -16,7 +31,18 @@ public class ZipWriterEntryOptions /// When null, uses the archive's default compression level for the specified compression type. /// Note: BZip2 and LZMA do not support compression levels in this implementation. /// - public int? CompressionLevel { get; set; } + public int? CompressionLevel + { + get => compressionLevel; + set + { + if (value.HasValue && compressionType.HasValue) + { + CompressionLevelValidation.Validate(compressionType.Value, value.Value); + } + compressionLevel = value; + } + } /// /// When CompressionType.Deflate is used, this property is referenced. @@ -49,4 +75,12 @@ public CompressionLevel? DeflateCompressionLevel /// This option is not supported with non-seekable streams. /// public bool? EnableZip64 { get; set; } + + internal void ValidateWithFallback(CompressionType fallbackCompressionType, int fallbackLevel) + { + CompressionLevelValidation.Validate( + CompressionType ?? fallbackCompressionType, + CompressionLevel ?? fallbackLevel + ); + } } diff --git a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs index 524ea7eb8..9ba8ea48c 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriterOptions.cs @@ -1,85 +1,142 @@ using System; using SharpCompress.Common; +using SharpCompress.Common.Options; using SharpCompress.Compressors.Deflate; +using SharpCompress.Writers; using D = SharpCompress.Compressors.Deflate; namespace SharpCompress.Writers.Zip; -public class ZipWriterOptions : WriterOptions +/// +/// Options for configuring Zip writer behavior. +/// +/// +/// This class is immutable. Use the with expression to create modified copies: +/// +/// var options = new ZipWriterOptions(CompressionType.Zip); +/// options = options with { UseZip64 = true }; +/// +/// +public sealed record ZipWriterOptions : IWriterOptions { - public ZipWriterOptions( - CompressionType compressionType, - CompressionLevel compressionLevel = D.CompressionLevel.Default - ) - : base(compressionType, (int)compressionLevel) { } + private CompressionType _compressionType; + private int _compressionLevel; - internal ZipWriterOptions(WriterOptions options) - : base(options.CompressionType) + /// + /// The compression type to use for the archive. + /// + public CompressionType CompressionType { - LeaveStreamOpen = options.LeaveStreamOpen; - ArchiveEncoding = options.ArchiveEncoding; - CompressionLevel = options.CompressionLevel; + get => _compressionType; + init => _compressionType = value; + } - if (options is ZipWriterOptions writerOptions) + /// + /// The compression level to be used when the compression type supports variable levels. + /// + public int CompressionLevel + { + get => _compressionLevel; + init { - UseZip64 = writerOptions.UseZip64; - ArchiveComment = writerOptions.ArchiveComment; + CompressionLevelValidation.Validate(CompressionType, value); + _compressionLevel = value; } } /// - /// Sets the compression level for Deflate compression (0-9). - /// This is a convenience method that sets the CompressionLevel property for Deflate compression. + /// SharpCompress will keep the supplied streams open. Default is true. /// - /// Deflate compression level (0=no compression, 6=default, 9=best compression) - public void SetDeflateCompressionLevel(CompressionLevel level) - { - CompressionLevel = (int)level; - } + public bool LeaveStreamOpen { get; init; } = true; + + /// + /// Encoding to use for archive entry names. + /// + public IArchiveEncoding ArchiveEncoding { get; init; } = new ArchiveEncoding(); + + /// + /// An optional progress reporter for tracking compression operations. + /// + public IProgress? Progress { get; init; } + + /// + /// Optional comment for the archive. + /// + public string? ArchiveComment { get; init; } + + /// + /// Sets a value indicating if zip64 support is enabled. + /// If this is not set, individual stream lengths cannot exceed 4 GiB. + /// This option is not supported for non-seekable streams. + /// Archives larger than 4GiB are supported as long as all streams + /// are less than 4GiB in length. + /// + public bool UseZip64 { get; init; } /// - /// Sets the compression level for ZStandard compression (1-22). - /// This is a convenience method that sets the CompressionLevel property for ZStandard compression. + /// Creates a new ZipWriterOptions instance with the specified compression type. /// - /// ZStandard compression level (1=fastest, 3=default, 22=best compression) - /// Thrown when level is not between 1 and 22 - public void SetZStandardCompressionLevel(int level) + /// The compression type for the archive. + public ZipWriterOptions(CompressionType compressionType) { - if (level < 1 || level > 22) + CompressionType = compressionType; + CompressionLevel = compressionType switch { - throw new ArgumentOutOfRangeException( - nameof(level), - "ZStandard compression level must be between 1 and 22" - ); - } + CompressionType.ZStandard => 3, + CompressionType.Deflate => (int)D.CompressionLevel.Default, + CompressionType.Deflate64 => (int)D.CompressionLevel.Default, + CompressionType.GZip => (int)D.CompressionLevel.Default, + _ => 0, + }; + } - CompressionLevel = level; + /// + /// Creates a new ZipWriterOptions instance with the specified compression type and level. + /// + /// The compression type for the archive. + /// The compression level (algorithm-specific). + public ZipWriterOptions(CompressionType compressionType, int compressionLevel) + { + CompressionType = compressionType; + CompressionLevel = compressionLevel; } /// - /// Legacy property for Deflate compression levels. - /// Valid range: 0-9 (0=no compression, 6=default, 9=best compression). + /// Creates a new ZipWriterOptions instance with the specified compression type and Deflate compression level. + /// + /// The compression type for the archive. + /// The Deflate compression level. + public ZipWriterOptions(CompressionType compressionType, D.CompressionLevel compressionLevel) + : this(compressionType, (int)compressionLevel) { } + + /// + /// Creates a new ZipWriterOptions instance from an existing WriterOptions instance. /// - /// - /// This property is deprecated. Use or instead. - /// - [Obsolete( - "Use CompressionLevel property or SetDeflateCompressionLevel method instead. This property will be removed in a future version." - )] - public CompressionLevel DeflateCompressionLevel + /// The WriterOptions to copy values from. + public ZipWriterOptions(WriterOptions options) + : this(options.CompressionType, options.CompressionLevel) { - get => (CompressionLevel)Math.Min(CompressionLevel, 9); - set => CompressionLevel = (int)value; + LeaveStreamOpen = options.LeaveStreamOpen; + ArchiveEncoding = options.ArchiveEncoding; + Progress = options.Progress; } - public string? ArchiveComment { get; set; } + /// + /// Creates a new ZipWriterOptions instance from an existing IWriterOptions instance. + /// + /// The IWriterOptions to copy values from. + public ZipWriterOptions(IWriterOptions options) + : this(options.CompressionType, options.CompressionLevel) + { + LeaveStreamOpen = options.LeaveStreamOpen; + ArchiveEncoding = options.ArchiveEncoding; + Progress = options.Progress; + } /// - /// Sets a value indicating if zip64 support is enabled. - /// If this is not set, individual stream lengths cannot exceed 4 GiB. - /// This option is not supported for non-seekable streams. - /// Archives larger than 4GiB are supported as long as all streams - /// are less than 4GiB in length. + /// Implicit conversion from CompressionType to ZipWriterOptions. /// - public bool UseZip64 { get; set; } + /// The compression type. + public static implicit operator ZipWriterOptions(CompressionType compressionType) => + new(compressionType); } diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 27e9e4968..29e7a1bd5 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -216,9 +216,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.2, )", - "resolved": "10.0.2", - "contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw==" + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -264,9 +264,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.23, )", - "resolved": "8.0.23", - "contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q==" + "requested": "[8.0.22, )", + "resolved": "8.0.22", + "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs b/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs index 4dde67802..62d10265e 100644 --- a/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Ace/AceReaderAsyncTests.cs @@ -80,10 +80,7 @@ private async Task ReadAsync(string testArchive, CompressionType expectedCompres if (!reader.Entry.IsDirectory) { Assert.Equal(expectedCompression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -105,10 +102,7 @@ CompressionType expectedCompression if (!reader.Entry.IsDirectory) { Assert.Equal(expectedCompression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } CompareFilesByPath( @@ -130,10 +124,7 @@ Func, IAsyncReader> readerFactory { if (!reader.Entry.IsDirectory) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 4d09ae84f..95e10214d 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -62,10 +62,7 @@ CompressionType compression } foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } stream.ThrowOnDispose = false; } @@ -151,10 +148,7 @@ string extension { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } catch (IndexOutOfRangeException) @@ -192,10 +186,7 @@ IEnumerable testArchives { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -224,10 +215,7 @@ IEnumerable testArchives { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -299,10 +287,7 @@ protected void ArchiveFileRead( { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -346,16 +331,7 @@ protected void ArchiveFileReadEx(string testArchive) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions - { - ExtractFullPath = true, - Overwrite = true, - PreserveAttributes = true, - PreserveFileTime = true, - } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFilesEx(); @@ -401,11 +377,9 @@ protected static IWriter CreateWriterWithLevel( int? compressionLevel = null ) { - var writerOptions = new ZipWriterOptions(compressionType); - if (compressionLevel.HasValue) - { - writerOptions.CompressionLevel = compressionLevel.Value; - } + var writerOptions = compressionLevel.HasValue + ? new WriterOptions(compressionType, compressionLevel.Value) + : new WriterOptions(compressionType); return WriterFactory.OpenWriter(stream, ArchiveType.Zip, writerOptions); } @@ -415,12 +389,9 @@ protected static IAsyncWriter CreateWriterWithLevelAsync( int? compressionLevel = null ) { - var writerOptions = new ZipWriterOptions(compressionType); - if (compressionLevel.HasValue) - { - writerOptions.CompressionLevel = compressionLevel.Value; - writerOptions.LeaveStreamOpen = true; - } + var writerOptions = compressionLevel.HasValue + ? new WriterOptions(compressionType, compressionLevel.Value) { LeaveStreamOpen = true } + : new WriterOptions(compressionType) { LeaveStreamOpen = true }; return WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.Zip, @@ -655,10 +626,7 @@ IEnumerable testArchives var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory) ) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } catch (IndexOutOfRangeException) diff --git a/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs b/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs index 6a32cdebf..17dfa9cf2 100644 --- a/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Arj/ArjReaderAsyncTests.cs @@ -104,10 +104,7 @@ private async Task ReadAsync(string testArchive, CompressionType? expectedCompre { Assert.Equal(expectedCompression.Value, reader.Entry.CompressionType); } - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -129,10 +126,7 @@ CompressionType expectedCompression if (!reader.Entry.IsDirectory) { Assert.Equal(expectedCompression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } CompareFilesByPath( @@ -155,10 +149,7 @@ Func, ValueTask> openReader { if (!reader.Entry.IsDirectory) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } diff --git a/tests/SharpCompress.Test/ExtractAll.cs b/tests/SharpCompress.Test/ExtractAll.cs index 1e047bc07..8b71edf3e 100644 --- a/tests/SharpCompress.Test/ExtractAll.cs +++ b/tests/SharpCompress.Test/ExtractAll.cs @@ -21,10 +21,9 @@ public class ExtractAllTests : TestBase public async ValueTask ExtractAllEntriesAsync(string archivePath) { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, archivePath); - var options = new ExtractionOptions() { ExtractFullPath = true, Overwrite = true }; await using var archive = await ArchiveFactory.OpenAsyncArchive(testArchive); - await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH, options); + await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } [Theory] @@ -38,9 +37,8 @@ public async ValueTask ExtractAllEntriesAsync(string archivePath) public void ExtractAllEntriesSync(string archivePath) { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, archivePath); - var options = new ExtractionOptions() { ExtractFullPath = true, Overwrite = true }; using var archive = ArchiveFactory.OpenArchive(testArchive); - archive.WriteToDirectory(SCRATCH_FILES_PATH, options); + archive.WriteToDirectory(SCRATCH_FILES_PATH); } } diff --git a/tests/SharpCompress.Test/ExtractAllEntriesTests.cs b/tests/SharpCompress.Test/ExtractAllEntriesTests.cs index 57bbcc505..cc8a32bd8 100644 --- a/tests/SharpCompress.Test/ExtractAllEntriesTests.cs +++ b/tests/SharpCompress.Test/ExtractAllEntriesTests.cs @@ -43,10 +43,7 @@ public void ExtractAllEntries_WithProgressReporting_SolidArchive() { if (!reader.Entry.IsDirectory) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); completed += reader.Entry.Size; var progress = completed / totalSize; diff --git a/tests/SharpCompress.Test/ExtractionTests.cs b/tests/SharpCompress.Test/ExtractionTests.cs index 2edb8110a..cf2330424 100644 --- a/tests/SharpCompress.Test/ExtractionTests.cs +++ b/tests/SharpCompress.Test/ExtractionTests.cs @@ -27,7 +27,11 @@ public void Extraction_ShouldHandleCaseInsensitivePathsOnWindows() using (var stream = File.Create(testArchive)) { using var writer = (ZipWriter) - WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate); + WriterFactory.OpenWriter( + stream, + ArchiveType.Zip, + new WriterOptions(CompressionType.Deflate) + ); // Create a test file to add to the archive var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile.txt"); @@ -43,12 +47,7 @@ public void Extraction_ShouldHandleCaseInsensitivePathsOnWindows() // This should not throw an exception even if Path.GetFullPath returns // a path with different casing than the actual directory - var exception = Record.Exception(() => - reader.WriteAllToDirectory( - extractPath, - new ExtractionOptions { ExtractFullPath = false, Overwrite = true } - ) - ); + var exception = Record.Exception(() => reader.WriteAllToDirectory(extractPath)); Assert.Null(exception); } @@ -72,7 +71,11 @@ public void Extraction_ShouldPreventPathTraversalAttacks() using (var stream = File.Create(testArchive)) { using var writer = (ZipWriter) - WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate); + WriterFactory.OpenWriter( + stream, + ArchiveType.Zip, + new WriterOptions(CompressionType.Deflate) + ); var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile2.txt"); File.WriteAllText(testFilePath, "Test content"); @@ -87,10 +90,7 @@ public void Extraction_ShouldPreventPathTraversalAttacks() using var reader = ReaderFactory.OpenReader(stream); var exception = Assert.Throws(() => - reader.WriteAllToDirectory( - extractPath, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ) + reader.WriteAllToDirectory(extractPath) ); Assert.Contains("outside of the destination", exception.Message); diff --git a/tests/SharpCompress.Test/GZip/AsyncTests.cs b/tests/SharpCompress.Test/GZip/AsyncTests.cs index ebc3c9203..b525a15b8 100644 --- a/tests/SharpCompress.Test/GZip/AsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/AsyncTests.cs @@ -29,10 +29,7 @@ public async ValueTask Reader_Async_Extract_All() #endif await using var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); - await reader.WriteAllToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH); // Just verify some files were extracted var extractedFiles = Directory.GetFiles( @@ -147,11 +144,7 @@ public async ValueTask Async_With_Cancellation_Token() cancellationToken: cts.Token ); - await reader.WriteAllToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, - cts.Token - ); + await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH, cts.Token); // Just verify some files were extracted var extractedFiles = Directory.GetFiles( diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs index 87f0b2032..3bcfb9100 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs @@ -7,6 +7,7 @@ using SharpCompress.Archives.Tar; using SharpCompress.Common; using SharpCompress.Test.Mocks; +using SharpCompress.Writers.GZip; using Xunit; namespace SharpCompress.Test.GZip; @@ -83,7 +84,10 @@ public async ValueTask GZip_Archive_NoAdd_Async() await Assert.ThrowsAsync(async () => await archive.AddEntryAsync("jpg\\test.jpg", File.OpenRead(jpg), closeStream: true) ); - await archive.SaveToAsync(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); + await archive.SaveToAsync( + Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), + new GZipWriterOptions() + ); } } diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs index b6ee03d3e..a33256a6d 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs @@ -5,6 +5,7 @@ using SharpCompress.Archives.GZip; using SharpCompress.Archives.Tar; using SharpCompress.Common; +using SharpCompress.Writers.GZip; using Xunit; namespace SharpCompress.Test.GZip; @@ -64,7 +65,7 @@ public void GZip_Archive_NoAdd() using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); using var archive = GZipArchive.OpenArchive(stream); Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); - archive.SaveTo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); + archive.SaveTo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz"), new GZipWriterOptions()); } [Fact] diff --git a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs index d23c379a4..60e115c08 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs @@ -27,7 +27,7 @@ public async ValueTask GZip_Writer_Generic_Async() var writer = WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.GZip, - CompressionType.GZip + new WriterOptions(CompressionType.GZip) ) ) { @@ -67,7 +67,7 @@ public void GZip_Writer_Generic_Bad_Compression_Async() => using var writer = WriterFactory.OpenWriter( new AsyncOnlyStream(stream), ArchiveType.GZip, - CompressionType.BZip2 + new WriterOptions(CompressionType.BZip2) ); }); diff --git a/tests/SharpCompress.Test/GZip/GZipWriterTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterTests.cs index 4d73e6e18..b7594d123 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterTests.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using SharpCompress.Common; using SharpCompress.Writers; using SharpCompress.Writers.GZip; @@ -22,7 +22,11 @@ public void GZip_Writer_Generic() ) ) using ( - var writer = WriterFactory.OpenWriter(stream, ArchiveType.GZip, CompressionType.GZip) + var writer = WriterFactory.OpenWriter( + stream, + ArchiveType.GZip, + new WriterOptions(CompressionType.GZip) + ) ) { writer.Write("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); @@ -61,7 +65,7 @@ public void GZip_Writer_Generic_Bad_Compression() => using var writer = WriterFactory.OpenWriter( stream, ArchiveType.GZip, - CompressionType.BZip2 + new WriterOptions(CompressionType.BZip2) ); }); diff --git a/tests/SharpCompress.Test/OptionsUsabilityTests.cs b/tests/SharpCompress.Test/OptionsUsabilityTests.cs new file mode 100644 index 000000000..603007c9b --- /dev/null +++ b/tests/SharpCompress.Test/OptionsUsabilityTests.cs @@ -0,0 +1,248 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using SharpCompress.Archives; +using SharpCompress.Common; +using SharpCompress.Readers; +using SharpCompress.Test.Mocks; +using SharpCompress.Writers; +using SharpCompress.Writers.GZip; +using SharpCompress.Writers.Zip; +using Xunit; + +namespace SharpCompress.Test; + +public class OptionsUsabilityTests : TestBase +{ + [Fact] + public void ReaderFactory_Stream_Default_Leaves_Stream_Open() + { + using var file = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip")); + using var testStream = new TestStream(file); + + using (var reader = ReaderFactory.OpenReader(testStream)) + { + reader.MoveToNextEntry(); + } + + Assert.False(testStream.IsDisposed); + } + + [Fact] + public void ArchiveFactory_Stream_Default_Leaves_Stream_Open() + { + using var file = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip")); + using var testStream = new TestStream(file); + + using (var archive = ArchiveFactory.OpenArchive(testStream)) + { + _ = archive.Entries; + } + + Assert.False(testStream.IsDisposed); + } + + [Fact] + public async Task ReaderFactory_Stream_Default_Leaves_Stream_Open_Async() + { + using var file = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip")); + using var testStream = new TestStream(file); + + await using ( + var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(testStream)) + ) + { + await reader.MoveToNextEntryAsync(); + } + + Assert.False(testStream.IsDisposed); + } + + [Fact] + public void WriterOptions_Invalid_CompressionLevels_Throw() + { + Assert.Throws(() => + new WriterOptions(CompressionType.Deflate, 10) + ); + Assert.Throws(() => + new WriterOptions(CompressionType.ZStandard, 0) + ); + Assert.Throws(() => + new WriterOptions(CompressionType.BZip2, 1) + ); + } + + [Fact] + public void ZipWriterOptions_Invalid_CompressionLevels_Throw() + { + Assert.Throws(() => + new ZipWriterOptions(CompressionType.Deflate, 10) + ); + Assert.Throws(() => + new ZipWriterOptions(CompressionType.ZStandard, 23) + ); + } + + [Fact] + public void GZipWriterOptions_Invalid_Settings_Throw() + { + Assert.Throws(() => new GZipWriterOptions(10)); + Assert.Throws(() => + new GZipWriterOptions { CompressionType = CompressionType.Deflate } + ); + } + + [Fact] + public void ZipWriterEntryOptions_Invalid_CompressionLevel_Throws() + { + using var destination = new MemoryStream(); + using var source = new MemoryStream(new byte[] { 1, 2, 3 }); + using var writer = new ZipWriter( + destination, + new ZipWriterOptions(CompressionType.Deflate) + ); + + var options = new ZipWriterEntryOptions { CompressionLevel = 11 }; + + Assert.Throws(() => + writer.Write("entry.bin", source, options) + ); + } + + [Fact] + public void WriterOptions_Factory_Methods_Create_Valid_Options() + { + // ForZip + var zipOptions = WriterOptions.ForZip(); + Assert.Equal(CompressionType.Deflate, zipOptions.CompressionType); + Assert.True(zipOptions.LeaveStreamOpen); + + // ForTar + var tarOptions = WriterOptions.ForTar(); + Assert.Equal(CompressionType.None, tarOptions.CompressionType); + + // ForGZip + var gzipOptions = WriterOptions.ForGZip(); + Assert.Equal(CompressionType.GZip, gzipOptions.CompressionType); + } + + [Fact] + public void WriterOptions_Fluent_Methods_Modify_Correctly() + { + var options = WriterOptions.ForZip().WithLeaveStreamOpen(false).WithCompressionLevel(9); + + Assert.Equal(CompressionType.Deflate, options.CompressionType); + Assert.Equal(9, options.CompressionLevel); + Assert.False(options.LeaveStreamOpen); + } + + [Fact] + public void WriterOptions_Factory_And_Fluent_Equivalent_To_Constructor() + { + // Factory + fluent approach + var factoryApproach = WriterOptions + .ForZip() + .WithLeaveStreamOpen(false) + .WithCompressionLevel(9); + + // Traditional constructor approach + var constructorApproach = new WriterOptions(CompressionType.Deflate) + { + CompressionLevel = 9, + LeaveStreamOpen = false, + }; + + Assert.Equal(factoryApproach.CompressionType, constructorApproach.CompressionType); + Assert.Equal(factoryApproach.CompressionLevel, constructorApproach.CompressionLevel); + Assert.Equal(factoryApproach.LeaveStreamOpen, constructorApproach.LeaveStreamOpen); + } + + [Fact] + public void ReaderOptions_Fluent_Methods_Modify_Correctly() + { + var options = new ReaderOptions() + .WithLeaveStreamOpen(false) + .WithPassword("secret") + .WithLookForHeader(true) + .WithBufferSize(65536); + + Assert.False(options.LeaveStreamOpen); + Assert.Equal("secret", options.Password); + Assert.True(options.LookForHeader); + Assert.Equal(65536, options.BufferSize); + } + + [Fact] + public void ReaderOptions_Fluent_And_Initializer_Equivalent() + { + // Fluent approach + var fluentApproach = new ReaderOptions() + .WithLeaveStreamOpen(false) + .WithPassword("secret") + .WithLookForHeader(true) + .WithOverwrite(false); + + // Object initializer approach + var initializerApproach = new ReaderOptions + { + LeaveStreamOpen = false, + Password = "secret", + LookForHeader = true, + Overwrite = false, + }; + + Assert.Equal(fluentApproach.LeaveStreamOpen, initializerApproach.LeaveStreamOpen); + Assert.Equal(fluentApproach.Password, initializerApproach.Password); + Assert.Equal(fluentApproach.LookForHeader, initializerApproach.LookForHeader); + Assert.Equal(fluentApproach.Overwrite, initializerApproach.Overwrite); + } + + [Fact] + public void ReaderOptions_Presets_Have_Correct_Defaults() + { + var external = ReaderOptions.ForExternalStream; + Assert.True(external.LeaveStreamOpen); + + var owned = ReaderOptions.ForOwnedFile; + Assert.False(owned.LeaveStreamOpen); + + var safe = ReaderOptions.SafeExtract; + Assert.False(safe.Overwrite); + + var flat = ReaderOptions.FlatExtract; + Assert.False(flat.ExtractFullPath); + Assert.True(flat.Overwrite); + } + + [Fact] + public void ReaderOptions_Factory_ForEncryptedArchive_Sets_Password() + { + var options = ReaderOptions.ForEncryptedArchive("myPassword"); + Assert.Equal("myPassword", options.Password); + + var noPassword = ReaderOptions.ForEncryptedArchive(); + Assert.Null(noPassword.Password); + } + + [Fact] + public void ReaderOptions_Factory_ForEncoding_Sets_Encoding() + { + var encoding = new ArchiveEncoding { Default = System.Text.Encoding.UTF8 }; + var options = ReaderOptions.ForEncoding(encoding); + Assert.Equal(encoding, options.ArchiveEncoding); + } + + [Fact] + public void ReaderOptions_Factory_ForSelfExtractingArchive_Configures_Correctly() + { + var options = ReaderOptions.ForSelfExtractingArchive("password"); + Assert.True(options.LookForHeader); + Assert.Equal("password", options.Password); + Assert.Equal(1_048_576, options.RewindableBufferSize); + + var noPassword = ReaderOptions.ForSelfExtractingArchive(); + Assert.True(noPassword.LookForHeader); + Assert.Null(noPassword.Password); + Assert.Equal(1_048_576, noPassword.RewindableBufferSize); + } +} diff --git a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs index 7b2fbe7e6..95fef9a8e 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs @@ -80,10 +80,7 @@ private async ValueTask ReadRarPasswordAsync(string testArchive, string? passwor if (!entry.IsDirectory) { Assert.Equal(CompressionType.Rar, entry.CompressionType); - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -107,10 +104,7 @@ protected async Task ArchiveFileReadPasswordAsync(string archiveName, string pas { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -141,10 +135,7 @@ private async ValueTask DoRar_test_invalid_exttime_ArchiveStreamReadAsync(string using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } @@ -158,10 +149,7 @@ public async ValueTask Rar_Jpg_ArchiveStreamRead_Async() { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -183,10 +171,7 @@ private async ValueTask DoRar_IsSolidArchiveCheckAsync(string filename) Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -273,10 +258,7 @@ private async ValueTask DoRar_Multi_ArchiveStreamReadAsync(string[] archives, bo Assert.Equal(archive.IsSolid, isSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } @@ -337,10 +319,7 @@ public async ValueTask Rar_Jpg_ArchiveFileRead_Async() { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -639,10 +618,7 @@ private async ValueTask ArchiveStreamReadAsync(string testArchive) using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } @@ -665,10 +641,7 @@ CompressionType compression if (!reader.Entry.IsDirectory) { Assert.Equal(compression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -676,10 +649,7 @@ await reader.WriteEntryToDirectoryAsync( await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } @@ -690,10 +660,7 @@ private async ValueTask ArchiveFileReadAsync(string testArchive) using var archive = ArchiveFactory.OpenArchive(testArchive); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } @@ -710,14 +677,52 @@ params string[] testArchives ); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } + /// + /// Tests for Issue #1050 - RAR extraction with WriteToDirectoryAsync creates folders + /// but places all files at the top level instead of in their subdirectories. + /// + [Fact] + public async ValueTask Rar_Issue1050_WriteToDirectoryAsync_ExtractsToSubdirectories() + { + var testFile = "Rar.issue1050.rar"; + using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile)); + await using var archive = RarArchive.OpenAsyncArchive(fileStream); + + // Extract using archive.WriteToDirectoryAsync without explicit options + await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH); + + // Verify files are in their subdirectories, not at the root + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "PhysicsBraid", "263825.tr11dtp")), + "File should be in PhysicsBraid subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Animations", "15441.tr11anim")), + "File should be in Animations subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766728.tr11dtp")), + "File should be in Braid subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp")), + "File should be in Braid subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "HeadBraid", "321353.tr11modeldata")), + "File should be in HeadBraid subdirectory" + ); + + // NOTE: The file size check is omitted because there's a separate pre-existing bug + // in the async RAR stream implementation that causes incorrect file sizes. + // This test only verifies the directory structure fix. + } + private async ValueTask ArchiveOpenStreamReadAsync( ReaderOptions? readerOptions, params string[] testArchives @@ -730,10 +735,7 @@ params string[] testArchives ); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } VerifyFiles(); } diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index c33511d46..72029d56d 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -79,10 +79,7 @@ private void ReadRarPassword(string testArchive, string? password) if (!entry.IsDirectory) { Assert.Equal(CompressionType.Rar, entry.CompressionType); - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } } @@ -106,10 +103,7 @@ protected void ArchiveFileReadPassword(string archiveName, string password) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -137,10 +131,7 @@ private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } @@ -154,10 +145,7 @@ public void Rar_Jpg_ArchiveStreamRead() { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -177,10 +165,7 @@ private void DoRar_IsSolidArchiveCheck(string filename) Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -266,10 +251,7 @@ private void DoRar_Multi_ArchiveStreamRead(string[] archives, bool isSolid) Assert.Equal(archive.IsSolid, isSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } @@ -327,10 +309,7 @@ public void Rar_Jpg_ArchiveFileRead() { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -722,6 +701,47 @@ public void Rar_StreamValidation_ThrowsOnTruncatedStream() Assert.Contains("unpacked file size does not match header", exception.Message); } + /// + /// Tests for Issue #1050 - RAR extraction with WriteToDirectory creates folders + /// but places all files at the top level instead of in their subdirectories. + /// + [Fact] + public void Rar_Issue1050_WriteToDirectory_ExtractsToSubdirectories() + { + var testFile = "Rar.issue1050.rar"; + using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile)); + using var archive = RarArchive.OpenArchive(fileStream); + + // Extract using archive.WriteToDirectory without explicit options + archive.WriteToDirectory(SCRATCH_FILES_PATH); + + // Verify files are in their subdirectories, not at the root + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "PhysicsBraid", "263825.tr11dtp")), + "File should be in PhysicsBraid subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Animations", "15441.tr11anim")), + "File should be in Animations subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766728.tr11dtp")), + "File should be in Braid subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp")), + "File should be in Braid subdirectory" + ); + Assert.True( + File.Exists(Path.Combine(SCRATCH_FILES_PATH, "HeadBraid", "321353.tr11modeldata")), + "File should be in HeadBraid subdirectory" + ); + + // Verify the exact file size of 766832.tr11dtp matches the archive entry size + var fileInfo = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp")); + Assert.Equal(4867620, fileInfo.Length); // Expected: 4,867,620 bytes + } + /// /// Test case for malformed RAR archives that previously caused infinite loops. /// This test verifies that attempting to read entries from a potentially malformed diff --git a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs index d81507a93..eb25b76cb 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs @@ -49,10 +49,7 @@ private async ValueTask DoRar_Multi_Reader_Async(string[] archives) IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -83,10 +80,7 @@ await Assert.ThrowsAsync(async () => IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -132,10 +126,7 @@ private async ValueTask DoRar_Multi_Reader_Delete_Files_Async(string[] archives) IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } foreach (var stream in streams) @@ -262,10 +253,7 @@ public async ValueTask Rar_Reader_Audio_program_Async() while (await reader.MoveToNextEntryAsync()) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } CompareFilesByPath( @@ -289,10 +277,7 @@ public async ValueTask Rar_Jpg_Reader_Async() while (await reader.MoveToNextEntryAsync()) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -334,10 +319,7 @@ private async ValueTask DoRar_Solid_Skip_Reader_Async(string filename) if (reader.Entry.Key.NotNull().Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -360,10 +342,7 @@ private async ValueTask DoRar_Reader_Skip_Async(string filename) if (reader.Entry.Key.NotNull().Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -385,10 +364,7 @@ private async ValueTask ReadAsync( if (!reader.Entry.IsDirectory) { Assert.Equal(expectedCompression, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } VerifyFiles(); diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index fd0be5933..beb3ad0e1 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -46,10 +46,7 @@ private void DoRar_Multi_Reader(string[] archives) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -80,10 +77,7 @@ private void DoRar_Multi_Reader_Encrypted(string[] archives) => { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -128,10 +122,7 @@ private void DoRar_Multi_Reader_Delete_Files(string[] archives) { while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } foreach (var stream in streams) @@ -236,10 +227,7 @@ public void Rar_Reader_Audio_program() while (reader.MoveToNextEntry()) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } CompareFilesByPath( @@ -259,10 +247,7 @@ public void Rar_Jpg_Reader() while (reader.MoveToNextEntry()) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -298,10 +283,7 @@ private void DoRar_Solid_Skip_Reader(string filename) if (reader.Entry.Key.NotNull().Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -324,10 +306,7 @@ private void DoRar_Reader_Skip(string filename) if (reader.Entry.Key.NotNull().Contains("jpg")) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index a6e1c25b9..6e5b4b9ec 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -41,11 +41,11 @@ Action readImpl testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); options ??= new ReaderOptions { BufferSize = 0x20000 }; - options.LeaveStreamOpen = true; - readImpl(testArchive, options); + var optionsWithStreamOpen = options with { LeaveStreamOpen = true }; + readImpl(testArchive, optionsWithStreamOpen); - options.LeaveStreamOpen = false; - readImpl(testArchive, options); + var optionsWithStreamClosed = options with { LeaveStreamOpen = false }; + readImpl(testArchive, optionsWithStreamClosed); VerifyFiles(); } @@ -89,10 +89,7 @@ protected void UseReader(IReader reader, CompressionType expectedCompression) if (!reader.Entry.IsDirectory) { Assert.Equal(expectedCompression, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -103,10 +100,7 @@ private void UseReader(IReader reader) { if (!reader.Entry.IsDirectory) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -141,11 +135,21 @@ protected async Task ReadAsync( options ??= new ReaderOptions() { BufferSize = 0x20000 }; - options.LeaveStreamOpen = true; - await ReadImplAsync(testArchive, expectedCompression, options, cancellationToken); + var optionsWithStreamOpen = options with { LeaveStreamOpen = true }; + await ReadImplAsync( + testArchive, + expectedCompression, + optionsWithStreamOpen, + cancellationToken + ); - options.LeaveStreamOpen = false; - await ReadImplAsync(testArchive, expectedCompression, options, cancellationToken); + var optionsWithStreamClosed = options with { LeaveStreamOpen = false }; + await ReadImplAsync( + testArchive, + expectedCompression, + optionsWithStreamClosed, + cancellationToken + ); VerifyFiles(); } @@ -203,11 +207,7 @@ public async ValueTask UseReaderAsync( Assert.Equal(expectedCompression, reader.Entry.CompressionType); } - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, - cancellationToken - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH, cancellationToken); } } } @@ -224,10 +224,7 @@ protected void ReadForBufferBoundaryCheck(string fileName, CompressionType compr { Assert.Equal(compressionType, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } CompareFilesByPath( @@ -271,10 +268,7 @@ Func, IReader> readerFactory while (reader.MoveToNextEntry()) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } VerifyFiles(); diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs index bcaf32b3e..ea9316e57 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs @@ -268,10 +268,7 @@ public void SevenZipArchive_Solid_ExtractAllEntries_Contiguous() { if (!reader.Entry.IsDirectory) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index 0759d726c..39df3cc77 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -148,8 +148,10 @@ public async ValueTask Tar_Create_New_Async() await using (var archive = TarArchive.CreateAsyncArchive()) { await archive.AddAllFromDirectoryAsync(ORIGINAL_FILES_PATH); - var twopt = new TarWriterOptions(CompressionType.None, true); - twopt.ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }; + var twopt = new TarWriterOptions(CompressionType.None, true) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; await archive.SaveToAsync(scratchPath, twopt); } CompareArchivesByPath(unmodified, scratchPath); @@ -166,7 +168,10 @@ public async ValueTask Tar_Random_Write_Add_Async() await using (var archive = TarArchive.OpenAsyncArchive(unmodified)) { await archive.AddEntryAsync("jpg\\test.jpg", jpg); - await archive.SaveToAsync(scratchPath, new WriterOptions(CompressionType.None)); + await archive.SaveToAsync( + scratchPath, + new TarWriterOptions(CompressionType.None, true) + ); } CompareArchivesByPath(modified, scratchPath); } @@ -184,7 +189,10 @@ public async ValueTask Tar_Random_Write_Remove_Async() x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ); await archive.RemoveEntryAsync(entry); - await archive.SaveToAsync(scratchPath, new WriterOptions(CompressionType.None)); + await archive.SaveToAsync( + scratchPath, + new TarWriterOptions(CompressionType.None, true) + ); } CompareArchivesByPath(modified, scratchPath); } @@ -196,8 +204,7 @@ public async ValueTask Tar_Japanese_Name_Async(int length) { using var mstm = new MemoryStream(); var enc = new ArchiveEncoding { Default = Encoding.UTF8 }; - var twopt = new TarWriterOptions(CompressionType.None, true); - twopt.ArchiveEncoding = enc; + var twopt = new TarWriterOptions(CompressionType.None, true) { ArchiveEncoding = enc }; var fname = new string((char)0x3042, length); using (var tw = new TarWriter(mstm, twopt)) using (var input = new MemoryStream(new byte[32])) diff --git a/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs index f5d3ad73b..08120d138 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs @@ -3,6 +3,7 @@ using System.Linq; using SharpCompress.Archives.Tar; using SharpCompress.Common; +using SharpCompress.Writers.Tar; using Xunit; namespace SharpCompress.Test.Tar; @@ -80,7 +81,7 @@ public void TarArchive_AddDirectoryEntry_SaveAndReload() using (var fileStream = File.Create(scratchPath)) { - archive.SaveTo(fileStream, CompressionType.None); + archive.SaveTo(fileStream, new TarWriterOptions(CompressionType.None, true)); } } diff --git a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs index af33dff7a..058806d17 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs @@ -34,7 +34,13 @@ public void Tar_FileName_Exactly_100_Characters() // Step 1: create a tar file containing a file with the test name using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) - using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, CompressionType.None)) + using ( + var writer = WriterFactory.OpenWriter( + stream, + ArchiveType.Tar, + new WriterOptions(CompressionType.None) + ) + ) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -94,7 +100,13 @@ public void Tar_VeryLongFilepathReadback() // Step 1: create a tar file containing a file with a long name using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) - using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, CompressionType.None)) + using ( + var writer = WriterFactory.OpenWriter( + stream, + ArchiveType.Tar, + new WriterOptions(CompressionType.None) + ) + ) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -162,8 +174,10 @@ public void Tar_Create_New() using (var archive = TarArchive.CreateArchive()) { archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); - var twopt = new TarWriterOptions(CompressionType.None, true); - twopt.ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }; + var twopt = new TarWriterOptions(CompressionType.None, true) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; archive.SaveTo(scratchPath, twopt); } CompareArchivesByPath(unmodified, scratchPath); @@ -180,7 +194,7 @@ public void Tar_Random_Write_Add() using (var archive = TarArchive.OpenArchive(unmodified)) { archive.AddEntry("jpg\\test.jpg", jpg); - archive.SaveTo(scratchPath, CompressionType.None); + archive.SaveTo(scratchPath, new TarWriterOptions(CompressionType.None, true)); } CompareArchivesByPath(modified, scratchPath); } @@ -198,7 +212,7 @@ public void Tar_Random_Write_Remove() x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ); archive.RemoveEntry(entry); - archive.SaveTo(scratchPath, CompressionType.None); + archive.SaveTo(scratchPath, new TarWriterOptions(CompressionType.None, true)); } CompareArchivesByPath(modified, scratchPath); } @@ -228,8 +242,7 @@ public void Tar_Japanese_Name(int length) { using var mstm = new MemoryStream(); var enc = new ArchiveEncoding { Default = Encoding.UTF8 }; - var twopt = new TarWriterOptions(CompressionType.None, true); - twopt.ArchiveEncoding = enc; + var twopt = new TarWriterOptions(CompressionType.None, true) { ArchiveEncoding = enc }; var fname = new string((char)0x3042, length); using (var tw = new TarWriter(mstm, twopt)) using (var input = new MemoryStream(new byte[32])) diff --git a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs index e2a25254a..871967540 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs @@ -33,10 +33,7 @@ public async ValueTask Tar_Skip_Async() x++; if (x % 2 == 0) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } diff --git a/tests/SharpCompress.Test/Tar/TarReaderTests.cs b/tests/SharpCompress.Test/Tar/TarReaderTests.cs index e8d7f1509..d76792cff 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderTests.cs @@ -31,10 +31,7 @@ public void Tar_Skip() x++; if (x % 2 == 0) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index f02b65d7a..4b66c0d9c 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -47,10 +47,7 @@ protected void Write( SharpCompressStream.CreateNonDisposing(stream), readerOptions ); - reader.WriteAllToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true } - ); + reader.WriteAllToDirectory(SCRATCH_FILES_PATH); } VerifyFiles(); } @@ -97,11 +94,7 @@ await writer.WriteAllAsync( readerOptions, cancellationToken ); - await reader.WriteAllToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true }, - cancellationToken - ); + await reader.WriteAllToDirectoryAsync(SCRATCH_FILES_PATH, cancellationToken); } VerifyFiles(); } diff --git a/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs b/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs index fa26a76ee..9a06560f9 100644 --- a/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs @@ -31,7 +31,7 @@ public void Zip64_Small_File_With_UseZip64_Should_Have_Matching_Versions() } // Create archive with UseZip64=true - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate) + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) { LeaveStreamOpen = false, UseZip64 = true, @@ -135,7 +135,7 @@ public void Zip64_Small_File_Without_UseZip64_Should_Have_Version_20() } // Create archive without UseZip64 - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate) + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) { LeaveStreamOpen = false, UseZip64 = false, @@ -186,7 +186,7 @@ public void LZMA_Compression_Should_Use_Version_63() File.Delete(filename); } - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.LZMA) + var writerOptions = new ZipWriterOptions(CompressionType.LZMA) { LeaveStreamOpen = false, UseZip64 = false, @@ -239,7 +239,7 @@ public void PPMd_Compression_Should_Use_Version_63() File.Delete(filename); } - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.PPMd) + var writerOptions = new ZipWriterOptions(CompressionType.PPMd) { LeaveStreamOpen = false, UseZip64 = false, @@ -292,7 +292,7 @@ public void Zip64_Multiple_Small_Files_With_UseZip64_Should_Have_Matching_Versio File.Delete(filename); } - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate) + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) { LeaveStreamOpen = false, UseZip64 = true, diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs index caba1e15c..5894d065a 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs @@ -133,8 +133,10 @@ public async ValueTask Zip_Random_Write_Remove_Async() ); await archive.RemoveEntryAsync(entry); - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; await archive.SaveToAsync(scratchPath, writerOptions); } @@ -153,8 +155,10 @@ public async ValueTask Zip_Random_Write_Add_Async() { await archive.AddEntryAsync("jpg\\test.jpg", jpg); - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; await archive.SaveToAsync(scratchPath, writerOptions); } @@ -172,8 +176,10 @@ public async ValueTask Zip_Create_New_Async() archive.DeflateCompressionLevel = CompressionLevel.BestSpeed; archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.UTF8; + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.UTF8 }, + }; await archive.SaveToAsync(scratchPath, writerOptions); } @@ -190,10 +196,7 @@ public async ValueTask Zip_Deflate_Entry_Stream_Async() { await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await entry.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } } finally @@ -212,10 +215,7 @@ public async ValueTask Zip_Deflate_Archive_WriteToDirectoryAsync() IAsyncArchive archive = ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); try { - await archive.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH); } finally { @@ -242,11 +242,7 @@ public async ValueTask Zip_Deflate_Archive_WriteToDirectoryAsync_WithProgress() await using IAsyncArchive archive = ZipArchive.OpenAsyncArchive( new AsyncOnlyStream(stream) ); - await archive.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, - progress - ); + await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH, progress); } await Task.Delay(1000); diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs index 183312cd9..7b6dc9819 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs @@ -3,6 +3,7 @@ using System.Linq; using SharpCompress.Archives.Zip; using SharpCompress.Common; +using SharpCompress.Writers.Zip; using Xunit; namespace SharpCompress.Test.Zip; @@ -80,7 +81,7 @@ public void ZipArchive_AddDirectoryEntry_SaveAndReload() using (var fileStream = File.Create(scratchPath)) { - archive.SaveTo(fileStream, CompressionType.Deflate); + archive.SaveTo(fileStream, new ZipWriterOptions(CompressionType.Deflate)); } } diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 1c0998df3..9040bf145 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -242,8 +242,10 @@ public void Zip_Random_Write_Remove() ); archive.RemoveEntry(entry); - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; archive.SaveTo(scratchPath, writerOptions); } @@ -262,8 +264,10 @@ public void Zip_Random_Write_Add() { archive.AddEntry("jpg\\test.jpg", jpg); - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; archive.SaveTo(scratchPath, writerOptions); } @@ -281,8 +285,8 @@ public void Zip_Save_Twice() var str = "test.txt"; var source = new MemoryStream(Encoding.UTF8.GetBytes(str)); arc.AddEntry("test.txt", source, true, source.Length); - arc.SaveTo(scratchPath1, CompressionType.Deflate); - arc.SaveTo(scratchPath2, CompressionType.Deflate); + arc.SaveTo(scratchPath1, new ZipWriterOptions(CompressionType.Deflate)); + arc.SaveTo(scratchPath2, new ZipWriterOptions(CompressionType.Deflate)); } Assert.Equal(new FileInfo(scratchPath1).Length, new FileInfo(scratchPath2).Length); @@ -330,8 +334,8 @@ public void Zip_Create_Same_Stream() { arc.AddEntry("1.txt", stream, false, stream.Length); arc.AddEntry("2.txt", stream, false, stream.Length); - arc.SaveTo(scratchPath1, CompressionType.Deflate); - arc.SaveTo(scratchPath2, CompressionType.Deflate); + arc.SaveTo(scratchPath1, new ZipWriterOptions(CompressionType.Deflate)); + arc.SaveTo(scratchPath2, new ZipWriterOptions(CompressionType.Deflate)); } } @@ -374,8 +378,10 @@ var file in Directory.EnumerateFiles( { archive.AddAllFromDirectory(SCRATCH_FILES_PATH); - WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate); - writerOptions.ArchiveEncoding.Default = Encoding.GetEncoding(866); + var writerOptions = new ZipWriterOptions(CompressionType.Deflate) + { + ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) }, + }; archive.SaveTo(scratchPath, writerOptions); } @@ -393,7 +399,7 @@ public void Zip_Create_Empty_And_Read() var archiveStream = new MemoryStream(); - archive.SaveTo(archiveStream, CompressionType.LZMA); + archive.SaveTo(archiveStream, new ZipWriterOptions(CompressionType.LZMA)); archiveStream.Position = 0; @@ -463,10 +469,7 @@ public void Zip_Deflate_WinzipAES_Read() { foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -515,10 +518,7 @@ public void Zip_BZip2_Pkware_Read() { foreach (var entry in reader.Entries.Where(x => !x.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } } VerifyFiles(); @@ -599,10 +599,7 @@ public void Zip_Evil_Throws_Exception_Windows() using var archive = ZipArchive.OpenArchive(zipFile); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { - entry.WriteToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + entry.WriteToDirectory(SCRATCH_FILES_PATH); } }); } @@ -622,7 +619,7 @@ public void TestSharpCompressWithEmptyStream() var zipWriter = WriterFactory.OpenWriter( stream, ArchiveType.Zip, - CompressionType.Deflate + new ZipWriterOptions(CompressionType.Deflate) ) ) { diff --git a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs index ece913bf9..fabbce67f 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs @@ -75,10 +75,7 @@ public async ValueTask Zip_Deflate_Streamed_Skip_Async() x++; if (x % 2 == 0) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -99,10 +96,7 @@ public async ValueTask Zip_Deflate_Streamed2_Skip_Async() x++; if (x % 2 == 0) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -159,10 +153,7 @@ public async ValueTask Zip_BZip2_PkwareEncryption_Read_Async() if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -175,16 +166,18 @@ public async ValueTask Zip_Reader_Disposal_Test_Async() using var stream = new TestStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); - await using (var reader = await ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream))) + await using ( + var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions().WithLeaveStreamOpen(false) + ) + ) { while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -204,10 +197,7 @@ public async ValueTask Zip_Reader_Disposal_Test2_Async() { if (!reader.Entry.IsDirectory) { - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } Assert.False(stream.IsDisposed); @@ -235,10 +225,7 @@ await Assert.ThrowsAsync(async () => if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -266,10 +253,7 @@ public async ValueTask Zip_Deflate_WinzipAES_Read_Async() if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); } } } @@ -297,10 +281,7 @@ public async ValueTask Zip_Deflate_ZipCrypto_Read_Async() if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.None, reader.Entry.CompressionType); - await reader.WriteEntryToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); count++; } } diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index 6460a766a..e4e6ee7ae 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -71,10 +71,7 @@ public void Zip_Deflate_Streamed_Skip() x++; if (x % 2 == 0) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -95,10 +92,7 @@ public void Zip_Deflate_Streamed2_Skip() x++; if (x % 2 == 0) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -142,10 +136,7 @@ public void Zip_BZip2_PkwareEncryption_Read() if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.BZip2, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -158,16 +149,18 @@ public void Zip_Reader_Disposal_Test() using var stream = new TestStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); - using (var reader = ReaderFactory.OpenReader(stream)) + using ( + var reader = ReaderFactory.OpenReader( + stream, + new ReaderOptions().WithLeaveStreamOpen(false) + ) + ) { while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -185,10 +178,7 @@ public void Zip_Reader_Disposal_Test2() { if (!reader.Entry.IsDirectory) { - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } Assert.False(stream.IsDisposed); @@ -212,10 +202,7 @@ public void Zip_LZMA_WinzipAES_Read() => if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -237,10 +224,7 @@ public void Zip_Deflate_WinzipAES_Read() if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); } } } @@ -259,10 +243,7 @@ public void Zip_Deflate_ZipCrypto_Read() if (!reader.Entry.IsDirectory) { Assert.Equal(CompressionType.None, reader.Entry.CompressionType); - reader.WriteEntryToDirectory( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + reader.WriteEntryToDirectory(SCRATCH_FILES_PATH); count++; } } @@ -286,7 +267,7 @@ public void TestSharpCompressWithEmptyStream() var zipWriter = WriterFactory.OpenWriter( stream, ArchiveType.Zip, - CompressionType.Deflate + new WriterOptions(CompressionType.Deflate) ) ) { diff --git a/tests/TestArchives/Archives/Rar.issue1050.rar b/tests/TestArchives/Archives/Rar.issue1050.rar new file mode 100644 index 000000000..704a01887 Binary files /dev/null and b/tests/TestArchives/Archives/Rar.issue1050.rar differ