diff --git a/.editorconfig b/.editorconfig index 96f2a953b..ec46ee724 100644 --- a/.editorconfig +++ b/.editorconfig @@ -307,7 +307,6 @@ dotnet_diagnostic.CS8602.severity = error dotnet_diagnostic.CS8604.severity = error dotnet_diagnostic.CS8618.severity = error dotnet_diagnostic.CS0618.severity = suggestion -dotnet_diagnostic.CS1998.severity = error dotnet_diagnostic.CS4014.severity = error dotnet_diagnostic.CS8600.severity = error dotnet_diagnostic.CS8603.severity = error diff --git a/.gitignore b/.gitignore index 6c6a863dc..17b167043 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ _ReSharper.SharpCompress/ bin/ *.suo *.user -TestArchives/Scratch/ -TestArchives/Scratch2/ +tests/TestArchives/Scratch/ +tests/TestArchives/Scratch2/ TestResults/ *.nupkg packages/*/ diff --git a/AGENTS.md b/AGENTS.md index 38ae56925..35e77d700 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -178,5 +178,4 @@ SharpCompress supports multiple archive and compression formats: 2. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction 3. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close) 4. **Tar + non-seekable stream** - Must provide file size or it will throw -5. **Multi-framework differences** - Some features differ between .NET Framework and modern .NET (e.g., Mono.Posix) 6. **Format detection** - Use `ReaderFactory.Open()` for auto-detection, test with actual archive files diff --git a/Directory.Build.props b/Directory.Build.props index b3d0b5954..f03c2d6e9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,5 +12,6 @@ False true true + true diff --git a/Directory.Packages.props b/Directory.Packages.props index 9f2e0c864..d5994bd76 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,7 @@ + diff --git a/SharpCompress.sln b/SharpCompress.sln index ba04063bf..af132f4c5 100644 --- a/SharpCompress.sln +++ b/SharpCompress.sln @@ -18,12 +18,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{CDB425 Directory.Build.props = Directory.Build.props global.json = global.json .editorconfig = .editorconfig + .gitignore = .gitignore Directory.Packages.props = Directory.Packages.props NuGet.config = NuGet.config .github\workflows\nuget-release.yml = .github\workflows\nuget-release.yml - USAGE.md = USAGE.md README.md = README.md - FORMATS.md = FORMATS.md AGENTS.md = AGENTS.md EndProjectSection EndProject diff --git a/docs/API.md b/docs/API.md index 88dbf2bb1..eb8f007a7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -8,49 +8,49 @@ Quick reference for commonly used SharpCompress APIs. ```csharp // Auto-detect format -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { // Works with Zip, Tar, GZip, Rar, 7Zip, etc. } // Specific format - Archive API -using (var archive = ZipArchive.Open("file.zip")) -using (var archive = TarArchive.Open("file.tar")) -using (var archive = RarArchive.Open("file.rar")) -using (var archive = SevenZipArchive.Open("file.7z")) -using (var archive = GZipArchive.Open("file.gz")) +using (var archive = ZipArchive.OpenArchive("file.zip")) +using (var archive = TarArchive.OpenArchive("file.tar")) +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 -{ +var options = new ReaderOptions +{ Password = "password", LeaveStreamOpen = true, ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) } }; -using (var archive = ZipArchive.Open("encrypted.zip", options)) +using (var archive = ZipArchive.OpenArchive("encrypted.zip", options)) ``` ### Creating Archives ```csharp // Writer Factory -using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate)) { // Write entries } // Specific writer -using (var archive = ZipArchive.Create()) -using (var archive = TarArchive.Create()) -using (var archive = GZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) +using (var archive = TarArchive.CreateArchive()) +using (var archive = GZipArchive.CreateArchive()) // With options -var options = new WriterOptions(CompressionType.Deflate) -{ +var options = new WriterOptions(CompressionType.Deflate) +{ CompressionLevel = 9, LeaveStreamOpen = false }; -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { archive.SaveTo("output.zip", options); } @@ -63,26 +63,26 @@ using (var archive = ZipArchive.Create()) ### Reading/Extracting ```csharp -using (var archive = ZipArchive.Open("file.zip")) +using (var archive = ZipArchive.OpenArchive("file.zip")) { // Get all entries - IEnumerable entries = archive.Entries; - + IEnumerable entries = archive.Entries; + // Find specific entry var entry = archive.Entries.FirstOrDefault(e => e.Key == "file.txt"); - + // Extract all archive.WriteToDirectory(@"C:\output", new ExtractionOptions { ExtractFullPath = true, Overwrite = true }); - + // 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()) { @@ -90,8 +90,15 @@ using (var archive = ZipArchive.Open("file.zip")) } } -// Async variants -await archive.WriteToDirectoryAsync(@"C:\output", options, cancellationToken); +// Async extraction (requires IAsyncArchive) +using (var asyncArchive = await ZipArchive.OpenAsyncArchive("file.zip")) +{ + await asyncArchive.WriteToDirectoryAsync( + @"C:\output", + new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, + cancellationToken: cancellationToken + ); +} using (var stream = await entry.OpenEntryStreamAsync(cancellationToken)) { // ... @@ -115,18 +122,18 @@ foreach (var entry in archive.Entries) ### Creating Archives ```csharp -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { // Add file - archive.AddEntry("file.txt", "C:\\source\\file.txt"); - + archive.AddEntry("file.txt", @"C:\source\file.txt"); + // Add multiple files - archive.AddAllFromDirectory("C:\\source"); - archive.AddAllFromDirectory("C:\\source", "*.txt"); // Pattern - + archive.AddAllFromDirectory(@"C:\source"); + archive.AddAllFromDirectory(@"C:\source", "*.txt"); // Pattern + // Save to file archive.SaveTo("output.zip", CompressionType.Deflate); - + // Save to stream archive.SaveTo(outputStream, new WriterOptions(CompressionType.Deflate) { @@ -144,18 +151,18 @@ using (var archive = ZipArchive.Create()) ```csharp using (var stream = File.OpenRead("file.zip")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { - IEntry entry = reader.Entry; - + IArchiveEntry entry = reader.Entry; + if (!entry.IsDirectory) { // Extract entry reader.WriteEntryToDirectory(@"C:\output"); reader.WriteEntryToFile(@"C:\output\file.txt"); - + // Or get stream using (var entryStream = reader.OpenEntryStream()) { @@ -165,16 +172,25 @@ using (var reader = ReaderFactory.Open(stream)) } } -// Async variants -while (await reader.MoveToNextEntryAsync()) +// Async variants (use OpenAsyncReader to get IAsyncReader) +using (var stream = File.OpenRead("file.zip")) +using (var reader = await ReaderFactory.OpenAsyncReader(stream)) { - await reader.WriteEntryToFileAsync(@"C:\output\" + reader.Entry.Key, cancellationToken); -} + while (await reader.MoveToNextEntryAsync()) + { + await reader.WriteEntryToFileAsync( + @"C:\output\" + reader.Entry.Key, + cancellationToken: cancellationToken + ); + } -// Async extraction -await reader.WriteAllToDirectoryAsync(@"C:\output", - new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, - cancellationToken); + // Async extraction of all entries + await reader.WriteAllToDirectoryAsync( + @"C:\output", + new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, + cancellationToken + ); +} ``` --- @@ -185,7 +201,7 @@ await reader.WriteAllToDirectoryAsync(@"C:\output", ```csharp using (var stream = File.Create("output.zip")) -using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate)) { // Write single file using (var fileStream = File.OpenRead("source.txt")) @@ -223,7 +239,7 @@ var options = new ReaderOptions Default = Encoding.GetEncoding(932) } }; -using (var archive = ZipArchive.Open("file.zip", options)) +using (var archive = ZipArchive.OpenArchive("file.zip", options)) { // ... } @@ -262,15 +278,20 @@ archive.WriteToDirectory(@"C:\output", options); // For creating archives CompressionType.None // No compression (store) CompressionType.Deflate // DEFLATE (default for ZIP/GZip) +CompressionType.Deflate64 // Deflate64 CompressionType.BZip2 // BZip2 CompressionType.LZMA // LZMA (for 7Zip, LZip, XZ) CompressionType.PPMd // PPMd (for ZIP) CompressionType.Rar // RAR compression (read-only) - -// For Tar archives -// Use CompressionType in TarWriter constructor -using (var writer = TarWriter(stream, CompressionType.GZip)) // Tar.GZip -using (var writer = TarWriter(stream, CompressionType.BZip2)) // Tar.BZip2 +CompressionType.ZStandard // ZStandard +ArchiveType.Arc +ArchiveType.Arj +ArchiveType.Ace + +// For Tar archives with compression +// Use WriterFactory to create compressed tar archives +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, CompressionType.GZip)) // Tar.GZip +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, CompressionType.BZip2)) // Tar.BZip2 ``` ### Archive Types @@ -328,7 +349,7 @@ var progress = new Progress(report => }); var options = new ReaderOptions { Progress = progress }; -using (var archive = ZipArchive.Open("archive.zip", options)) +using (var archive = ZipArchive.OpenArchive("archive.zip", options)) { archive.WriteToDirectory(@"C:\output"); } @@ -342,11 +363,13 @@ cts.CancelAfter(TimeSpan.FromMinutes(5)); try { - using (var archive = ZipArchive.Open("archive.zip")) + using (var archive = await ZipArchive.OpenAsyncArchive("archive.zip")) { - await archive.WriteToDirectoryAsync(@"C:\output", + await archive.WriteToDirectoryAsync( + @"C:\output", new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, - cts.Token); + cancellationToken: cts.Token + ); } } catch (OperationCanceledException) @@ -358,23 +381,23 @@ catch (OperationCanceledException) ### Create with Custom Compression ```csharp -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory(@"D:\source"); // Fastest - archive.SaveTo("fast.zip", new WriterOptions(CompressionType.Deflate) - { - CompressionLevel = 1 + archive.SaveTo("fast.zip", new WriterOptions(CompressionType.Deflate) + { + CompressionLevel = 1 }); // Balanced (default) archive.SaveTo("normal.zip", CompressionType.Deflate); // Best compression - archive.SaveTo("best.zip", new WriterOptions(CompressionType.Deflate) - { - CompressionLevel = 9 + archive.SaveTo("best.zip", new WriterOptions(CompressionType.Deflate) + { + CompressionLevel = 9 }); } ``` @@ -383,7 +406,7 @@ using (var archive = ZipArchive.Create()) ```csharp using (var outputStream = new MemoryStream()) -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { // Add content from memory using (var contentStream = new MemoryStream(Encoding.UTF8.GetBytes("Hello"))) @@ -402,7 +425,7 @@ using (var archive = ZipArchive.Create()) ### Extract Specific Files ```csharp -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { var filesToExtract = new[] { "file1.txt", "file2.txt" }; @@ -416,7 +439,7 @@ using (var archive = ZipArchive.Open("archive.zip")) ### List Archive Contents ```csharp -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { foreach (var entry in archive.Entries) { @@ -436,7 +459,7 @@ using (var archive = ZipArchive.Open("archive.zip")) ```csharp var stream = File.OpenRead("archive.zip"); -var archive = ZipArchive.Open(stream); +var archive = ZipArchive.OpenArchive(stream); archive.WriteToDirectory(@"C:\output"); // stream not disposed - leaked resource ``` @@ -445,7 +468,7 @@ archive.WriteToDirectory(@"C:\output"); ```csharp using (var stream = File.OpenRead("archive.zip")) -using (var archive = ZipArchive.Open(stream)) +using (var archive = ZipArchive.OpenArchive(stream)) { archive.WriteToDirectory(@"C:\output"); } @@ -456,7 +479,7 @@ using (var archive = ZipArchive.Open(stream)) ```csharp // Loading entire archive then iterating -using (var archive = ZipArchive.Open("large.zip")) +using (var archive = ZipArchive.OpenArchive("large.zip")) { var entries = archive.Entries.ToList(); // Loads all in memory foreach (var e in entries) @@ -471,7 +494,7 @@ using (var archive = ZipArchive.Open("large.zip")) ```csharp // Streaming iteration using (var stream = File.OpenRead("large.zip")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0ba6f5819..2a3bb985e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -76,7 +76,7 @@ Factory classes for auto-detecting archive format and creating appropriate reade - Format-specific: `ZipFactory.cs`, `TarFactory.cs`, `RarFactory.cs`, etc. **How It Works:** -1. `ReaderFactory.Open(stream)` probes stream signatures +1. `ReaderFactory.OpenReader(stream)` probes stream signatures 2. Identifies format by magic bytes 3. Creates appropriate reader instance 4. Returns generic `IReader` interface @@ -142,7 +142,7 @@ Stream wrappers and utilities. **Example:** ```csharp // User calls factory -using (var reader = ReaderFactory.Open(stream)) // Returns IReader +using (var reader = ReaderFactory.OpenReader(stream)) // Returns IReader { while (reader.MoveToNextEntry()) { @@ -175,7 +175,7 @@ CompressionType.LZMA // LZMA CompressionType.PPMd // PPMd // Writer uses strategy pattern -var archive = ZipArchive.Create(); +var archive = ZipArchive.CreateArchive(); archive.SaveTo("output.zip", CompressionType.Deflate); // Use Deflate archive.SaveTo("output.bz2", CompressionType.BZip2); // Use BZip2 ``` @@ -248,7 +248,7 @@ foreach (var entry in entries) } // Reader API - provides iterator -IReader reader = ReaderFactory.Open(stream); +IReader reader = ReaderFactory.OpenReader(stream); while (reader.MoveToNextEntry()) { // Forward-only iteration - one entry at a time @@ -381,7 +381,7 @@ public class NewFormatArchive : AbstractArchive private NewFormatHeader _header; private List _entries; - public static NewFormatArchive Open(Stream stream) + public static NewFormatArchive OpenArchive(Stream stream) { var archive = new NewFormatArchive(); archive._header = NewFormatHeader.Read(stream); @@ -442,8 +442,8 @@ public class NewFormatFactory : Factory, IArchiveFactory, IReaderFactory public static NewFormatFactory Instance { get; } = new(); - public IArchive CreateArchive(Stream stream) - => NewFormatArchive.Open(stream); + public IArchive CreateArchive(Stream stream) + => NewFormatArchive.OpenArchive(stream); public IReader CreateReader(Stream stream, ReaderOptions options) => new NewFormatReader(stream) { Options = options }; @@ -481,7 +481,7 @@ public class NewFormatTests : TestBase public void NewFormat_Extracts_Successfully() { var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "archive.newformat"); - using (var archive = NewFormatArchive.Open(archivePath)) + using (var archive = NewFormatArchive.OpenArchive(archivePath)) { archive.WriteToDirectory(SCRATCH_FILES_PATH); // Assert extraction @@ -561,7 +561,7 @@ public class CustomStream : Stream ```csharp // Correct: Nested using blocks using (var fileStream = File.OpenRead("archive.zip")) -using (var archive = ZipArchive.Open(fileStream)) +using (var archive = ZipArchive.OpenArchive(fileStream)) { archive.WriteToDirectory(@"C:\output"); } @@ -570,7 +570,7 @@ using (var archive = ZipArchive.Open(fileStream)) // Correct: Using with options var options = new ReaderOptions { LeaveStreamOpen = true }; var stream = File.OpenRead("archive.zip"); -using (var archive = ZipArchive.Open(stream, options)) +using (var archive = ZipArchive.OpenArchive(stream, options)) { archive.WriteToDirectory(@"C:\output"); } @@ -641,7 +641,7 @@ public void Archive_Extraction_Works() var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "test.zip"); // Act - using (var archive = ZipArchive.Open(testArchive)) + using (var archive = ZipArchive.OpenArchive(testArchive)) { archive.WriteToDirectory(SCRATCH_FILES_PATH); } diff --git a/docs/ENCODING.md b/docs/ENCODING.md index 1737ace93..8200e756f 100644 --- a/docs/ENCODING.md +++ b/docs/ENCODING.md @@ -27,7 +27,7 @@ var options = new ReaderOptions } }; -using (var archive = ZipArchive.Open("japanese.zip", options)) +using (var archive = ZipArchive.OpenArchive("japanese.zip", options)) { foreach (var entry in archive.Entries) { @@ -51,7 +51,7 @@ var options = new ReaderOptions { ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) } }; -using (var archive = ZipArchive.Open("file.zip", options)) +using (var archive = ZipArchive.OpenArchive("file.zip", options)) { // Use archive with correct encoding } @@ -64,7 +64,7 @@ var options = new ReaderOptions ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) } }; using (var stream = File.OpenRead("file.zip")) -using (var reader = ReaderFactory.Open(stream, options)) +using (var reader = ReaderFactory.OpenReader(stream, options)) { while (reader.MoveToNextEntry()) { @@ -89,7 +89,7 @@ var options = new ReaderOptions Default = Encoding.GetEncoding(932) } }; -using (var archive = ZipArchive.Open("japanese.zip", options)) +using (var archive = ZipArchive.OpenArchive("japanese.zip", options)) { // Correctly decodes Japanese filenames } @@ -266,7 +266,7 @@ SharpCompress attempts to auto-detect encoding, but this isn't always reliable: ```csharp // Auto-detection (default) -using (var archive = ZipArchive.Open("file.zip")) // Uses UTF8 by default +using (var archive = ZipArchive.OpenArchive("file.zip")) // Uses UTF8 by default { // May show corrupted characters if archive uses different encoding } @@ -276,7 +276,7 @@ var options = new ReaderOptions { ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(932) } }; -using (var archive = ZipArchive.Open("file.zip", options)) +using (var archive = ZipArchive.OpenArchive("file.zip", options)) { // Correct characters displayed } @@ -324,7 +324,7 @@ var options = new ReaderOptions } }; -using (var archive = ZipArchive.Open("mixed.zip", options)) +using (var archive = ZipArchive.OpenArchive("mixed.zip", options)) { foreach (var entry in archive.Entries) { @@ -388,7 +388,7 @@ var options = new ReaderOptions } }; -using (var archive = ZipArchive.Open("japanese_files.zip", options)) +using (var archive = ZipArchive.OpenArchive("japanese_files.zip", options)) { archive.WriteToDirectory(@"C:\output", new ExtractionOptions { @@ -410,7 +410,7 @@ var options = new ReaderOptions } }; -using (var archive = ZipArchive.Open("french_files.zip", options)) +using (var archive = ZipArchive.OpenArchive("french_files.zip", options)) { archive.WriteToDirectory(@"C:\output"); } @@ -428,7 +428,7 @@ var options = new ReaderOptions } }; -using (var archive = ZipArchive.Open("chinese_files.zip", options)) +using (var archive = ZipArchive.OpenArchive("chinese_files.zip", options)) { archive.WriteToDirectory(@"C:\output"); } @@ -445,7 +445,7 @@ var options = new ReaderOptions } }; -using (var archive = ZipArchive.Open("russian_files.zip", options)) +using (var archive = ZipArchive.OpenArchive("russian_files.zip", options)) { archive.WriteToDirectory(@"C:\output"); } @@ -463,7 +463,7 @@ var options = new ReaderOptions }; using (var stream = File.OpenRead("japanese.zip")) -using (var reader = ReaderFactory.Open(stream, options)) +using (var reader = ReaderFactory.OpenReader(stream, options)) { while (reader.MoveToNextEntry()) { @@ -484,7 +484,7 @@ When creating archives, SharpCompress uses UTF8 by default (recommended): ```csharp // Create with UTF8 (default, recommended) -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory(@"D:\my_files"); archive.SaveTo("output.zip", CompressionType.Deflate); diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index 03c12553b..51b2f9206 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -24,7 +24,7 @@ Choose the right API based on your use case: // - You need random access to entries // - Stream is seekable (file, MemoryStream) -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { // Random access - all entries available var specific = archive.Entries.FirstOrDefault(e => e.Key == "file.txt"); @@ -51,7 +51,7 @@ using (var archive = ZipArchive.Open("archive.zip")) // - Forward-only processing is acceptable using (var stream = File.OpenRead("large.zip")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -129,7 +129,7 @@ For processing archives from downloads or pipes: ```csharp // Download stream (non-seekable) using (var httpStream = await httpClient.GetStreamAsync(url)) -using (var reader = ReaderFactory.Open(httpStream)) +using (var reader = ReaderFactory.OpenReader(httpStream)) { // Process entries as they arrive while (reader.MoveToNextEntry()) @@ -159,14 +159,14 @@ Choose based on your constraints: ```csharp // Download then extract (requires disk space) var archivePath = await DownloadFile(url, @"C:\temp\archive.zip"); -using (var archive = ZipArchive.Open(archivePath)) +using (var archive = ZipArchive.OpenArchive(archivePath)) { archive.WriteToDirectory(@"C:\output"); } // Stream during download (on-the-fly extraction) using (var httpStream = await httpClient.GetStreamAsync(url)) -using (var reader = ReaderFactory.Open(httpStream)) +using (var reader = ReaderFactory.OpenReader(httpStream)) { while (reader.MoveToNextEntry()) { @@ -198,7 +198,7 @@ Extracting File3 requires decompressing File1 and File2 first. **Random Extraction (Slow):** ```csharp -using (var archive = RarArchive.Open("solid.rar")) +using (var archive = RarArchive.OpenArchive("solid.rar")) { foreach (var entry in archive.Entries) { @@ -210,7 +210,7 @@ using (var archive = RarArchive.Open("solid.rar")) **Sequential Extraction (Fast):** ```csharp -using (var archive = RarArchive.Open("solid.rar")) +using (var archive = RarArchive.OpenArchive("solid.rar")) { // Method 1: Use WriteToDirectory (recommended) archive.WriteToDirectory(@"C:\output", new ExtractionOptions @@ -256,7 +256,7 @@ using (var archive = RarArchive.Open("solid.rar")) // Level 9 = Slowest, best compression // Write with different compression levels -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory(@"D:\data"); @@ -293,7 +293,7 @@ using (var archive = ZipArchive.Create()) // Smaller block size = lower memory, faster // Larger block size = better compression, slower -using (var archive = TarArchive.Create()) +using (var archive = TarArchive.CreateArchive()) { archive.AddAllFromDirectory(@"D:\data"); @@ -313,7 +313,7 @@ LZMA compression is very powerful but memory-intensive: // - Better compression: larger dictionary // Preset via CompressionType -using (var archive = TarArchive.Create()) +using (var archive = TarArchive.CreateArchive()) { archive.AddAllFromDirectory(@"D:\data"); archive.SaveTo("archive.tar.xz", CompressionType.LZMA); // Default settings @@ -333,7 +333,7 @@ Async is beneficial when: ```csharp // Async extraction (non-blocking) -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { await archive.WriteToDirectoryAsync( @"C:\output", @@ -353,7 +353,7 @@ Async doesn't improve performance for: ```csharp // Sync extraction (simpler, same performance on fast I/O) -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { archive.WriteToDirectory( @"C:\output", @@ -373,7 +373,7 @@ cts.CancelAfter(TimeSpan.FromMinutes(5)); try { - using (var archive = ZipArchive.Open("archive.zip")) + using (var archive = ZipArchive.OpenArchive("archive.zip")) { await archive.WriteToDirectoryAsync( @"C:\output", @@ -408,14 +408,14 @@ catch (OperationCanceledException) // ✗ Slow - opens each archive separately foreach (var file in files) { - using (var archive = ZipArchive.Open("archive.zip")) + using (var archive = ZipArchive.OpenArchive("archive.zip")) { archive.WriteToDirectory(@"C:\output"); } } // ✓ Better - process multiple entries at once -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { archive.WriteToDirectory(@"C:\output"); } @@ -425,7 +425,7 @@ using (var archive = ZipArchive.Open("archive.zip")) ```csharp var sw = Stopwatch.StartNew(); -using (var archive = ZipArchive.Open("large.zip")) +using (var archive = ZipArchive.OpenArchive("large.zip")) { archive.WriteToDirectory(@"C:\output"); } diff --git a/docs/USAGE.md b/docs/USAGE.md index 8e636cab8..52d690a32 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -48,7 +48,7 @@ Also, look over the tests for more thorough [examples](https://github.com/adamha ### Create Zip Archive from multiple files ```C# -using(var archive = ZipArchive.Create()) +using(var archive = ZipArchive.CreateArchive()) { archive.AddEntry("file01.txt", "C:\\file01.txt"); archive.AddEntry("file02.txt", "C:\\file02.txt"); @@ -61,7 +61,7 @@ using(var archive = ZipArchive.Create()) ### Create Zip Archive from all files in a directory to a file ```C# -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory("D:\\temp"); archive.SaveTo("C:\\temp.zip", CompressionType.Deflate); @@ -72,7 +72,7 @@ using (var archive = ZipArchive.Create()) ```C# var memoryStream = new MemoryStream(); -using (var archive = ZipArchive.Create()) +using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory("D:\\temp"); archive.SaveTo(memoryStream, new WriterOptions(CompressionType.Deflate) @@ -90,7 +90,7 @@ 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.Open("Test.rar")) +using (var archive = RarArchive.OpenArchive("Test.rar")) { // Simple extraction with RarArchive; this WriteToDirectory pattern works for all archive types archive.WriteToDirectory(@"D:\temp", new ExtractionOptions() @@ -104,7 +104,7 @@ using (var archive = RarArchive.Open("Test.rar")) ### Iterate over all files from a Rar file using RarArchive ```C# -using (var archive = RarArchive.Open("Test.rar")) +using (var archive = RarArchive.OpenArchive("Test.rar")) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -126,7 +126,7 @@ var progress = new Progress(report => Console.WriteLine($"Extracting {report.EntryPath}: {report.PercentComplete}%"); }); -using (var archive = RarArchive.Open("archive.rar", new ReaderOptions { Progress = progress })) // Must be solid Rar or 7Zip +using (var archive = RarArchive.OpenArchive("archive.rar", new ReaderOptions { Progress = progress })) // Must be solid Rar or 7Zip { archive.WriteToDirectory(@"D:\output", new ExtractionOptions() { @@ -140,7 +140,7 @@ using (var archive = RarArchive.Open("archive.rar", new ReaderOptions { Progress ```C# using (Stream stream = File.OpenRead("Tar.tar.bz2")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -161,7 +161,7 @@ using (var reader = ReaderFactory.Open(stream)) ```C# using (Stream stream = File.OpenRead("Tar.tar.bz2")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -180,7 +180,7 @@ using (var reader = ReaderFactory.Open(stream)) ```C# using (Stream stream = File.OpenWrite("C:\\temp.tgz")) -using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip) +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip) { LeaveOpenStream = true })) @@ -199,7 +199,7 @@ opts.ArchiveEncoding.CustomDecoder = (data, x, y) => { return encoding.GetString(data); }; -var tr = SharpCompress.Archives.Zip.ZipArchive.Open("test.zip", opts); +var tr = SharpCompress.Archives.Zip.ZipArchive.OpenArchive("test.zip", opts); foreach(var entry in tr.Entries) { Console.WriteLine($"{entry.Key}"); @@ -213,7 +213,7 @@ foreach(var entry in tr.Entries) **Extract single entry asynchronously:** ```C# using (Stream stream = File.OpenRead("archive.zip")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -234,7 +234,7 @@ using (var reader = ReaderFactory.Open(stream)) **Extract all entries asynchronously:** ```C# using (Stream stream = File.OpenRead("archive.tar.gz")) -using (var reader = ReaderFactory.Open(stream)) +using (var reader = ReaderFactory.OpenReader(stream)) { await reader.WriteAllToDirectoryAsync( @"D:\temp", @@ -250,7 +250,7 @@ using (var reader = ReaderFactory.Open(stream)) **Open and process entry stream asynchronously:** ```C# -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) { @@ -268,7 +268,7 @@ using (var archive = ZipArchive.Open("archive.zip")) **Write single file asynchronously:** ```C# using (Stream archiveStream = File.OpenWrite("output.zip")) -using (var writer = WriterFactory.Open(archiveStream, ArchiveType.Zip, CompressionType.Deflate)) +using (var writer = WriterFactory.OpenWriter(archiveStream, ArchiveType.Zip, CompressionType.Deflate)) { using (Stream fileStream = File.OpenRead("input.txt")) { @@ -280,7 +280,7 @@ using (var writer = WriterFactory.Open(archiveStream, ArchiveType.Zip, Compressi **Write entire directory asynchronously:** ```C# using (Stream stream = File.OpenWrite("backup.tar.gz")) -using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip))) +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, new WriterOptions(CompressionType.GZip))) { await writer.WriteAllAsync( @"D:\files", @@ -299,7 +299,7 @@ var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromMinutes(5)); using (Stream stream = File.OpenWrite("archive.zip")) -using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) +using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate)) { try { @@ -316,7 +316,7 @@ using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType. **Extract from archive asynchronously:** ```C# -using (var archive = ZipArchive.Open("archive.zip")) +using (var archive = ZipArchive.OpenArchive("archive.zip")) { // Simple async extraction - works for all archive types await archive.WriteToDirectoryAsync( diff --git a/src/SharpCompress/Archives/AbstractArchive.cs b/src/SharpCompress/Archives/AbstractArchive.cs index 12ab15a6b..63e67e69b 100644 --- a/src/SharpCompress/Archives/AbstractArchive.cs +++ b/src/SharpCompress/Archives/AbstractArchive.cs @@ -68,7 +68,7 @@ internal AbstractArchive(ArchiveType type) /// /// The total size of the files as uncompressed in the archive. /// - public virtual long TotalUncompressSize => + public virtual long TotalUncompressedSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size); protected abstract IEnumerable LoadVolumes(SourceStream sourceStream); @@ -187,10 +187,26 @@ private async ValueTask EnsureEntriesLoadedAsync() } public virtual IAsyncEnumerable EntriesAsync => _lazyEntriesAsync; - IAsyncEnumerable IAsyncArchive.EntriesAsync => - EntriesAsync.Cast(); - public IAsyncEnumerable VolumesAsync => _lazyVolumesAsync.Cast(); + private async IAsyncEnumerable EntriesAsyncCast() + { + await foreach (var entry in EntriesAsync) + { + yield return entry; + } + } + + IAsyncEnumerable IAsyncArchive.EntriesAsync => EntriesAsyncCast(); + + private async IAsyncEnumerable VolumesAsyncCast() + { + await foreach (var volume in VolumesAsync) + { + yield return volume; + } + } + + public IAsyncEnumerable VolumesAsync => VolumesAsyncCast(); public async ValueTask ExtractAllEntriesAsync() { @@ -209,14 +225,16 @@ public async ValueTask ExtractAllEntriesAsync() public async ValueTask IsCompleteAsync() { await EnsureEntriesLoadedAsync(); - return await EntriesAsync.All(x => x.IsComplete); + return await EntriesAsync.AllAsync(x => x.IsComplete); } public async ValueTask TotalSizeAsync() => - await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.CompressedSize); + await EntriesAsync.AggregateAsync(0L, (total, cf) => total + cf.CompressedSize); + + public async ValueTask TotalUncompressedSizeAsync() => + await EntriesAsync.AggregateAsync(0L, (total, cf) => total + cf.Size); - public async ValueTask TotalUncompressSizeAsync() => - await EntriesAsync.Aggregate(0L, (total, cf) => total + cf.Size); + public ValueTask IsEncryptedAsync() => new(IsEncrypted); #endregion } diff --git a/src/SharpCompress/Archives/AbstractWritableArchive.cs b/src/SharpCompress/Archives/AbstractWritableArchive.cs index 13fb66f9a..6e482f235 100644 --- a/src/SharpCompress/Archives/AbstractWritableArchive.cs +++ b/src/SharpCompress/Archives/AbstractWritableArchive.cs @@ -12,7 +12,8 @@ namespace SharpCompress.Archives; public abstract class AbstractWritableArchive : AbstractArchive, - IWritableArchive + IWritableArchive, + IWritableAsyncArchive where TEntry : IArchiveEntry where TVolume : IVolume { @@ -83,12 +84,12 @@ public void RemoveEntry(TEntry entry) } } - void IWritableArchive.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry); + void IWritableArchiveCommon.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry); public TEntry AddEntry(string key, Stream source, long size = 0, DateTime? modified = null) => AddEntry(key, source, false, size, modified); - IArchiveEntry IWritableArchive.AddEntry( + IArchiveEntry IWritableArchiveCommon.AddEntry( string key, Stream source, bool closeStream, @@ -96,7 +97,7 @@ IArchiveEntry IWritableArchive.AddEntry( DateTime? modified ) => AddEntry(key, source, closeStream, size, modified); - IArchiveEntry IWritableArchive.AddDirectoryEntry(string key, DateTime? modified) => + IArchiveEntry IWritableArchiveCommon.AddDirectoryEntry(string key, DateTime? modified) => AddDirectoryEntry(key, modified); public TEntry AddEntry( diff --git a/src/SharpCompress/Archives/ArchiveFactory.cs b/src/SharpCompress/Archives/ArchiveFactory.cs index e052c3e80..a9800acd9 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.cs @@ -13,27 +13,14 @@ namespace SharpCompress.Archives; public static class ArchiveFactory { - /// - /// Opens an Archive for random access - /// - /// - /// - /// - public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null) + public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) { readerOptions ??= new ReaderOptions(); stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); - return FindFactory(stream).Open(stream, readerOptions); + return FindFactory(stream).OpenArchive(stream, readerOptions); } - /// - /// Opens an Archive for random access asynchronously - /// - /// - /// - /// - /// - public static async ValueTask OpenAsync( + public static async ValueTask OpenAsyncArchive( Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default @@ -41,14 +28,11 @@ public static async ValueTask OpenAsync( { readerOptions ??= new ReaderOptions(); stream = SharpCompressStream.Create(stream, bufferSize: readerOptions.BufferSize); - var factory = await FindFactoryAsync(stream, cancellationToken) - .ConfigureAwait(false); - return await factory - .OpenAsync(stream, readerOptions, cancellationToken) - .ConfigureAwait(false); + var factory = await FindFactoryAsync(stream, cancellationToken); + return factory.OpenAsyncArchive(stream, readerOptions); } - public static IWritableArchive Create(ArchiveType type) + public static IWritableArchive CreateArchive(ArchiveType type) { var factory = Factory .Factories.OfType() @@ -56,58 +40,36 @@ public static IWritableArchive Create(ArchiveType type) if (factory != null) { - return factory.CreateWriteableArchive(); + return factory.CreateArchive(); } throw new NotSupportedException("Cannot create Archives of type: " + type); } - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static IArchive Open(string filePath, ReaderOptions? options = null) + public static IArchive OpenArchive(string filePath, ReaderOptions? options = null) { filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), options); + return OpenArchive(new FileInfo(filePath), options); } - /// - /// Opens an Archive from a filepath asynchronously. - /// - /// - /// - /// - public static ValueTask OpenAsync( + public static ValueTask OpenAsyncArchive( string filePath, ReaderOptions? options = null, CancellationToken cancellationToken = default ) { filePath.NotNullOrEmpty(nameof(filePath)); - return OpenAsync(new FileInfo(filePath), options, cancellationToken); + return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken); } - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null) + public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null) { options ??= new ReaderOptions { LeaveStreamOpen = false }; - return FindFactory(fileInfo).Open(fileInfo, options); + return FindFactory(fileInfo).OpenArchive(fileInfo, options); } - /// - /// Opens an Archive from a FileInfo object asynchronously. - /// - /// - /// - /// - public static async ValueTask OpenAsync( + public static async ValueTask OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? options = null, CancellationToken cancellationToken = default @@ -115,17 +77,14 @@ public static async ValueTask OpenAsync( { options ??= new ReaderOptions { LeaveStreamOpen = false }; - var factory = await FindFactoryAsync(fileInfo, cancellationToken) - .ConfigureAwait(false); - return await factory.OpenAsync(fileInfo, options, cancellationToken).ConfigureAwait(false); + var factory = await FindFactoryAsync(fileInfo, cancellationToken); + return factory.OpenAsyncArchive(fileInfo, options, cancellationToken); } - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// - public static IArchive Open(IEnumerable fileInfos, ReaderOptions? options = null) + public static IArchive OpenArchive( + IEnumerable fileInfos, + ReaderOptions? options = null + ) { fileInfos.NotNull(nameof(fileInfos)); var filesArray = fileInfos.ToArray(); @@ -137,22 +96,16 @@ public static IArchive Open(IEnumerable fileInfos, ReaderOptions? opti var fileInfo = filesArray[0]; if (filesArray.Length == 1) { - return Open(fileInfo, options); + return OpenArchive(fileInfo, options); } fileInfo.NotNull(nameof(fileInfo)); options ??= new ReaderOptions { LeaveStreamOpen = false }; - return FindFactory(fileInfo).Open(filesArray, options); + return FindFactory(fileInfo).OpenArchive(filesArray, options); } - /// - /// Opens a multi-part archive from files asynchronously. - /// - /// - /// - /// - public static async ValueTask OpenAsync( + public static async ValueTask OpenAsyncArchive( IEnumerable fileInfos, ReaderOptions? options = null, CancellationToken cancellationToken = default @@ -168,24 +121,17 @@ public static async ValueTask OpenAsync( var fileInfo = filesArray[0]; if (filesArray.Length == 1) { - return await OpenAsync(fileInfo, options, cancellationToken).ConfigureAwait(false); + return await OpenAsyncArchive(fileInfo, options, cancellationToken); } fileInfo.NotNull(nameof(fileInfo)); options ??= new ReaderOptions { LeaveStreamOpen = false }; - var factory = FindFactory(fileInfo); - return await factory - .OpenAsync(filesArray, options, cancellationToken) - .ConfigureAwait(false); + var factory = await FindFactoryAsync(fileInfo, cancellationToken); + return factory.OpenAsyncArchive(filesArray, options, cancellationToken); } - /// - /// Constructor with IEnumerable FileInfo objects, multi and split support. - /// - /// - /// - public static IArchive Open(IEnumerable streams, ReaderOptions? options = null) + public static IArchive OpenArchive(IEnumerable streams, ReaderOptions? options = null) { streams.NotNull(nameof(streams)); var streamsArray = streams.ToArray(); @@ -197,22 +143,16 @@ public static IArchive Open(IEnumerable streams, ReaderOptions? options var firstStream = streamsArray[0]; if (streamsArray.Length == 1) { - return Open(firstStream, options); + return OpenArchive(firstStream, options); } firstStream.NotNull(nameof(firstStream)); options ??= new ReaderOptions(); - return FindFactory(firstStream).Open(streamsArray, options); + return FindFactory(firstStream).OpenArchive(streamsArray, options); } - /// - /// Opens a multi-part archive from streams asynchronously. - /// - /// - /// - /// - public static async ValueTask OpenAsync( + public static async ValueTask OpenAsyncArchive( IEnumerable streams, ReaderOptions? options = null, CancellationToken cancellationToken = default @@ -229,28 +169,23 @@ public static async ValueTask OpenAsync( var firstStream = streamsArray[0]; if (streamsArray.Length == 1) { - return await OpenAsync(firstStream, options, cancellationToken).ConfigureAwait(false); + return await OpenAsyncArchive(firstStream, options, cancellationToken); } firstStream.NotNull(nameof(firstStream)); options ??= new ReaderOptions(); var factory = FindFactory(firstStream); - return await factory - .OpenAsync(streamsArray, options, cancellationToken) - .ConfigureAwait(false); + return factory.OpenAsyncArchive(streamsArray, options); } - /// - /// Extract to specific directory, retaining filename - /// public static void WriteToDirectory( string sourceArchive, string destinationDirectory, ExtractionOptions? options = null ) { - using var archive = Open(sourceArchive); + using var archive = OpenArchive(sourceArchive); archive.WriteToDirectory(destinationDirectory, options); } @@ -382,22 +317,12 @@ public static bool IsArchive( return false; } - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// - /// public static IEnumerable GetFileParts(string part1) { part1.NotNullOrEmpty(nameof(part1)); return GetFileParts(new FileInfo(part1)).Select(a => a.FullName); } - /// - /// From a passed in archive (zip, rar, 7z, 001), return all parts. - /// - /// - /// public static IEnumerable GetFileParts(FileInfo part1) { part1.NotNull(nameof(part1)); @@ -411,7 +336,7 @@ public static IEnumerable GetFileParts(FileInfo part1) if (part != null) { yield return part; - while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too + while ((part = factory.GetFilePart(i++, part1)) != null) { yield return part; } diff --git a/src/SharpCompress/Archives/AutoArchiveFactory.cs b/src/SharpCompress/Archives/AutoArchiveFactory.cs index 7751c7e01..f588b0851 100644 --- a/src/SharpCompress/Archives/AutoArchiveFactory.cs +++ b/src/SharpCompress/Archives/AutoArchiveFactory.cs @@ -31,21 +31,22 @@ public ValueTask IsArchiveAsync( public FileInfo? GetFilePart(int index, FileInfo part1) => throw new NotSupportedException(); - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - ArchiveFactory.Open(stream, readerOptions); + public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) => + ArchiveFactory.OpenArchive(stream, readerOptions); - public async ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => await ArchiveFactory.OpenAsync(stream, readerOptions, cancellationToken); + public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => + (IAsyncArchive)OpenArchive(stream, readerOptions); - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - ArchiveFactory.Open(fileInfo, readerOptions); + public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + ArchiveFactory.OpenArchive(fileInfo, readerOptions); - public async ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => await ArchiveFactory.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs new file mode 100644 index 000000000..4d413e63f --- /dev/null +++ b/src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.GZip; +using SharpCompress.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 +#endif +{ + public static IWritableAsyncArchive OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IWritableAsyncArchive)OpenArchive( + new FileInfo(path), + readerOptions ?? new ReaderOptions() + ); + } + + 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( + FileInfo fileInfo, + ReaderOptions? readerOptions = null + ) + { + fileInfo.NotNull(nameof(fileInfo)); + return new GZipArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new GZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new GZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new GZipArchive( + new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + } + + public static IWritableArchive CreateArchive() => new GZipArchive(); + + public static IWritableAsyncArchive CreateAsyncArchive() => new GZipArchive(); + + public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); + + public static bool IsGZipFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + + using Stream stream = fileInfo.OpenRead(); + return IsGZipFile(stream); + } + + public static bool IsGZipFile(Stream stream) + { + Span header = stackalloc byte[10]; + + if (!stream.ReadFully(header)) + { + return false; + } + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + { + return false; + } + + return true; + } + + public static async ValueTask IsGZipFileAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + byte[] header = new byte[10]; + + if (!await stream.ReadFullyAsync(header, cancellationToken).ConfigureAwait(false)) + { + return false; + } + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + { + return false; + } + + return true; + } +} diff --git a/src/SharpCompress/Archives/GZip/GZipArchive.cs b/src/SharpCompress/Archives/GZip/GZipArchive.cs index 7c8c08f7c..7d4c345d1 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchive.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchive.cs @@ -14,186 +14,20 @@ namespace SharpCompress.Archives.GZip; -public class GZipArchive : AbstractWritableArchive +public partial class GZipArchive : AbstractWritableArchive { - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static GZipArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new GZipArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static GZipArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new GZipArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static GZipArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new GZipArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new GZipArchive( - new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a GZipArchive asynchronously from a stream. - /// - /// - /// - /// - public static ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, readerOptions)); - } - - /// - /// Opens a GZipArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static ValueTask OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfo, readerOptions)); - } - - /// - /// Opens a GZipArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(streams, readerOptions)); - } - - /// - /// Opens a GZipArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfos, readerOptions)); - } - - public static GZipArchive Create() => new(); - - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private GZipArchive(SourceStream sourceStream) : base(ArchiveType.GZip, sourceStream) { } + internal GZipArchive() + : base(ArchiveType.GZip) { } + protected override IEnumerable LoadVolumes(SourceStream sourceStream) { sourceStream.LoadAllParts(); return sourceStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, 0)); } - public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath)); - - public static bool IsGZipFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - - using Stream stream = fileInfo.OpenRead(); - return IsGZipFile(stream); - } - public void SaveTo(string filePath) => SaveTo(new FileInfo(filePath)); public void SaveTo(FileInfo fileInfo) @@ -215,50 +49,6 @@ public async ValueTask SaveToAsync( .ConfigureAwait(false); } - public static bool IsGZipFile(Stream stream) - { - // read the header on the first read - Span header = stackalloc byte[10]; - - // workitem 8501: handle edge case (decompress empty stream) - if (!stream.ReadFully(header)) - { - return false; - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - return false; - } - - return true; - } - - public static async ValueTask IsGZipFileAsync( - Stream stream, - CancellationToken cancellationToken = default - ) - { - // read the header on the first read - byte[] header = new byte[10]; - - // workitem 8501: handle edge case (decompress empty stream) - if (!await stream.ReadFullyAsync(header, cancellationToken).ConfigureAwait(false)) - { - return false; - } - - if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) - { - return false; - } - - return true; - } - - internal GZipArchive() - : base(ArchiveType.GZip) { } - protected override GZipArchiveEntry CreateEntryInternal( string filePath, Stream source, @@ -329,7 +119,18 @@ protected override IEnumerable LoadEntries(IEnumerable LoadEntriesAsync( + IAsyncEnumerable volumes + ) + { + var stream = (await volumes.SingleAsync()).Stream; + yield return new GZipArchiveEntry( + this, + await GZipFilePart.CreateAsync(stream, ReaderOptions.ArchiveEncoding) ); } @@ -337,13 +138,13 @@ protected override IReader CreateReaderForSolidExtraction() { var stream = Volumes.Single().Stream; stream.Position = 0; - return GZipReader.Open(stream); + return GZipReader.OpenReader(stream); } protected override ValueTask CreateReaderForSolidExtractionAsync() { var stream = Volumes.Single().Stream; stream.Position = 0; - return new(GZipReader.Open(stream)); + return new((IAsyncReader)GZipReader.OpenReader(stream)); } } diff --git a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs index 049c7262a..a8f7b7772 100644 --- a/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/GZip/GZipArchiveEntry.cs @@ -23,12 +23,10 @@ public virtual Stream OpenEntryStream() return Parts.Single().GetCompressedStream().NotNull(); } - public async ValueTask OpenEntryStreamAsync( - CancellationToken cancellationToken = default - ) + public ValueTask OpenEntryStreamAsync(CancellationToken cancellationToken = default) { // GZip synchronous implementation is fast enough, just wrap it - return OpenEntryStream(); + return new(OpenEntryStream()); } #region IArchiveEntry Members diff --git a/src/SharpCompress/Archives/IArchive.cs b/src/SharpCompress/Archives/IArchive.cs index 3ed7490d3..6016214b7 100644 --- a/src/SharpCompress/Archives/IArchive.cs +++ b/src/SharpCompress/Archives/IArchive.cs @@ -38,5 +38,10 @@ public interface IArchive : IDisposable /// /// The total size of the files as uncompressed in the archive. /// - long TotalUncompressSize { get; } + long TotalUncompressedSize { get; } + + /// + /// Returns whether the archive is encrypted. + /// + bool IsEncrypted { get; } } diff --git a/src/SharpCompress/Archives/IArchiveExtensions.cs b/src/SharpCompress/Archives/IArchiveExtensions.cs index c1d2ac987..80857a25c 100644 --- a/src/SharpCompress/Archives/IArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveExtensions.cs @@ -8,7 +8,6 @@ namespace SharpCompress.Archives; public static class IArchiveExtensions { - /// The archive to extract. extension(IArchive archive) { /// @@ -23,7 +22,6 @@ public void WriteToDirectory( IProgress? progress = null ) { - // For solid archives (Rar, 7Zip), use the optimized reader-based approach if (archive.IsSolid || archive.Type == ArchiveType.SevenZip) { using var reader = archive.ExtractAllEntries(); @@ -31,7 +29,6 @@ public void WriteToDirectory( } else { - // For non-solid archives, extract entries directly archive.WriteToDirectoryInternal(destinationDirectory, options, progress); } } @@ -42,14 +39,10 @@ private void WriteToDirectoryInternal( IProgress? progress ) { - // Prepare for progress reporting - var totalBytes = archive.TotalUncompressSize; + var totalBytes = archive.TotalUncompressedSize; var bytesRead = 0L; - - // Tracking for created directories. var seenDirectories = new HashSet(); - // Extract foreach (var entry in archive.Entries) { if (entry.IsDirectory) @@ -68,10 +61,8 @@ private void WriteToDirectoryInternal( continue; } - // Use the entry's WriteToDirectory method which respects ExtractionOptions entry.WriteToDirectory(destinationDirectory, options); - // Update progress bytesRead += entry.Size; progress?.Report( new ProgressReport(entry.Key ?? string.Empty, bytesRead, totalBytes) diff --git a/src/SharpCompress/Archives/IArchiveFactory.cs b/src/SharpCompress/Archives/IArchiveFactory.cs index 1c1253f6a..eb80e072a 100644 --- a/src/SharpCompress/Archives/IArchiveFactory.cs +++ b/src/SharpCompress/Archives/IArchiveFactory.cs @@ -1,6 +1,5 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SharpCompress.Factories; using SharpCompress.Readers; @@ -26,26 +25,21 @@ public interface IArchiveFactory : IFactory /// /// An open, readable and seekable stream. /// reading options. - IArchive Open(Stream stream, ReaderOptions? readerOptions = null); + IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null); /// /// Opens an Archive for random access asynchronously. /// /// An open, readable and seekable stream. /// reading options. - /// Cancellation token. - ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ); + IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null); /// /// Constructor with a FileInfo object to an existing file. /// /// the file to open. /// reading options. - IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null); + IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null); /// /// Opens an Archive from a FileInfo object asynchronously. @@ -53,7 +47,7 @@ ValueTask OpenAsync( /// the file to open. /// reading options. /// Cancellation token. - ValueTask OpenAsync( + IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Archives/IArchiveOpenable.cs b/src/SharpCompress/Archives/IArchiveOpenable.cs new file mode 100644 index 000000000..e5ae52b32 --- /dev/null +++ b/src/SharpCompress/Archives/IArchiveOpenable.cs @@ -0,0 +1,40 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Readers; + +namespace SharpCompress.Archives; + +public interface IArchiveOpenable + where TSync : IArchive + where TASync : IAsyncArchive +{ + public static abstract TSync OpenArchive(string filePath, ReaderOptions? readerOptions = null); + + public static abstract TSync OpenArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null + ); + + public static abstract TSync OpenArchive(Stream stream, ReaderOptions? readerOptions = null); + + public static abstract TASync OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); + + public static abstract TASync OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); + + public static abstract TASync OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); +} + +#endif diff --git a/src/SharpCompress/Archives/IAsyncArchive.cs b/src/SharpCompress/Archives/IAsyncArchive.cs index bd3f290ea..2994e8b86 100644 --- a/src/SharpCompress/Archives/IAsyncArchive.cs +++ b/src/SharpCompress/Archives/IAsyncArchive.cs @@ -39,5 +39,10 @@ public interface IAsyncArchive : IAsyncDisposable /// /// The total size of the files as uncompressed in the archive. /// - ValueTask TotalUncompressSizeAsync(); + ValueTask TotalUncompressedSizeAsync(); + + /// + /// Returns whether the archive is encrypted. + /// + ValueTask IsEncryptedAsync(); } diff --git a/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs b/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs index b6b0cad1e..df4cb05c4 100644 --- a/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IAsyncArchiveExtensions.cs @@ -10,84 +10,83 @@ namespace SharpCompress.Archives; public static class IAsyncArchiveExtensions { - /// - /// Extract to specific directory asynchronously with progress reporting and cancellation support - /// - /// The archive to extract. - /// The folder to extract into. - /// Extraction options. - /// Optional progress reporter for tracking extraction progress. - /// Optional cancellation token. - public static async Task WriteToDirectoryAsync( - this IAsyncArchive archive, - string destinationDirectory, - ExtractionOptions? options = null, - IProgress? progress = null, - CancellationToken cancellationToken = default - ) + extension(IAsyncArchive archive) { - // For solid archives (Rar, 7Zip), use the optimized reader-based approach - if (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip) + /// + /// Extract to specific directory asynchronously with progress reporting and cancellation support + /// + /// The archive to extract. + /// The folder to extract into. + /// Extraction options. + /// Optional progress reporter for tracking extraction progress. + /// Optional cancellation token. + public async Task WriteToDirectoryAsync( + string destinationDirectory, + ExtractionOptions? options = null, + IProgress? progress = null, + CancellationToken cancellationToken = default + ) { - await using var reader = await archive.ExtractAllEntriesAsync(); - await reader.WriteAllToDirectoryAsync(destinationDirectory, options, cancellationToken); - } - else - { - // For non-solid archives, extract entries directly - await archive.WriteToDirectoryAsyncInternal( - destinationDirectory, - options, - progress, - cancellationToken - ); + if (await archive.IsSolidAsync() || archive.Type == ArchiveType.SevenZip) + { + await using var reader = await archive.ExtractAllEntriesAsync(); + await reader.WriteAllToDirectoryAsync( + destinationDirectory, + options, + cancellationToken + ); + } + else + { + await archive.WriteToDirectoryAsyncInternal( + destinationDirectory, + options, + progress, + cancellationToken + ); + } } - } - - private static async Task WriteToDirectoryAsyncInternal( - this IAsyncArchive archive, - string destinationDirectory, - ExtractionOptions? options, - IProgress? progress, - CancellationToken cancellationToken - ) - { - // Prepare for progress reporting - var totalBytes = await archive.TotalUncompressSizeAsync(); - var bytesRead = 0L; - // Tracking for created directories. - var seenDirectories = new HashSet(); - - // Extract - await foreach (var entry in archive.EntriesAsync.WithCancellation(cancellationToken)) + private async Task WriteToDirectoryAsyncInternal( + string destinationDirectory, + ExtractionOptions? options, + IProgress? progress, + CancellationToken cancellationToken + ) { - cancellationToken.ThrowIfCancellationRequested(); + var totalBytes = await archive.TotalUncompressedSizeAsync(); + var bytesRead = 0L; + var seenDirectories = new HashSet(); - if (entry.IsDirectory) + await foreach (var entry in archive.EntriesAsync.WithCancellation(cancellationToken)) { - var dirPath = Path.Combine( - destinationDirectory, - entry.Key.NotNull("Entry Key is null") - ); - if ( - Path.GetDirectoryName(dirPath + "/") is { } parentDirectory - && seenDirectories.Add(dirPath) - ) + cancellationToken.ThrowIfCancellationRequested(); + + if (entry.IsDirectory) { - Directory.CreateDirectory(parentDirectory); + var dirPath = Path.Combine( + destinationDirectory, + entry.Key.NotNull("Entry Key is null") + ); + if ( + Path.GetDirectoryName(dirPath + "/") is { } parentDirectory + && seenDirectories.Add(dirPath) + ) + { + Directory.CreateDirectory(parentDirectory); + } + continue; } - continue; - } - // Use the entry's WriteToDirectoryAsync method which respects ExtractionOptions - await entry - .WriteToDirectoryAsync(destinationDirectory, options, cancellationToken) - .ConfigureAwait(false); + await entry + .WriteToDirectoryAsync(destinationDirectory, options, cancellationToken) + .ConfigureAwait(false); - // Update progress - bytesRead += entry.Size; - progress?.Report(new ProgressReport(entry.Key ?? string.Empty, bytesRead, totalBytes)); + bytesRead += entry.Size; + progress?.Report( + new ProgressReport(entry.Key ?? string.Empty, bytesRead, totalBytes) + ); + } } } } diff --git a/src/SharpCompress/Archives/IMultiArchiveFactory.cs b/src/SharpCompress/Archives/IMultiArchiveFactory.cs index 4fa94d7fe..fc418ad42 100644 --- a/src/SharpCompress/Archives/IMultiArchiveFactory.cs +++ b/src/SharpCompress/Archives/IMultiArchiveFactory.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Threading; -using System.Threading.Tasks; using SharpCompress.Factories; using SharpCompress.Readers; @@ -27,18 +26,16 @@ public interface IMultiArchiveFactory : IFactory /// /// /// reading options. - IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null); + IArchive OpenArchive(IReadOnlyList streams, ReaderOptions? readerOptions = null); /// /// Opens a multi-part archive from streams asynchronously. /// /// /// reading options. - /// Cancellation token. - ValueTask OpenAsync( + IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default + ReaderOptions? readerOptions = null ); /// @@ -46,7 +43,7 @@ ValueTask OpenAsync( /// /// /// reading options. - IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null); + IArchive OpenArchive(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null); /// /// Opens a multi-part archive from files asynchronously. @@ -54,7 +51,7 @@ ValueTask OpenAsync( /// /// reading options. /// Cancellation token. - ValueTask OpenAsync( + IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Archives/IMultiArchiveOpenable.cs b/src/SharpCompress/Archives/IMultiArchiveOpenable.cs new file mode 100644 index 000000000..53f375c09 --- /dev/null +++ b/src/SharpCompress/Archives/IMultiArchiveOpenable.cs @@ -0,0 +1,35 @@ +#if NET8_0_OR_GREATER +using System.Collections.Generic; +using System.IO; +using System.Threading; +using SharpCompress.Readers; + +namespace SharpCompress.Archives; + +public interface IMultiArchiveOpenable + where TSync : IArchive + where TASync : IAsyncArchive +{ + public static abstract TSync OpenArchive( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ); + + public static abstract TSync OpenArchive( + IEnumerable streams, + ReaderOptions? readerOptions = null + ); + + public static abstract TASync OpenAsyncArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); + + public static abstract TASync OpenAsyncArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); +} +#endif diff --git a/src/SharpCompress/Archives/IWritableArchive.cs b/src/SharpCompress/Archives/IWritableArchive.cs index 74d8da763..28496b48e 100644 --- a/src/SharpCompress/Archives/IWritableArchive.cs +++ b/src/SharpCompress/Archives/IWritableArchive.cs @@ -6,8 +6,17 @@ namespace SharpCompress.Archives; -public interface IWritableArchive : IArchive +public interface IWritableArchiveCommon { + /// + /// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended. + /// + /// IDisposeable to resume entry rebuilding + IDisposable PauseEntryRebuilding(); + + /// + /// Removes the specified entry from the archive. + /// void RemoveEntry(IArchiveEntry entry); IArchiveEntry AddEntry( @@ -19,18 +28,24 @@ IArchiveEntry AddEntry( ); IArchiveEntry AddDirectoryEntry(string key, DateTime? modified = null); +} +public interface IWritableArchive : IArchive, IWritableArchiveCommon +{ + /// + /// Saves the archive to the specified stream using the given writer options. + /// void SaveTo(Stream stream, WriterOptions options); +} +public interface IWritableAsyncArchive : IAsyncArchive, IWritableArchiveCommon +{ + /// + /// Asynchronously saves the archive to the specified stream using the given writer options. + /// ValueTask SaveToAsync( Stream stream, WriterOptions options, CancellationToken cancellationToken = default ); - - /// - /// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended. - /// - /// IDisposeable to resume entry rebuilding - IDisposable PauseEntryRebuilding(); } diff --git a/src/SharpCompress/Archives/IWritableArchiveCommonExtensions.cs b/src/SharpCompress/Archives/IWritableArchiveCommonExtensions.cs new file mode 100644 index 000000000..7f276ecee --- /dev/null +++ b/src/SharpCompress/Archives/IWritableArchiveCommonExtensions.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; + +namespace SharpCompress.Archives; + +public static class IWritableArchiveCommonExtensions +{ + extension(IWritableArchiveCommon writableArchive) + { + public void AddAllFromDirectory( + string filePath, + string searchPattern = "*.*", + SearchOption searchOption = SearchOption.AllDirectories + ) + { + using (writableArchive.PauseEntryRebuilding()) + { + foreach ( + var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption) + ) + { + var fileInfo = new FileInfo(path); + writableArchive.AddEntry( + path.Substring(filePath.Length), + fileInfo.OpenRead(), + true, + fileInfo.Length, + fileInfo.LastWriteTime + ); + } + } + } + + public IArchiveEntry AddEntry(string key, string file) => + writableArchive.AddEntry(key, new FileInfo(file)); + + public IArchiveEntry AddEntry( + string key, + Stream source, + long size = 0, + DateTime? modified = null + ) => writableArchive.AddEntry(key, source, false, size, modified); + + public IArchiveEntry AddEntry(string key, FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + throw new ArgumentException("FileInfo does not exist."); + } + return writableArchive.AddEntry( + key, + fileInfo.OpenRead(), + true, + fileInfo.Length, + fileInfo.LastWriteTime + ); + } + } +} diff --git a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs index 60ec83d85..60971bfc1 100644 --- a/src/SharpCompress/Archives/IWritableArchiveExtensions.cs +++ b/src/SharpCompress/Archives/IWritableArchiveExtensions.cs @@ -1,106 +1,20 @@ -using System; using System.IO; -using System.Threading; -using System.Threading.Tasks; +using SharpCompress.Common; using SharpCompress.Writers; namespace SharpCompress.Archives; public static class IWritableArchiveExtensions { - public static void AddEntry( - this IWritableArchive writableArchive, - string entryPath, - string filePath - ) + extension(IWritableArchive writableArchive) { - var fileInfo = new FileInfo(filePath); - if (!fileInfo.Exists) - { - throw new FileNotFoundException("Could not AddEntry: " + filePath); - } - writableArchive.AddEntry( - entryPath, - new FileInfo(filePath).OpenRead(), - true, - fileInfo.Length, - 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, - WriterOptions options - ) => writableArchive.SaveTo(new FileInfo(filePath), options); - - public static void SaveTo( - this IWritableArchive writableArchive, - FileInfo fileInfo, - WriterOptions options - ) - { - using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - writableArchive.SaveTo(stream, options); - } - - public static ValueTask SaveToAsync( - this IWritableArchive writableArchive, - string filePath, - WriterOptions options, - CancellationToken cancellationToken = default - ) => writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken); - - public static async ValueTask SaveToAsync( - this IWritableArchive writableArchive, - FileInfo fileInfo, - WriterOptions options, - CancellationToken cancellationToken = default - ) - { - using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); - await writableArchive.SaveToAsync(stream, options, cancellationToken).ConfigureAwait(false); - } - - public static void AddAllFromDirectory( - this IWritableArchive writableArchive, - string filePath, - string searchPattern = "*.*", - SearchOption searchOption = SearchOption.AllDirectories - ) - { - using (writableArchive.PauseEntryRebuilding()) - { - foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)) - { - var fileInfo = new FileInfo(path); - writableArchive.AddEntry( - path.Substring(filePath.Length), - fileInfo.OpenRead(), - true, - fileInfo.Length, - fileInfo.LastWriteTime - ); - } - } - } - - public static IArchiveEntry AddEntry( - this IWritableArchive writableArchive, - string key, - FileInfo fileInfo - ) - { - if (!fileInfo.Exists) + public void SaveTo(FileInfo fileInfo, WriterOptions? options = null) { - throw new ArgumentException("FileInfo does not exist."); + using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write); + writableArchive.SaveTo(stream, options ?? new(CompressionType.Deflate)); } - return writableArchive.AddEntry( - key, - fileInfo.OpenRead(), - true, - fileInfo.Length, - fileInfo.LastWriteTime - ); } } diff --git a/src/SharpCompress/Archives/IWritableArchiveOpenable.cs b/src/SharpCompress/Archives/IWritableArchiveOpenable.cs new file mode 100644 index 000000000..b0b761a2f --- /dev/null +++ b/src/SharpCompress/Archives/IWritableArchiveOpenable.cs @@ -0,0 +1,10 @@ +#if NET8_0_OR_GREATER +namespace SharpCompress.Archives; + +public interface IWritableArchiveOpenable + : IArchiveOpenable +{ + 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 new file mode 100644 index 000000000..a69bfc0a3 --- /dev/null +++ b/src/SharpCompress/Archives/IWritableAsyncArchiveExtensions.cs @@ -0,0 +1,36 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Writers; + +namespace SharpCompress.Archives; + +public static class IWritableAsyncArchiveExtensions +{ + extension(IWritableAsyncArchive writableArchive) + { + public ValueTask SaveToAsync( + string filePath, + WriterOptions? options = null, + CancellationToken cancellationToken = default + ) => + writableArchive.SaveToAsync( + new FileInfo(filePath), + options ?? new(CompressionType.Deflate), + 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); + } + } +} diff --git a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs index 4fae9f558..f1de642d0 100644 --- a/src/SharpCompress/Archives/IWriteableArchiveFactory.cs +++ b/src/SharpCompress/Archives/IWriteableArchiveFactory.cs @@ -16,5 +16,5 @@ public interface IWriteableArchiveFactory : Factories.IFactory /// Creates a new, empty archive, ready to be written. /// /// - IWritableArchive CreateWriteableArchive(); + IWritableArchive CreateArchive(); } diff --git a/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs b/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs index bf6f0cd44..eb7c1f3be 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.Extensions.cs @@ -1,18 +1,36 @@ -using System.Linq; +using System.Linq; +using System.Threading.Tasks; +using SharpCompress.Common.Rar; namespace SharpCompress.Archives.Rar; public static class RarArchiveExtensions { - /// - /// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing. - /// - public static bool IsFirstVolume(this RarArchive archive) => - archive.Volumes.First().IsFirstVolume; + extension(IRarArchive archive) + { + /// + /// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing. + /// + public bool IsFirstVolume() => archive.Volumes.Cast().First().IsFirstVolume; - /// - /// RarArchive is part of a multi-part archive. - /// - public static bool IsMultipartVolume(this RarArchive archive) => - archive.Volumes.First().IsMultiVolume; + /// + /// RarArchive is part of a multi-part archive. + /// + public bool IsMultipartVolume() => archive.Volumes.Cast().First().IsMultiVolume; + } + + extension(IRarAsyncArchive archive) + { + /// + /// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing. + /// + public async ValueTask IsFirstVolumeAsync() => + (await archive.VolumesAsync.CastAsync().FirstAsync()).IsFirstVolume; + + /// + /// RarArchive is part of a multi-part archive. + /// + public async ValueTask IsMultipartVolumeAsync() => + (await archive.VolumesAsync.CastAsync().FirstAsync()).IsMultiVolume; + } } diff --git a/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs new file mode 100644 index 000000000..f3e9a99d2 --- /dev/null +++ b/src/SharpCompress/Archives/Rar/RarArchive.Factory.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using SharpCompress.Common; +using SharpCompress.Common.Rar; +using SharpCompress.Common.Rar.Headers; +using SharpCompress.Compressors.Rar; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Readers.Rar; + +namespace SharpCompress.Archives.Rar; + +public partial class RarArchive +#if NET8_0_OR_GREATER + : IArchiveOpenable, + IMultiArchiveOpenable +#endif +{ + public static IRarAsyncArchive OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions); + } + + public static IRarArchive OpenArchive(string filePath, ReaderOptions? options = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + var fileInfo = new FileInfo(filePath); + return new RarArchive( + new SourceStream( + fileInfo, + i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), + options ?? new ReaderOptions() + ) + ); + } + + public static IRarArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return new RarArchive( + new SourceStream( + fileInfo, + i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), + options ?? new ReaderOptions() + ) + ); + } + + public static IRarArchive OpenArchive(Stream stream, ReaderOptions? options = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions())); + } + + public static IRarArchive OpenArchive( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new RarArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IRarArchive OpenArchive( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new RarArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IRarAsyncArchive OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)OpenArchive(stream, readerOptions); + } + + public static IRarAsyncArchive OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)OpenArchive(fileInfo, readerOptions); + } + + public static IRarAsyncArchive OpenAsyncArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)OpenArchive(streams, readerOptions); + } + + public static IRarAsyncArchive OpenAsyncArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IRarAsyncArchive)OpenArchive(fileInfos, readerOptions); + } + + public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); + + public static bool IsRarFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsRarFile(stream); + } + + public static bool IsRarFile(Stream stream, ReaderOptions? options = null) + { + try + { + MarkHeader.Read(stream, true, false); + return true; + } + catch + { + return false; + } + } +} diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index 03b8d4d90..08e896860 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -14,17 +14,23 @@ namespace SharpCompress.Archives.Rar; -public class RarArchive : AbstractArchive +public interface IRarArchiveCommon +{ + int MinVersion { get; } + int MaxVersion { get; } +} + +public interface IRarArchive : IArchive, IRarArchiveCommon { } + +public interface IRarAsyncArchive : IAsyncArchive, IRarArchiveCommon { } + +public partial class RarArchive : AbstractArchive, IRarArchive { private bool _disposed; internal Lazy UnpackV2017 { get; } = new(() => new Compressors.Rar.UnpackV2017.Unpack()); internal Lazy UnpackV1 { get; } = new(() => new Compressors.Rar.UnpackV1.Unpack()); - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private RarArchive(SourceStream sourceStream) : base(ArchiveType.Rar, sourceStream) { } @@ -47,10 +53,10 @@ protected override IEnumerable LoadEntries(IEnumerable LoadVolumes(SourceStream sourceStream) { - sourceStream.LoadAllParts(); //request all streams + sourceStream.LoadAllParts(); var streams = sourceStream.Streams.ToArray(); var i = 0; - if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split + if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) { sourceStream.IsVolumes = true; streams[1].Position = 0; @@ -63,7 +69,6 @@ protected override IEnumerable LoadVolumes(SourceStream sourceStream) )); } - //split mode or single file return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable(); } @@ -82,12 +87,12 @@ private RarReader CreateReaderForSolidExtractionInternal() volume.Stream.Position = 0; return volume.Stream; }); - return RarReader.Open(streams, ReaderOptions); + return (RarReader)RarReader.OpenReader(streams, ReaderOptions); } var stream = Volumes.First().Stream; stream.Position = 0; - return RarReader.Open(stream, ReaderOptions); + return (RarReader)RarReader.OpenReader(stream, ReaderOptions); } public override bool IsSolid => Volumes.First().IsSolidArchive; @@ -96,187 +101,4 @@ private RarReader CreateReaderForSolidExtractionInternal() public virtual int MinVersion => Volumes.First().MinVersion; public virtual int MaxVersion => Volumes.First().MaxVersion; - - #region Creation - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static RarArchive Open(string filePath, ReaderOptions? options = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - var fileInfo = new FileInfo(filePath); - return new RarArchive( - new SourceStream( - fileInfo, - i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), - options ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new RarArchive( - new SourceStream( - fileInfo, - i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo), - options ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static RarArchive Open(Stream stream, ReaderOptions? options = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions())); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static RarArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new RarArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static RarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new RarArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Opens a RarArchive asynchronously from a stream. - /// - /// - /// - /// - public static ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, readerOptions)); - } - - /// - /// Opens a RarArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static ValueTask OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfo, readerOptions)); - } - - /// - /// Opens a RarArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(streams, readerOptions)); - } - - /// - /// Opens a RarArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfos, readerOptions)); - } - - public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath)); - - public static bool IsRarFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsRarFile(stream); - } - - public static bool IsRarFile(Stream stream, ReaderOptions? options = null) - { - try - { - MarkHeader.Read(stream, true, false); - return true; - } - catch - { - return false; - } - } - - #endregion } diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs new file mode 100644 index 000000000..2abb00ab4 --- /dev/null +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.SevenZip; +using SharpCompress.Compressors.LZMA.Utilites; +using SharpCompress.IO; +using SharpCompress.Readers; + +namespace SharpCompress.Archives.SevenZip; + +public partial class SevenZipArchive +#if NET8_0_OR_GREATER + : IArchiveOpenable, + IMultiArchiveOpenable +#endif +{ + public static IAsyncArchive OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty("path"); + return (IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions()); + } + + public static IArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty("filePath"); + return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); + } + + public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull("fileInfo"); + return new SevenZipArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive OpenArchive( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new SevenZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive OpenArchive( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new SevenZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull("stream"); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new SevenZipArchive( + new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IAsyncArchive OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(stream, readerOptions); + } + + public static IAsyncArchive OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } + + public static IAsyncArchive OpenAsyncArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(streams, readerOptions); + } + + public static IAsyncArchive OpenAsyncArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + } + + public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath)); + + public static bool IsSevenZipFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsSevenZipFile(stream); + } + + public static bool IsSevenZipFile(Stream stream) + { + try + { + return SignatureMatch(stream); + } + catch + { + return false; + } + } + + private static ReadOnlySpan Signature => + new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C }; + + private static bool SignatureMatch(Stream stream) + { + var reader = new BinaryReader(stream); + ReadOnlySpan signatureBytes = reader.ReadBytes(6); + return signatureBytes.SequenceEqual(Signature); + } +} diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index 43f49abed..e6b511d83 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -12,191 +12,22 @@ namespace SharpCompress.Archives.SevenZip; -public class SevenZipArchive : AbstractArchive +public partial class SevenZipArchive : AbstractArchive { private ArchiveDatabase? _database; - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static SevenZipArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty("filePath"); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull("fileInfo"); - return new SevenZipArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static SevenZipArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new SevenZipArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static SevenZipArchive Open( - IEnumerable streams, - ReaderOptions? readerOptions = null - ) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new SevenZipArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static SevenZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull("stream"); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new SevenZipArchive( - new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a SevenZipArchive asynchronously from a stream. - /// - /// - /// - /// - public static ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, readerOptions)); - } - - /// - /// Opens a SevenZipArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static ValueTask OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfo, readerOptions)); - } - - /// - /// Opens a SevenZipArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(streams, readerOptions)); - } - - /// - /// Opens a SevenZipArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfos, readerOptions)); - } - - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private SevenZipArchive(SourceStream sourceStream) : base(ArchiveType.SevenZip, sourceStream) { } - protected override IEnumerable LoadVolumes(SourceStream sourceStream) - { - sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams - return new SevenZipVolume(sourceStream, ReaderOptions, 0).AsEnumerable(); //simple single volume or split, multivolume not supported - } - - public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath)); + internal SevenZipArchive() + : base(ArchiveType.SevenZip) { } - public static bool IsSevenZipFile(FileInfo fileInfo) + protected override IEnumerable LoadVolumes(SourceStream sourceStream) { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsSevenZipFile(stream); + sourceStream.NotNull("SourceStream is null").LoadAllParts(); + return new SevenZipVolume(sourceStream, ReaderOptions, 0).AsEnumerable(); } - internal SevenZipArchive() - : base(ArchiveType.SevenZip) { } - protected override IEnumerable LoadEntries( IEnumerable volumes ) @@ -222,7 +53,7 @@ IEnumerable volumes foreach (var entry in group) { entry.IsSolid = isSolid; - isSolid = true; //mark others in this group as solid - same as rar behaviour. + isSolid = true; } } @@ -240,28 +71,6 @@ private void LoadFactory(Stream stream) } } - public static bool IsSevenZipFile(Stream stream) - { - try - { - return SignatureMatch(stream); - } - catch - { - return false; - } - } - - private static ReadOnlySpan Signature => - new byte[] { (byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C }; - - private static bool SignatureMatch(Stream stream) - { - var reader = new BinaryReader(stream); - ReadOnlySpan signatureBytes = reader.ReadBytes(6); - return signatureBytes.SequenceEqual(Signature); - } - protected override IReader CreateReaderForSolidExtraction() => new SevenZipReader(ReaderOptions, this); @@ -298,9 +107,6 @@ protected override IEnumerable GetEntries(Stream stream) _currentEntry = dir; yield return dir; } - // For non-directory entries, yield them without creating shared streams - // Each call to GetEntryStream() will create a fresh decompression stream - // to avoid state corruption issues with async operations foreach (var entry in entries.Where(x => !x.IsDirectory)) { _currentEntry = entry; @@ -310,13 +116,6 @@ protected override IEnumerable GetEntries(Stream stream) protected override EntryStream GetEntryStream() { - // Create a fresh decompression stream for each file (no state sharing). - // However, the LZMA decoder has bugs in its async implementation that cause - // state corruption even on fresh streams. The SyncOnlyStream wrapper - // works around these bugs by forcing async operations to use sync equivalents. - // - // TODO: Fix the LZMA decoder async bugs (in LzmaStream, Decoder, OutWindow) - // so this wrapper is no longer necessary. var entry = _currentEntry.NotNull("currentEntry is not null"); if (entry.IsDirectory) { @@ -326,15 +125,6 @@ protected override EntryStream GetEntryStream() } } - /// - /// WORKAROUND: Forces async operations to use synchronous equivalents. - /// This is necessary because the LZMA decoder has bugs in its async implementation - /// that cause state corruption (IndexOutOfRangeException, DataErrorException). - /// - /// The proper fix would be to repair the LZMA decoder's async methods - /// (LzmaStream.ReadAsync, Decoder.CodeAsync, OutWindow async operations), - /// but that requires deep changes to the decoder state machine. - /// private sealed class SyncOnlyStream : Stream { private readonly Stream _baseStream; @@ -364,7 +154,6 @@ public override long Seek(long offset, SeekOrigin origin) => public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count); - // Force async operations to use sync equivalents to avoid LZMA decoder bugs public override Task ReadAsync( byte[] buffer, int offset, diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs index a0d4a50d8..5b785e7c4 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchiveEntry.cs @@ -12,9 +12,8 @@ internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part) public Stream OpenEntryStream() => FilePart.GetCompressedStream(); - public async ValueTask OpenEntryStreamAsync( - CancellationToken cancellationToken = default - ) => OpenEntryStream(); + public ValueTask OpenEntryStreamAsync(CancellationToken cancellationToken = default) => + new(OpenEntryStream()); public IArchive Archive { get; } diff --git a/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs new file mode 100644 index 000000000..f6e03f4f8 --- /dev/null +++ b/src/SharpCompress/Archives/Tar/TarArchive.Factory.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.Tar; +using SharpCompress.Common.Tar.Headers; +using SharpCompress.IO; +using SharpCompress.Readers; +using SharpCompress.Writers; +using SharpCompress.Writers.Tar; + +namespace SharpCompress.Archives.Tar; + +public partial class TarArchive +#if NET8_0_OR_GREATER + : IWritableArchiveOpenable, + IMultiArchiveOpenable +#endif +{ + 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( + FileInfo fileInfo, + ReaderOptions? readerOptions = null + ) + { + fileInfo.NotNull(nameof(fileInfo)); + return new TarArchive( + new SourceStream( + fileInfo, + i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new TarArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new TarArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new TarArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(new FileInfo(path), readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + } + + public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); + + public static bool IsTarFile(FileInfo fileInfo) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsTarFile(stream); + } + + public static bool IsTarFile(Stream stream) + { + try + { + var tarHeader = new TarHeader(new ArchiveEncoding()); + var readSucceeded = tarHeader.Read(new BinaryReader(stream)); + var isEmptyArchive = + tarHeader.Name?.Length == 0 + && tarHeader.Size == 0 + && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType); + return readSucceeded || isEmptyArchive; + } + catch { } + return false; + } + + public static IWritableArchive CreateArchive() => 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 1aeaf9a7a..16eddbebf 100644 --- a/src/SharpCompress/Archives/Tar/TarArchive.cs +++ b/src/SharpCompress/Archives/Tar/TarArchive.cs @@ -15,196 +15,14 @@ namespace SharpCompress.Archives.Tar; -public class TarArchive : AbstractWritableArchive +public partial class TarArchive : AbstractWritableArchive { - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static TarArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new TarArchive( - new SourceStream( - fileInfo, - i => ArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static TarArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new TarArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static TarArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new TarArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new TarArchive( - new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a TarArchive asynchronously from a stream. - /// - /// - /// - /// - public static ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, readerOptions)); - } - - /// - /// Opens a TarArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static ValueTask OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfo, readerOptions)); - } - - /// - /// Opens a TarArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(streams, readerOptions)); - } - - /// - /// Opens a TarArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfos, readerOptions)); - } - - public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath)); - - public static bool IsTarFile(FileInfo fileInfo) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsTarFile(stream); - } - - public static bool IsTarFile(Stream stream) - { - try - { - var tarHeader = new TarHeader(new ArchiveEncoding()); - var readSucceeded = tarHeader.Read(new BinaryReader(stream)); - var isEmptyArchive = - tarHeader.Name?.Length == 0 - && tarHeader.Size == 0 - && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType); - return readSucceeded || isEmptyArchive; - } - catch { } - return false; - } - protected override IEnumerable LoadVolumes(SourceStream sourceStream) { - sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams - return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); //simple single volume or split, multivolume not supported + sourceStream.NotNull("SourceStream is null").LoadAllParts(); + return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// private TarArchive(SourceStream sourceStream) : base(ArchiveType.Tar, sourceStream) { } @@ -269,8 +87,6 @@ var header in TarHeaderFactory.ReadHeader( } } - public static TarArchive Create() => new(); - protected override TarArchiveEntry CreateEntryInternal( string filePath, Stream source, @@ -364,13 +180,13 @@ protected override IReader CreateReaderForSolidExtraction() { var stream = Volumes.Single().Stream; stream.Position = 0; - return TarReader.Open(stream); + return TarReader.OpenReader(stream); } protected override ValueTask CreateReaderForSolidExtractionAsync() { var stream = Volumes.Single().Stream; stream.Position = 0; - return new(TarReader.Open(stream)); + return new((IAsyncReader)TarReader.OpenReader(stream)); } } diff --git a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs index cbea2c717..b09876e98 100644 --- a/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Tar/TarArchiveEntry.cs @@ -14,9 +14,8 @@ internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull(); - public async ValueTask OpenEntryStreamAsync( - CancellationToken cancellationToken = default - ) => OpenEntryStream(); + public ValueTask OpenEntryStreamAsync(CancellationToken cancellationToken = default) => + new(OpenEntryStream()); #region IArchiveEntry Members diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs new file mode 100644 index 000000000..bb51552ed --- /dev/null +++ b/src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpCompress.Common; +using SharpCompress.Common.Zip; +using SharpCompress.Common.Zip.Headers; +using SharpCompress.IO; +using SharpCompress.Readers; + +namespace SharpCompress.Archives.Zip; + +public partial class ZipArchive +#if NET8_0_OR_GREATER + : IWritableArchiveOpenable, + IMultiArchiveOpenable +#endif +{ + 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( + FileInfo fileInfo, + ReaderOptions? readerOptions = null + ) + { + fileInfo.NotNull(nameof(fileInfo)); + return new ZipArchive( + new SourceStream( + fileInfo, + i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive( + IEnumerable fileInfos, + ReaderOptions? readerOptions = null + ) + { + fileInfos.NotNull(nameof(fileInfos)); + var files = fileInfos.ToArray(); + return new ZipArchive( + new SourceStream( + files[0], + i => i < files.Length ? files[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive( + IEnumerable streams, + ReaderOptions? readerOptions = null + ) + { + streams.NotNull(nameof(streams)); + var strms = streams.ToArray(); + return new ZipArchive( + new SourceStream( + strms[0], + i => i < strms.Length ? strms[i] : null, + readerOptions ?? new ReaderOptions() + ) + ); + } + + public static IWritableArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) + { + stream.NotNull(nameof(stream)); + + if (stream is not { CanSeek: true }) + { + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + + return new ZipArchive( + new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) + ); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(path, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(stream, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(fileInfo, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(streams, readerOptions); + } + + public static IWritableAsyncArchive OpenAsyncArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IWritableAsyncArchive)OpenArchive(fileInfos, readerOptions); + } + + public static bool IsZipFile( + string filePath, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) => IsZipFile(new FileInfo(filePath), password, bufferSize); + + public static bool IsZipFile( + FileInfo fileInfo, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + if (!fileInfo.Exists) + { + return false; + } + using Stream stream = fileInfo.OpenRead(); + return IsZipFile(stream, password, bufferSize); + } + + public static bool IsZipFile( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) + { + return false; + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } + + public static bool IsZipMulti( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize + ) + { + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) + { + if (stream.CanSeek) + { + var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); + var x = z.ReadSeekableHeader(stream, useSync: true).FirstOrDefault(); + return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; + } + else + { + return false; + } + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } + + public static async ValueTask IsZipFileAsync( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = await headerFactory + .ReadStreamHeaderAsync(stream) + .Where(x => x.ZipHeaderType != ZipHeaderType.Split) + .FirstOrDefaultAsync(cancellationToken); + if (header is null) + { + return false; + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } + + public static IWritableArchive CreateArchive() => new ZipArchive(); + + public static IWritableAsyncArchive CreateAsyncArchive() => new ZipArchive(); + + public static async ValueTask IsZipMultiAsync( + Stream stream, + string? password = null, + int bufferSize = ReaderOptions.DefaultBufferSize, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); + try + { + if (stream is not SharpCompressStream) + { + stream = new SharpCompressStream(stream, bufferSize: bufferSize); + } + + var header = headerFactory + .ReadStreamHeader(stream) + .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); + if (header is null) + { + if (stream.CanSeek) + { + var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); + ZipHeader? x = null; + await foreach ( + var h in z.ReadSeekableHeaderAsync(stream) + .WithCancellation(cancellationToken) + ) + { + x = h; + break; + } + return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; + } + else + { + return false; + } + } + return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); + } + catch (CryptographicException) + { + return true; + } + catch + { + return false; + } + } +} diff --git a/src/SharpCompress/Archives/Zip/ZipArchive.cs b/src/SharpCompress/Archives/Zip/ZipArchive.cs index 756bc8863..9e2d80e11 100644 --- a/src/SharpCompress/Archives/Zip/ZipArchive.cs +++ b/src/SharpCompress/Archives/Zip/ZipArchive.cs @@ -16,21 +16,12 @@ namespace SharpCompress.Archives.Zip; -public class ZipArchive : AbstractWritableArchive +public partial class ZipArchive : AbstractWritableArchive { private readonly SeekableZipHeaderFactory? headerFactory; - /// - /// Gets or sets the compression level applied to files added to the archive, - /// if the compression method is set to deflate - /// public CompressionLevel DeflateCompressionLevel { get; set; } - /// - /// Constructor with a SourceStream able to handle FileInfo and Streams. - /// - /// - /// internal ZipArchive(SourceStream sourceStream) : base(ArchiveType.Zip, sourceStream) => headerFactory = new SeekableZipHeaderFactory( @@ -38,371 +29,36 @@ internal ZipArchive(SourceStream sourceStream) sourceStream.ReaderOptions.ArchiveEncoding ); - /// - /// Constructor expects a filepath to an existing file. - /// - /// - /// - public static ZipArchive Open(string filePath, ReaderOptions? readerOptions = null) - { - filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions()); - } - - /// - /// Constructor with a FileInfo object to an existing file. - /// - /// - /// - public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) - { - fileInfo.NotNull(nameof(fileInfo)); - return new ZipArchive( - new SourceStream( - fileInfo, - i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo), - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all file parts passed in - /// - /// - /// - public static ZipArchive Open( - IEnumerable fileInfos, - ReaderOptions? readerOptions = null - ) - { - fileInfos.NotNull(nameof(fileInfos)); - var files = fileInfos.ToArray(); - return new ZipArchive( - new SourceStream( - files[0], - i => i < files.Length ? files[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Constructor with all stream parts passed in - /// - /// - /// - public static ZipArchive Open(IEnumerable streams, ReaderOptions? readerOptions = null) - { - streams.NotNull(nameof(streams)); - var strms = streams.ToArray(); - return new ZipArchive( - new SourceStream( - strms[0], - i => i < strms.Length ? strms[i] : null, - readerOptions ?? new ReaderOptions() - ) - ); - } - - /// - /// Takes a seekable Stream as a source - /// - /// - /// - public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null) - { - stream.NotNull(nameof(stream)); - - if (stream is not { CanSeek: true }) - { - throw new ArgumentException("Stream must be seekable", nameof(stream)); - } - - return new ZipArchive( - new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions()) - ); - } - - /// - /// Opens a ZipArchive asynchronously from a stream. - /// - /// - /// - /// - public static ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, readerOptions)); - } - - /// - /// Opens a ZipArchive asynchronously from a FileInfo. - /// - /// - /// - /// - public static ValueTask OpenAsync( - FileInfo fileInfo, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfo, readerOptions)); - } - - /// - /// Opens a ZipArchive asynchronously from multiple streams. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(streams, readerOptions)); - } - - /// - /// Opens a ZipArchive asynchronously from multiple FileInfo objects. - /// - /// - /// - /// - public static ValueTask OpenAsync( - IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - return new(Open(fileInfos, readerOptions)); - } - - public static bool IsZipFile( - string filePath, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) => IsZipFile(new FileInfo(filePath), password, bufferSize); - - public static bool IsZipFile( - FileInfo fileInfo, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - if (!fileInfo.Exists) - { - return false; - } - using Stream stream = fileInfo.OpenRead(); - return IsZipFile(stream, password, bufferSize); - } - - public static bool IsZipFile( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = headerFactory - .ReadStreamHeader(stream) - .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - return false; - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } - - public static bool IsZipMulti( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize - ) - { - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = headerFactory - .ReadStreamHeader(stream) - .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe - { - var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); - var x = z.ReadSeekableHeader(stream, useSync: true).FirstOrDefault(); - return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; - } - else - { - return false; - } - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } - - public static async ValueTask IsZipFileAsync( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = await headerFactory - .ReadStreamHeaderAsync(stream) - .Where(x => x.ZipHeaderType != ZipHeaderType.Split) - .FirstOrDefaultAsync(); - if (header is null) - { - return false; - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } - - public static async ValueTask IsZipMultiAsync( - Stream stream, - string? password = null, - int bufferSize = ReaderOptions.DefaultBufferSize, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null); - try - { - if (stream is not SharpCompressStream) - { - stream = new SharpCompressStream(stream, bufferSize: bufferSize); - } - - var header = headerFactory - .ReadStreamHeader(stream) - .FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split); - if (header is null) - { - if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe - { - var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding()); - ZipHeader? x = null; - await foreach ( - var h in z.ReadSeekableHeaderAsync(stream) - .WithCancellation(cancellationToken) - ) - { - x = h; - break; - } - return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry; - } - else - { - return false; - } - } - return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType); - } - catch (CryptographicException) - { - return true; - } - catch - { - return false; - } - } + internal ZipArchive() + : base(ArchiveType.Zip) { } protected override IEnumerable LoadVolumes(SourceStream stream) { - stream.LoadAllParts(); //request all streams + stream.LoadAllParts(); stream.Position = 0; var streams = stream.Streams.ToList(); var idx = 0; - if (streams.Count() > 1) //test part 2 - true = multipart not split + if (streams.Count() > 1) { - streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception + streams[1].Position += 4; var isZip = IsZipFile(streams[1], ReaderOptions.Password, ReaderOptions.BufferSize); streams[1].Position -= 4; if (isZip) { stream.IsVolumes = true; - var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end + var tmp = streams[0]; streams.RemoveAt(0); streams.Add(tmp); - //streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception return streams.Select(a => new ZipVolume(a, ReaderOptions, idx++)); } } - //split mode or single file return new ZipVolume(stream, ReaderOptions, idx++).AsEnumerable(); } - internal ZipArchive() - : base(ArchiveType.Zip) { } - protected override IEnumerable LoadEntries(IEnumerable volumes) { var vols = volumes.ToArray(); @@ -584,19 +240,17 @@ protected override ZipArchiveEntry CreateDirectoryEntry( DateTime? modified ) => new ZipWritableArchiveEntry(this, directoryPath, modified); - public static ZipArchive Create() => new(); - protected override IReader CreateReaderForSolidExtraction() { var stream = Volumes.Single().Stream; ((IStreamStack)stream).StackSeek(0); - return ZipReader.Open(stream, ReaderOptions, Entries); + return ZipReader.OpenReader(stream, ReaderOptions, Entries); } protected override ValueTask CreateReaderForSolidExtractionAsync() { var stream = Volumes.Single().Stream; stream.Position = 0; - return new(ZipReader.Open(stream)); + return new((IAsyncReader)ZipReader.OpenReader(stream)); } } diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index 9a551d07f..c0cc6b107 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -40,6 +40,6 @@ public class GZipEntry : Entry internal static IEnumerable GetEntries(Stream stream, OptionsBase options) { - yield return new GZipEntry(new GZipFilePart(stream, options.ArchiveEncoding)); + yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding)); } } diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index f1d0eaf1a..a2cb0a764 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -2,6 +2,8 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common.Tar.Headers; using SharpCompress.Compressors; using SharpCompress.Compressors.Deflate; @@ -13,28 +15,58 @@ internal sealed class GZipFilePart : FilePart private string? _name; private readonly Stream _stream; - internal GZipFilePart(Stream stream, IArchiveEncoding archiveEncoding) - : base(archiveEncoding) + internal static GZipFilePart Create(Stream stream, IArchiveEncoding archiveEncoding) { - _stream = stream; - ReadAndValidateGzipHeader(); + var part = new GZipFilePart(stream, archiveEncoding); + + part.ReadAndValidateGzipHeader(); + if (stream.CanSeek) + { + var position = stream.Position; + stream.Position = stream.Length - 8; + part.ReadTrailer(); + stream.Position = position; + part.EntryStartPosition = position; + } + else + { + // For non-seekable streams, we can't read the trailer or track position. + // Set to 0 since the stream will be read sequentially from its current position. + part.EntryStartPosition = 0; + } + return part; + } + + internal static async ValueTask CreateAsync( + Stream stream, + IArchiveEncoding archiveEncoding, + CancellationToken cancellationToken = default + ) + { + var part = new GZipFilePart(stream, archiveEncoding); + + await part.ReadAndValidateGzipHeaderAsync(cancellationToken); if (stream.CanSeek) { var position = stream.Position; stream.Position = stream.Length - 8; - ReadTrailer(); + await part.ReadTrailerAsync(cancellationToken); stream.Position = position; - EntryStartPosition = position; + part.EntryStartPosition = position; } else { // For non-seekable streams, we can't read the trailer or track position. // Set to 0 since the stream will be read sequentially from its current position. - EntryStartPosition = 0; + part.EntryStartPosition = 0; } + return part; } - internal long EntryStartPosition { get; } + private GZipFilePart(Stream stream, IArchiveEncoding archiveEncoding) + : base(archiveEncoding) => _stream = stream; + + internal long EntryStartPosition { get; private set; } internal DateTime? DateModified { get; private set; } internal uint? Crc { get; private set; } @@ -51,12 +83,22 @@ private void ReadTrailer() { // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 Span trailer = stackalloc byte[8]; - var n = _stream.Read(trailer); + _stream.ReadFully(trailer); Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer); UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4)); } + private async ValueTask ReadTrailerAsync(CancellationToken cancellationToken = default) + { + // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 + var trailer = new byte[8]; + _ = await _stream.ReadFullyAsync(trailer, 0, 8, cancellationToken); + + Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer); + UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.AsSpan().Slice(4)); + } + private void ReadAndValidateGzipHeader() { // read the header on the first read @@ -109,6 +151,61 @@ private void ReadAndValidateGzipHeader() } } + private async ValueTask ReadAndValidateGzipHeaderAsync( + CancellationToken cancellationToken = default + ) + { + // read the header on the first read + var header = new byte[10]; + var n = await _stream.ReadAsync(header, 0, 10, cancellationToken); + + // workitem 8501: handle edge case (decompress empty stream) + if (n == 0) + { + return; + } + + if (n != 10) + { + throw new ZlibException("Not a valid GZIP stream."); + } + + if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8) + { + throw new ZlibException("Bad GZIP header."); + } + + var timet = BinaryPrimitives.ReadInt32LittleEndian(header.AsSpan().Slice(4)); + DateModified = TarHeader.EPOCH.AddSeconds(timet); + if ((header[3] & 0x04) == 0x04) + { + // read and discard extra field + var lengthField = new byte[2]; + _ = await _stream.ReadAsync(lengthField, 0, 2, cancellationToken); + + var extraLength = (short)(lengthField[0] + (lengthField[1] * 256)); + var extra = new byte[extraLength]; + + if (!await _stream.ReadFullyAsync(extra, cancellationToken)) + { + throw new ZlibException("Unexpected end-of-file reading GZIP header."); + } + } + if ((header[3] & 0x08) == 0x08) + { + _name = await ReadZeroTerminatedStringAsync(_stream, cancellationToken); + } + if ((header[3] & 0x10) == 0x010) + { + await ReadZeroTerminatedStringAsync(_stream, cancellationToken); + } + if ((header[3] & 0x02) == 0x02) + { + var buf = new byte[1]; + _ = await _stream.ReadAsync(buf, 0, 1, cancellationToken); // CRC16, ignore + } + } + private string ReadZeroTerminatedString(Stream stream) { Span buf1 = stackalloc byte[1]; @@ -134,4 +231,33 @@ private string ReadZeroTerminatedString(Stream stream) var buffer = list.ToArray(); return ArchiveEncoding.Decode(buffer); } + + private async ValueTask ReadZeroTerminatedStringAsync( + Stream stream, + CancellationToken cancellationToken = default + ) + { + var buf1 = new byte[1]; + var list = new List(); + var done = false; + do + { + // workitem 7740 + var n = await stream.ReadAsync(buf1, 0, 1, cancellationToken); + if (n != 1) + { + throw new ZlibException("Unexpected EOF reading GZIP header."); + } + if (buf1[0] == 0) + { + done = true; + } + else + { + list.Add(buf1[0]); + } + } while (!done); + var buffer = list.ToArray(); + return ArchiveEncoding.Decode(buffer); + } } diff --git a/src/SharpCompress/Factories/AceFactory.cs b/src/SharpCompress/Factories/AceFactory.cs index 95f647ddb..6691f1321 100644 --- a/src/SharpCompress/Factories/AceFactory.cs +++ b/src/SharpCompress/Factories/AceFactory.cs @@ -30,13 +30,17 @@ public override bool IsArchive( ) => AceHeader.IsArchive(stream); public IReader OpenReader(Stream stream, ReaderOptions? options) => - AceReader.Open(stream, options); + AceReader.OpenReader(stream, options); - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default - ) => new(AceReader.Open(stream, options)); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)AceReader.OpenReader(stream, options); + } public override ValueTask IsArchiveAsync( Stream stream, diff --git a/src/SharpCompress/Factories/ArcFactory.cs b/src/SharpCompress/Factories/ArcFactory.cs index 37984112a..9ed581ae4 100644 --- a/src/SharpCompress/Factories/ArcFactory.cs +++ b/src/SharpCompress/Factories/ArcFactory.cs @@ -42,13 +42,17 @@ public override bool IsArchive( } public IReader OpenReader(Stream stream, ReaderOptions? options) => - ArcReader.Open(stream, options); + ArcReader.OpenReader(stream, options); - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default - ) => new(ArcReader.Open(stream, options)); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)ArcReader.OpenReader(stream, options); + } public override ValueTask IsArchiveAsync( Stream stream, diff --git a/src/SharpCompress/Factories/ArjFactory.cs b/src/SharpCompress/Factories/ArjFactory.cs index 6e5f7a309..c58314d29 100644 --- a/src/SharpCompress/Factories/ArjFactory.cs +++ b/src/SharpCompress/Factories/ArjFactory.cs @@ -33,13 +33,17 @@ public override bool IsArchive( } public IReader OpenReader(Stream stream, ReaderOptions? options) => - ArjReader.Open(stream, options); + ArjReader.OpenReader(stream, options); - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default - ) => new(ArjReader.Open(stream, options)); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)ArjReader.OpenReader(stream, options); + } public override ValueTask IsArchiveAsync( Stream stream, diff --git a/src/SharpCompress/Factories/Factory.cs b/src/SharpCompress/Factories/Factory.cs index f28f3d249..cd6da6a78 100644 --- a/src/SharpCompress/Factories/Factory.cs +++ b/src/SharpCompress/Factories/Factory.cs @@ -133,10 +133,7 @@ await IsArchiveAsync( ) { ((IStreamStack)stream).StackSeek(pos); - return ( - true, - await readerFactory.OpenReaderAsync(stream, options, cancellationToken) - ); + return (true, readerFactory.OpenAsyncReader(stream, options, cancellationToken)); } } diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index 48f5c63e5..6e92a90d6 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -61,15 +61,12 @@ public override ValueTask IsArchiveAsync( #region IArchiveFactory /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - GZipArchive.Open(stream, readerOptions); + public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) => + GZipArchive.OpenArchive(stream, readerOptions); /// - public ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => GZipArchive.OpenAsync(stream, readerOptions, cancellationToken); + public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => + (IAsyncArchive)OpenArchive(stream, readerOptions); public override ValueTask IsArchiveAsync( Stream stream, @@ -78,41 +75,48 @@ public override ValueTask IsArchiveAsync( ) => new(IsArchive(stream, password, bufferSize)); /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - GZipArchive.Open(fileInfo, readerOptions); - - /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => GZipArchive.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } #endregion #region IMultiArchiveFactory /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => - GZipArchive.Open(streams, readerOptions); + public IArchive OpenArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null + ) => GZipArchive.OpenArchive(streams, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => GZipArchive.OpenAsync(streams, readerOptions, cancellationToken); + ReaderOptions? readerOptions = null + ) => (IAsyncArchive)OpenArchive(streams, readerOptions); /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => - GZipArchive.Open(fileInfos, readerOptions); + public IArchive OpenArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null + ) => GZipArchive.OpenArchive(fileInfos, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => GZipArchive.OpenAsync(fileInfos, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + } #endregion @@ -150,25 +154,29 @@ out IReader? reader /// public IReader OpenReader(Stream stream, ReaderOptions? options) => - GZipReader.Open(stream, options); + GZipReader.OpenReader(stream, options); /// - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(GZipReader.Open(stream, options)); + return (IAsyncReader)GZipReader.OpenReader(stream, options); } + /// + public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + GZipArchive.OpenArchive(fileInfo, readerOptions); + #endregion #region IWriterFactory /// - public IWriter Open(Stream stream, WriterOptions writerOptions) + public IWriter OpenWriter(Stream stream, WriterOptions writerOptions) { if (writerOptions.CompressionType != CompressionType.GZip) { @@ -178,14 +186,18 @@ public IWriter Open(Stream stream, WriterOptions writerOptions) } /// - public ValueTask OpenAsync( + public IAsyncWriter OpenAsyncWriter( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, writerOptions)); + if (writerOptions.CompressionType != CompressionType.GZip) + { + throw new InvalidFormatException("GZip archives only support GZip compression type."); + } + return (IAsyncWriter)OpenWriter(stream, writerOptions); } #endregion @@ -193,7 +205,7 @@ public ValueTask OpenAsync( #region IWriteableArchiveFactory /// - public IWritableArchive CreateWriteableArchive() => GZipArchive.Create(); + public IWritableArchive CreateArchive() => GZipArchive.CreateArchive(); #endregion } diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs index fb9e03abb..40f9216c2 100644 --- a/src/SharpCompress/Factories/RarFactory.cs +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -46,26 +46,27 @@ public override bool IsArchive( #region IArchiveFactory /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - RarArchive.Open(stream, readerOptions); + public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) => + RarArchive.OpenArchive(stream, readerOptions); /// - public ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => RarArchive.OpenAsync(stream, readerOptions, cancellationToken); + public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => + (IAsyncArchive)OpenArchive(stream, readerOptions); /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - RarArchive.Open(fileInfo, readerOptions); + public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + RarArchive.OpenArchive(fileInfo, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => RarArchive.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } public override ValueTask IsArchiveAsync( Stream stream, @@ -78,26 +79,33 @@ public override ValueTask IsArchiveAsync( #region IMultiArchiveFactory /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => - RarArchive.Open(streams, readerOptions); + public IArchive OpenArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null + ) => RarArchive.OpenArchive(streams, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => RarArchive.OpenAsync(streams, readerOptions, cancellationToken); + ReaderOptions? readerOptions = null + ) => (IAsyncArchive)OpenArchive(streams, readerOptions); /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => - RarArchive.Open(fileInfos, readerOptions); + public IArchive OpenArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null + ) => RarArchive.OpenArchive(fileInfos, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => RarArchive.OpenAsync(fileInfos, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + } #endregion @@ -105,17 +113,17 @@ public ValueTask OpenAsync( /// public IReader OpenReader(Stream stream, ReaderOptions? options) => - RarReader.Open(stream, options); + RarReader.OpenReader(stream, options); /// - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(RarReader.Open(stream, options)); + return (IAsyncReader)RarReader.OpenReader(stream, options); } #endregion diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index c387e3b30..c2ea69f16 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -41,26 +41,27 @@ public override bool IsArchive( #region IArchiveFactory /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - SevenZipArchive.Open(stream, readerOptions); + public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) => + SevenZipArchive.OpenArchive(stream, readerOptions); /// - public ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => SevenZipArchive.OpenAsync(stream, readerOptions, cancellationToken); + public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => + (IAsyncArchive)OpenArchive(stream, readerOptions); /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - SevenZipArchive.Open(fileInfo, readerOptions); + public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + SevenZipArchive.OpenArchive(fileInfo, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => SevenZipArchive.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } public override ValueTask IsArchiveAsync( Stream stream, @@ -73,26 +74,33 @@ public override ValueTask IsArchiveAsync( #region IMultiArchiveFactory /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => - SevenZipArchive.Open(streams, readerOptions); + public IArchive OpenArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null + ) => SevenZipArchive.OpenArchive(streams, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => SevenZipArchive.OpenAsync(streams, readerOptions, cancellationToken); + ReaderOptions? readerOptions = null + ) => (IAsyncArchive)OpenArchive(streams, readerOptions); /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => - SevenZipArchive.Open(fileInfos, readerOptions); + public IArchive OpenArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null + ) => SevenZipArchive.OpenArchive(fileInfos, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => SevenZipArchive.OpenAsync(fileInfos, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + } #endregion diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 4e22e0bd4..3ba7687e2 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -72,52 +72,60 @@ public override ValueTask IsArchiveAsync( #region IArchiveFactory /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - TarArchive.Open(stream, readerOptions); + public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) => + TarArchive.OpenArchive(stream, readerOptions); /// - public ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => TarArchive.OpenAsync(stream, readerOptions, cancellationToken); + public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => + (IAsyncArchive)OpenArchive(stream, readerOptions); /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - TarArchive.Open(fileInfo, readerOptions); + public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + TarArchive.OpenArchive(fileInfo, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => TarArchive.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } #endregion #region IMultiArchiveFactory /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => - TarArchive.Open(streams, readerOptions); + public IArchive OpenArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null + ) => TarArchive.OpenArchive(streams, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => TarArchive.OpenAsync(streams, readerOptions, cancellationToken); + ReaderOptions? readerOptions = null + ) => (IAsyncArchive)OpenArchive(streams, readerOptions); /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => - TarArchive.Open(fileInfos, readerOptions); + public IArchive OpenArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null + ) => TarArchive.OpenArchive(fileInfos, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => TarArchive.OpenAsync(fileInfos, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + } #endregion @@ -268,17 +276,17 @@ TestOption testOption /// public IReader OpenReader(Stream stream, ReaderOptions? options) => - TarReader.Open(stream, options); + TarReader.OpenReader(stream, options); /// - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(TarReader.Open(stream, options)); + return (IAsyncReader)TarReader.OpenReader(stream, options); } #endregion @@ -286,18 +294,18 @@ public ValueTask OpenReaderAsync( #region IWriterFactory /// - public IWriter Open(Stream stream, WriterOptions writerOptions) => + public IWriter OpenWriter(Stream stream, WriterOptions writerOptions) => new TarWriter(stream, new TarWriterOptions(writerOptions)); /// - public ValueTask OpenAsync( + public IAsyncWriter OpenAsyncWriter( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, writerOptions)); + return (IAsyncWriter)OpenWriter(stream, writerOptions); } #endregion @@ -305,7 +313,7 @@ public ValueTask OpenAsync( #region IWriteableArchiveFactory /// - public IWritableArchive CreateWriteableArchive() => TarArchive.Create(); + public IWritableArchive CreateArchive() => TarArchive.CreateArchive(); #endregion } diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index a9e62f142..ba2441079 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -139,52 +139,60 @@ public override async ValueTask IsArchiveAsync( #region IArchiveFactory /// - public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) => - ZipArchive.Open(stream, readerOptions); + public IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null) => + ZipArchive.OpenArchive(stream, readerOptions); /// - public ValueTask OpenAsync( - Stream stream, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => ZipArchive.OpenAsync(stream, readerOptions, cancellationToken); + public IAsyncArchive OpenAsyncArchive(Stream stream, ReaderOptions? readerOptions = null) => + (IAsyncArchive)OpenArchive(stream, readerOptions); /// - public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) => - ZipArchive.Open(fileInfo, readerOptions); + public IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null) => + ZipArchive.OpenArchive(fileInfo, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( FileInfo fileInfo, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => ZipArchive.OpenAsync(fileInfo, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfo, readerOptions); + } #endregion #region IMultiArchiveFactory /// - public IArchive Open(IReadOnlyList streams, ReaderOptions? readerOptions = null) => - ZipArchive.Open(streams, readerOptions); + public IArchive OpenArchive( + IReadOnlyList streams, + ReaderOptions? readerOptions = null + ) => ZipArchive.OpenArchive(streams, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null, - CancellationToken cancellationToken = default - ) => ZipArchive.OpenAsync(streams, readerOptions, cancellationToken); + ReaderOptions? readerOptions = null + ) => (IAsyncArchive)OpenArchive(streams, readerOptions); /// - public IArchive Open(IReadOnlyList fileInfos, ReaderOptions? readerOptions = null) => - ZipArchive.Open(fileInfos, readerOptions); + public IArchive OpenArchive( + IReadOnlyList fileInfos, + ReaderOptions? readerOptions = null + ) => ZipArchive.OpenArchive(fileInfos, readerOptions); /// - public ValueTask OpenAsync( + public IAsyncArchive OpenAsyncArchive( IReadOnlyList fileInfos, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default - ) => ZipArchive.OpenAsync(fileInfos, readerOptions, cancellationToken); + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + } #endregion @@ -192,17 +200,17 @@ public ValueTask OpenAsync( /// public IReader OpenReader(Stream stream, ReaderOptions? options) => - ZipReader.Open(stream, options); + ZipReader.OpenReader(stream, options); /// - public ValueTask OpenReaderAsync( + public IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(ZipReader.Open(stream, options)); + return (IAsyncReader)ZipReader.OpenReader(stream, options); } #endregion @@ -210,18 +218,18 @@ public ValueTask OpenReaderAsync( #region IWriterFactory /// - public IWriter Open(Stream stream, WriterOptions writerOptions) => + public IWriter OpenWriter(Stream stream, WriterOptions writerOptions) => new ZipWriter(stream, new ZipWriterOptions(writerOptions)); /// - public ValueTask OpenAsync( + public IAsyncWriter OpenAsyncWriter( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return new(Open(stream, writerOptions)); + return (IAsyncWriter)OpenWriter(stream, writerOptions); } #endregion @@ -229,7 +237,7 @@ public ValueTask OpenAsync( #region IWriteableArchiveFactory /// - public IWritableArchive CreateWriteableArchive() => ZipArchive.Create(); + public IWritableArchive CreateArchive() => ZipArchive.CreateArchive(); #endregion } diff --git a/src/SharpCompress/LazyAsyncReadOnlyCollection.cs b/src/SharpCompress/LazyAsyncReadOnlyCollection.cs index 85caf6113..114bc71a1 100644 --- a/src/SharpCompress/LazyAsyncReadOnlyCollection.cs +++ b/src/SharpCompress/LazyAsyncReadOnlyCollection.cs @@ -10,23 +10,23 @@ namespace SharpCompress; internal sealed class LazyAsyncReadOnlyCollection(IAsyncEnumerable source) : IAsyncEnumerable { - private readonly List backing = new(); - private readonly IAsyncEnumerator source = source.GetAsyncEnumerator(); - private bool fullyLoaded; + private readonly List _backing = new(); + private readonly IAsyncEnumerator _source = source.GetAsyncEnumerator(); + private bool _fullyLoaded; private class LazyLoader( LazyAsyncReadOnlyCollection lazyReadOnlyCollection, CancellationToken cancellationToken ) : IAsyncEnumerator { - private bool disposed; - private int index = -1; + private bool _disposed; + private int _index = -1; public ValueTask DisposeAsync() { - if (!disposed) + if (!_disposed) { - disposed = true; + _disposed = true; } return default; } @@ -34,27 +34,27 @@ public ValueTask DisposeAsync() public async ValueTask MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); - if (index + 1 < lazyReadOnlyCollection.backing.Count) + if (_index + 1 < lazyReadOnlyCollection._backing.Count) { - index++; + _index++; return true; } if ( - !lazyReadOnlyCollection.fullyLoaded - && await lazyReadOnlyCollection.source.MoveNextAsync() + !lazyReadOnlyCollection._fullyLoaded + && await lazyReadOnlyCollection._source.MoveNextAsync() ) { - lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.source.Current); - index++; + lazyReadOnlyCollection._backing.Add(lazyReadOnlyCollection._source.Current); + _index++; return true; } - lazyReadOnlyCollection.fullyLoaded = true; + lazyReadOnlyCollection._fullyLoaded = true; return false; } #region IEnumerator Members - public T Current => lazyReadOnlyCollection.backing[index]; + public T Current => lazyReadOnlyCollection._backing[_index]; #endregion @@ -62,9 +62,9 @@ public async ValueTask MoveNextAsync() public void Dispose() { - if (!disposed) + if (!_disposed) { - disposed = true; + _disposed = true; } } @@ -73,18 +73,18 @@ public void Dispose() internal async ValueTask EnsureFullyLoaded() { - if (!fullyLoaded) + if (!_fullyLoaded) { var loader = new LazyLoader(this, CancellationToken.None); while (await loader.MoveNextAsync()) { // Intentionally empty } - fullyLoaded = true; + _fullyLoaded = true; } } - internal IEnumerable GetLoaded() => backing; + internal IEnumerable GetLoaded() => _backing; #region ICollection Members diff --git a/src/SharpCompress/LazyReadOnlyCollection.cs b/src/SharpCompress/LazyReadOnlyCollection.cs index cc9cb3fdc..eee60bc76 100644 --- a/src/SharpCompress/LazyReadOnlyCollection.cs +++ b/src/SharpCompress/LazyReadOnlyCollection.cs @@ -8,24 +8,24 @@ namespace SharpCompress; internal sealed class LazyReadOnlyCollection : ICollection { - private readonly List backing = new(); - private readonly IEnumerator source; - private bool fullyLoaded; + private readonly List _backing = new(); + private readonly IEnumerator _source; + private bool _fullyLoaded; - public LazyReadOnlyCollection(IEnumerable source) => this.source = source.GetEnumerator(); + public LazyReadOnlyCollection(IEnumerable source) => _source = source.GetEnumerator(); private class LazyLoader : IEnumerator { - private readonly LazyReadOnlyCollection lazyReadOnlyCollection; - private bool disposed; - private int index = -1; + private readonly LazyReadOnlyCollection _lazyReadOnlyCollection; + private bool _disposed; + private int _index = -1; internal LazyLoader(LazyReadOnlyCollection lazyReadOnlyCollection) => - this.lazyReadOnlyCollection = lazyReadOnlyCollection; + _lazyReadOnlyCollection = lazyReadOnlyCollection; #region IEnumerator Members - public T Current => lazyReadOnlyCollection.backing[index]; + public T Current => _lazyReadOnlyCollection._backing[_index]; #endregion @@ -33,9 +33,9 @@ internal LazyLoader(LazyReadOnlyCollection lazyReadOnlyCollection) => public void Dispose() { - if (!disposed) + if (!_disposed) { - disposed = true; + _disposed = true; } } @@ -47,18 +47,18 @@ public void Dispose() public bool MoveNext() { - if (index + 1 < lazyReadOnlyCollection.backing.Count) + if (_index + 1 < _lazyReadOnlyCollection._backing.Count) { - index++; + _index++; return true; } - if (!lazyReadOnlyCollection.fullyLoaded && lazyReadOnlyCollection.source.MoveNext()) + if (!_lazyReadOnlyCollection._fullyLoaded && _lazyReadOnlyCollection._source.MoveNext()) { - lazyReadOnlyCollection.backing.Add(lazyReadOnlyCollection.source.Current); - index++; + _lazyReadOnlyCollection._backing.Add(_lazyReadOnlyCollection._source.Current); + _index++; return true; } - lazyReadOnlyCollection.fullyLoaded = true; + _lazyReadOnlyCollection._fullyLoaded = true; return false; } @@ -69,14 +69,14 @@ public bool MoveNext() internal void EnsureFullyLoaded() { - if (!fullyLoaded) + if (!_fullyLoaded) { this.ForEach(x => { }); - fullyLoaded = true; + _fullyLoaded = true; } } - internal IEnumerable GetLoaded() => backing; + internal IEnumerable GetLoaded() => _backing; #region ICollection Members @@ -87,13 +87,13 @@ internal void EnsureFullyLoaded() public bool Contains(T item) { EnsureFullyLoaded(); - return backing.Contains(item); + return _backing.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { EnsureFullyLoaded(); - backing.CopyTo(array, arrayIndex); + _backing.CopyTo(array, arrayIndex); } public int Count @@ -101,7 +101,7 @@ public int Count get { EnsureFullyLoaded(); - return backing.Count; + return _backing.Count; } } diff --git a/src/SharpCompress/Polyfills/AsyncEnumerableExtensions.cs b/src/SharpCompress/Polyfills/AsyncEnumerableExtensions.cs index 785d4c328..9d0dad0a8 100644 --- a/src/SharpCompress/Polyfills/AsyncEnumerableExtensions.cs +++ b/src/SharpCompress/Polyfills/AsyncEnumerableExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; namespace SharpCompress; @@ -29,29 +31,57 @@ public static async IAsyncEnumerable ToAsyncEnumerable(this IEnumerable public static class AsyncEnumerableExtensions { +#if !NET10_0_OR_GREATER extension(IAsyncEnumerable source) - where T : notnull { - public async ValueTask> ToListAsync() + public async IAsyncEnumerable Select(Func selector) { - var list = new List(); - await foreach (var item in source) + await foreach (var element in source) { - list.Add(item); + yield return selector(element); } - return list; } - public async IAsyncEnumerable Cast() - where TResult : class + public async ValueTask CountAsync(CancellationToken cancellationToken = default) { + await using var e = source.GetAsyncEnumerator(cancellationToken); + + var count = 0; + while (await e.MoveNextAsync()) + { + checked + { + count++; + } + } + + return count; + } + + public async IAsyncEnumerable Take(int count) + { + await foreach (var element in source) + { + yield return element; + + if (--count == 0) + { + break; + } + } + } + + public async ValueTask> ToListAsync() + { + var list = new List(); await foreach (var item in source) { - yield return (item as TResult).NotNull(); + list.Add(item); } + return list; } - public async ValueTask All(Func predicate) + public async ValueTask AllAsync(Func predicate) { await foreach (var item in source) { @@ -75,27 +105,77 @@ public async IAsyncEnumerable Where(Func predicate) } } - public async ValueTask FirstOrDefaultAsync() + public async ValueTask SingleAsync(Func? predicate = null) { - await foreach (var item in source) + IAsyncEnumerator enumerator; + if (predicate is null) { - return item; // Returns the very first item found + enumerator = source.GetAsyncEnumerator(); + } + else + { + enumerator = source.Where(predicate).GetAsyncEnumerator(); } - return default; // Returns null/default if the stream is empty + if (!await enumerator.MoveNextAsync()) + { + throw new InvalidOperationException("The source sequence is empty."); + } + var value = enumerator.Current; + if (await enumerator.MoveNextAsync()) + { + throw new InvalidOperationException( + "The source sequence contains more than one element." + ); + } + return value; } - public async ValueTask Aggregate( - TAccumulate seed, - Func func + public async ValueTask FirstAsync() + { + await foreach (var item in source) + { + return item; + } + throw new InvalidOperationException("The source sequence is empty."); + } + + public async ValueTask FirstOrDefaultAsync( + CancellationToken cancellationToken = default ) { - TAccumulate result = seed; - await foreach (var element in source) + await foreach (var item in source.WithCancellation(cancellationToken)) { - result = func(result, element); + return item; } - return result; + + return default; + } + } +#endif + + public static async IAsyncEnumerable CastAsync( + this IAsyncEnumerable source + ) + where TResult : class + { + await foreach (var item in source) + { + yield return (item as TResult).NotNull(); + } + } + + public static async ValueTask AggregateAsync( + this IAsyncEnumerable source, + TAccumulate seed, + Func func + ) + { + var result = seed; + await foreach (var element in source) + { + result = func(result, element); } + return result; } } diff --git a/src/SharpCompress/Polyfills/StreamExtensions.cs b/src/SharpCompress/Polyfills/StreamExtensions.cs index 6316416da..6803e1e03 100644 --- a/src/SharpCompress/Polyfills/StreamExtensions.cs +++ b/src/SharpCompress/Polyfills/StreamExtensions.cs @@ -27,7 +27,11 @@ public void Skip(long advanceAmount) public Task SkipAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); +#if NET8_0_OR_GREATER + return stream.CopyToAsync(Stream.Null, cancellationToken); +#else return stream.CopyToAsync(Stream.Null); +#endif } internal int Read(Span buffer) diff --git a/src/SharpCompress/Readers/Ace/AceReader.Factory.cs b/src/SharpCompress/Readers/Ace/AceReader.Factory.cs new file mode 100644 index 000000000..653ff021c --- /dev/null +++ b/src/SharpCompress/Readers/Ace/AceReader.Factory.cs @@ -0,0 +1,53 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.Ace; + +public partial class AceReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } + + public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenReader(new FileInfo(filePath), readerOptions); + } + + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenReader(fileInfo.OpenRead(), readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/Ace/AceReader.cs b/src/SharpCompress/Readers/Ace/AceReader.cs index 9c491bd1d..8a7835090 100644 --- a/src/SharpCompress/Readers/Ace/AceReader.cs +++ b/src/SharpCompress/Readers/Ace/AceReader.cs @@ -23,7 +23,7 @@ namespace SharpCompress.Readers.Ace /// - Recovery record support /// - Additional header flags /// - public abstract class AceReader : AbstractReader + public abstract partial class AceReader : AbstractReader { private readonly IArchiveEncoding _archiveEncoding; @@ -50,7 +50,7 @@ private AceReader(Stream stream, ReaderOptions options) /// The stream containing the ACE archive. /// Reader options. /// An AceReader instance. - public static AceReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); return new SingleVolumeAceReader(stream, options ?? new ReaderOptions()); @@ -62,7 +62,7 @@ public static AceReader Open(Stream stream, ReaderOptions? options = null) /// /// /// - public static AceReader Open(IEnumerable streams, ReaderOptions? options = null) + public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) { streams.NotNull(nameof(streams)); return new MultiVolumeAceReader(streams, options ?? new ReaderOptions()); diff --git a/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs b/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs new file mode 100644 index 000000000..22ec26664 --- /dev/null +++ b/src/SharpCompress/Readers/Arc/ArcReader.Factory.cs @@ -0,0 +1,53 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.Arc; + +public partial class ArcReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } + + public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenReader(new FileInfo(filePath), readerOptions); + } + + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenReader(fileInfo.OpenRead(), readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/Arc/ArcReader.cs b/src/SharpCompress/Readers/Arc/ArcReader.cs index 439cdb12e..c99e80966 100644 --- a/src/SharpCompress/Readers/Arc/ArcReader.cs +++ b/src/SharpCompress/Readers/Arc/ArcReader.cs @@ -9,7 +9,7 @@ namespace SharpCompress.Readers.Arc { - public class ArcReader : AbstractReader + public partial class ArcReader : AbstractReader { private ArcReader(Stream stream, ReaderOptions options) : base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0); @@ -22,7 +22,7 @@ private ArcReader(Stream stream, ReaderOptions options) /// /// /// - public static ArcReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); return new ArcReader(stream, options ?? new ReaderOptions()); diff --git a/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs b/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs new file mode 100644 index 000000000..f0f7b01b4 --- /dev/null +++ b/src/SharpCompress/Readers/Arj/ArjReader.Factory.cs @@ -0,0 +1,53 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.Arj; + +public partial class ArjReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } + + public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenReader(new FileInfo(filePath), readerOptions); + } + + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenReader(fileInfo.OpenRead(), readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/Arj/ArjReader.cs b/src/SharpCompress/Readers/Arj/ArjReader.cs index 439fb22c4..94a891853 100644 --- a/src/SharpCompress/Readers/Arj/ArjReader.cs +++ b/src/SharpCompress/Readers/Arj/ArjReader.cs @@ -8,7 +8,7 @@ namespace SharpCompress.Readers.Arj { - public abstract class ArjReader : AbstractReader + public abstract partial class ArjReader : AbstractReader { internal ArjReader(ReaderOptions options) : base(options, ArchiveType.Arj) { } @@ -27,7 +27,7 @@ internal ArjReader(ReaderOptions options) /// /// /// - public static ArjReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); return new SingleVolumeArjReader(stream, options ?? new ReaderOptions()); @@ -39,7 +39,7 @@ public static ArjReader Open(Stream stream, ReaderOptions? options = null) /// /// /// - public static ArjReader Open(IEnumerable streams, ReaderOptions? options = null) + public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) { streams.NotNull(nameof(streams)); return new MultiVolumeArjReader(streams, options ?? new ReaderOptions()); diff --git a/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs b/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs new file mode 100644 index 000000000..606298161 --- /dev/null +++ b/src/SharpCompress/Readers/GZip/GZipReader.Factory.cs @@ -0,0 +1,53 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.GZip; + +public partial class GZipReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } + + public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenReader(new FileInfo(filePath), readerOptions); + } + + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenReader(fileInfo.OpenRead(), readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/GZip/GZipReader.cs b/src/SharpCompress/Readers/GZip/GZipReader.cs index e10d509a6..9fec5993a 100644 --- a/src/SharpCompress/Readers/GZip/GZipReader.cs +++ b/src/SharpCompress/Readers/GZip/GZipReader.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using SharpCompress.Common; using SharpCompress.Common.GZip; namespace SharpCompress.Readers.GZip; -public class GZipReader : AbstractReader +public partial class GZipReader : AbstractReader { private GZipReader(Stream stream, ReaderOptions options) : base(options, ArchiveType.GZip) => Volume = new GZipVolume(stream, options, 0); public override GZipVolume Volume { get; } - #region Open + #region OpenReader /// /// Opens a GZipReader for Non-seeking usage with a single volume @@ -20,13 +20,13 @@ private GZipReader(Stream stream, ReaderOptions options) /// /// /// - public static GZipReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); return new GZipReader(stream, options ?? new ReaderOptions()); } - #endregion Open + #endregion OpenReader protected override IEnumerable GetEntries(Stream stream) => GZipEntry.GetEntries(stream, Options); diff --git a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs index 2b9a6a6b2..be53d6301 100644 --- a/src/SharpCompress/Readers/IAsyncReaderExtensions.cs +++ b/src/SharpCompress/Readers/IAsyncReaderExtensions.cs @@ -65,5 +65,33 @@ await reader .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); + await reader.WriteEntryToAsync(fs, ct).ConfigureAwait(false); + }, + cancellationToken + ) + .ConfigureAwait(false); + + public async ValueTask WriteEntryToAsync( + FileInfo destinationFileInfo, + ExtractionOptions? options = null, + CancellationToken cancellationToken = default + ) => + await reader + .WriteEntryToAsync(destinationFileInfo.FullName, options, cancellationToken) + .ConfigureAwait(false); } } diff --git a/src/SharpCompress/Readers/IReaderFactory.cs b/src/SharpCompress/Readers/IReaderFactory.cs index 4757644ba..52e7d35e3 100644 --- a/src/SharpCompress/Readers/IReaderFactory.cs +++ b/src/SharpCompress/Readers/IReaderFactory.cs @@ -1,19 +1,26 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; namespace SharpCompress.Readers; public interface IReaderFactory : Factories.IFactory { /// - /// Opens a Reader for Non-seeking usage + /// Opens a Reader for Non-seeking usage. /// /// /// /// IReader OpenReader(Stream stream, ReaderOptions? options); - ValueTask OpenReaderAsync( + + /// + /// Opens a Reader for Non-seeking usage asynchronously. + /// + /// + /// + /// + /// + IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options, CancellationToken cancellationToken diff --git a/src/SharpCompress/Readers/IReaderOpenable.cs b/src/SharpCompress/Readers/IReaderOpenable.cs new file mode 100644 index 000000000..a421a49e7 --- /dev/null +++ b/src/SharpCompress/Readers/IReaderOpenable.cs @@ -0,0 +1,36 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; + +namespace SharpCompress.Readers; + +public interface IReaderOpenable +{ + public static abstract IReader OpenReader(string filePath, ReaderOptions? readerOptions = null); + + public static abstract IReader OpenReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null + ); + + public static abstract IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null); + + public static abstract IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); + + public static abstract IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); + + public static abstract IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ); +} +#endif diff --git a/src/SharpCompress/Readers/Rar/RarReader.Factory.cs b/src/SharpCompress/Readers/Rar/RarReader.Factory.cs new file mode 100644 index 000000000..775b37644 --- /dev/null +++ b/src/SharpCompress/Readers/Rar/RarReader.Factory.cs @@ -0,0 +1,41 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.Rar; + +public partial class RarReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/Rar/RarReader.cs b/src/SharpCompress/Readers/Rar/RarReader.cs index 9c6de26f8..34c64d1c3 100644 --- a/src/SharpCompress/Readers/Rar/RarReader.cs +++ b/src/SharpCompress/Readers/Rar/RarReader.cs @@ -11,7 +11,7 @@ namespace SharpCompress.Readers.Rar; /// /// This class faciliates Reading a Rar Archive in a non-seekable forward-only manner /// -public abstract class RarReader : AbstractReader +public abstract partial class RarReader : AbstractReader { private bool _disposed; private RarVolume? volume; @@ -40,27 +40,27 @@ public override void Dispose() public override RarVolume? Volume => volume; - public static RarReader Open(string filePath, ReaderOptions? options = null) + public static IReader OpenReader(string filePath, ReaderOptions? options = null) { filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), options); + return OpenReader(new FileInfo(filePath), options); } - public static RarReader Open(FileInfo fileInfo, ReaderOptions? options = null) + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null) { options ??= new ReaderOptions { LeaveStreamOpen = false }; - return Open(fileInfo.OpenRead(), options); + return OpenReader(fileInfo.OpenRead(), options); } - public static RarReader Open(IEnumerable filePaths, ReaderOptions? options = null) + public static IReader OpenReader(IEnumerable filePaths, ReaderOptions? options = null) { - return Open(filePaths.Select(x => new FileInfo(x)), options); + return OpenReader(filePaths.Select(x => new FileInfo(x)), options); } - public static RarReader Open(IEnumerable fileInfos, ReaderOptions? options = null) + public static IReader OpenReader(IEnumerable fileInfos, ReaderOptions? options = null) { options ??= new ReaderOptions { LeaveStreamOpen = false }; - return Open(fileInfos.Select(x => x.OpenRead()), options); + return OpenReader(fileInfos.Select(x => x.OpenRead()), options); } /// @@ -69,7 +69,7 @@ public static RarReader Open(IEnumerable fileInfos, ReaderOptions? opt /// /// /// - public static RarReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); return new SingleVolumeRarReader(stream, options ?? new ReaderOptions()); @@ -81,7 +81,7 @@ public static RarReader Open(Stream stream, ReaderOptions? options = null) /// /// /// - public static RarReader Open(IEnumerable streams, ReaderOptions? options = null) + public static IReader OpenReader(IEnumerable streams, ReaderOptions? options = null) { streams.NotNull(nameof(streams)); return new MultiVolumeRarReader(streams, options ?? new ReaderOptions()); diff --git a/src/SharpCompress/Readers/ReaderFactory.cs b/src/SharpCompress/Readers/ReaderFactory.cs index 6102f1469..acd4e96bd 100644 --- a/src/SharpCompress/Readers/ReaderFactory.cs +++ b/src/SharpCompress/Readers/ReaderFactory.cs @@ -11,10 +11,10 @@ namespace SharpCompress.Readers; public static class ReaderFactory { - public static IReader Open(string filePath, ReaderOptions? options = null) + public static IReader OpenReader(string filePath, ReaderOptions? options = null) { filePath.NotNullOrEmpty(nameof(filePath)); - return Open(new FileInfo(filePath), options); + return OpenReader(new FileInfo(filePath), options); } /// @@ -24,20 +24,20 @@ public static IReader Open(string filePath, ReaderOptions? options = null) /// /// /// - public static ValueTask OpenAsync( + public static IAsyncReader OpenAsyncReader( string filePath, ReaderOptions? options = null, CancellationToken cancellationToken = default ) { filePath.NotNullOrEmpty(nameof(filePath)); - return OpenAsync(new FileInfo(filePath), options, cancellationToken); + return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken); } - public static IReader Open(FileInfo fileInfo, ReaderOptions? options = null) + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null) { options ??= new ReaderOptions { LeaveStreamOpen = false }; - return Open(fileInfo.OpenRead(), options); + return OpenReader(fileInfo.OpenRead(), options); } /// @@ -47,14 +47,14 @@ public static IReader Open(FileInfo fileInfo, ReaderOptions? options = null) /// /// /// - public static ValueTask OpenAsync( + public static IAsyncReader OpenAsyncReader( FileInfo fileInfo, ReaderOptions? options = null, CancellationToken cancellationToken = default ) { options ??= new ReaderOptions { LeaveStreamOpen = false }; - return OpenAsync(fileInfo.OpenRead(), options, cancellationToken); + return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken); } /// @@ -63,7 +63,7 @@ public static ValueTask OpenAsync( /// /// /// - public static IReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); options ??= new ReaderOptions() { LeaveStreamOpen = false }; @@ -110,7 +110,7 @@ public static IReader Open(Stream stream, ReaderOptions? options = null) ); } - public static async ValueTask OpenAsync( + public static IAsyncReader OpenAsyncReader( Stream stream, ReaderOptions? options = null, CancellationToken cancellationToken = default @@ -136,19 +136,10 @@ public static async ValueTask OpenAsync( if (testedFactory is IReaderFactory readerFactory) { ((IStreamStack)bStream).StackSeek(pos); - if ( - await testedFactory.IsArchiveAsync( - bStream, - options.Password, - options.BufferSize, - cancellationToken - ) - ) + if (testedFactory.IsArchive(bStream)) { ((IStreamStack)bStream).StackSeek(pos); - return await readerFactory - .OpenReaderAsync(bStream, options, cancellationToken) - .ConfigureAwait(false); + return readerFactory.OpenAsyncReader(bStream, options, cancellationToken); } } ((IStreamStack)bStream).StackSeek(pos); @@ -161,20 +152,10 @@ await testedFactory.IsArchiveAsync( continue; // Already tested above } ((IStreamStack)bStream).StackSeek(pos); - if ( - factory is IReaderFactory readerFactory - && await factory.IsArchiveAsync( - bStream, - options.Password, - options.BufferSize, - cancellationToken - ) - ) + if (factory is IReaderFactory readerFactory && factory.IsArchive(bStream)) { ((IStreamStack)bStream).StackSeek(pos); - return await readerFactory - .OpenReaderAsync(bStream, options, cancellationToken) - .ConfigureAwait(false); + return readerFactory.OpenAsyncReader(bStream, options, cancellationToken); } } diff --git a/src/SharpCompress/Readers/Tar/TarReader.Factory.cs b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs new file mode 100644 index 000000000..9a85c4afd --- /dev/null +++ b/src/SharpCompress/Readers/Tar/TarReader.Factory.cs @@ -0,0 +1,53 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.Tar; + +public partial class TarReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } + + public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenReader(new FileInfo(filePath), readerOptions); + } + + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenReader(fileInfo.OpenRead(), readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/Tar/TarReader.cs b/src/SharpCompress/Readers/Tar/TarReader.cs index c92188856..7f53961f2 100644 --- a/src/SharpCompress/Readers/Tar/TarReader.cs +++ b/src/SharpCompress/Readers/Tar/TarReader.cs @@ -16,7 +16,7 @@ namespace SharpCompress.Readers.Tar; -public class TarReader : AbstractReader +public partial class TarReader : AbstractReader { private readonly CompressionType compressionType; @@ -45,7 +45,7 @@ protected override Stream RequestInitialStream() }; } - #region Open + #region OpenReader /// /// Opens a TarReader for Non-seeking usage with a single volume @@ -53,7 +53,7 @@ protected override Stream RequestInitialStream() /// /// /// - public static TarReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); options = options ?? new ReaderOptions(); @@ -115,7 +115,7 @@ public static TarReader Open(Stream stream, ReaderOptions? options = null) return new TarReader(rewindableStream, options, CompressionType.None); } - #endregion Open + #endregion OpenReader protected override IEnumerable GetEntries(Stream stream) => TarEntry.GetEntries( diff --git a/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs b/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs new file mode 100644 index 000000000..289059fcb --- /dev/null +++ b/src/SharpCompress/Readers/Zip/ZipReader.Factory.cs @@ -0,0 +1,53 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Readers.Zip; + +public partial class ZipReader : IReaderOpenable +{ + public static IAsyncReader OpenAsyncReader( + string path, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + path.NotNullOrEmpty(nameof(path)); + return (IAsyncReader)OpenReader(new FileInfo(path), readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + Stream stream, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(stream, readerOptions); + } + + public static IAsyncReader OpenAsyncReader( + FileInfo fileInfo, + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncReader)OpenReader(fileInfo, readerOptions); + } + + public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenReader(new FileInfo(filePath), readerOptions); + } + + public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenReader(fileInfo.OpenRead(), readerOptions); + } +} +#endif diff --git a/src/SharpCompress/Readers/Zip/ZipReader.cs b/src/SharpCompress/Readers/Zip/ZipReader.cs index d15fa7e04..6c3df1815 100644 --- a/src/SharpCompress/Readers/Zip/ZipReader.cs +++ b/src/SharpCompress/Readers/Zip/ZipReader.cs @@ -9,7 +9,7 @@ namespace SharpCompress.Readers.Zip; -public class ZipReader : AbstractReader +public partial class ZipReader : AbstractReader { private readonly StreamingZipHeaderFactory _headerFactory; @@ -45,13 +45,13 @@ private ZipReader(Stream stream, ReaderOptions options, IEnumerable en /// /// /// - public static ZipReader Open(Stream stream, ReaderOptions? options = null) + public static IReader OpenReader(Stream stream, ReaderOptions? options = null) { stream.NotNull(nameof(stream)); return new ZipReader(stream, options ?? new ReaderOptions()); } - public static ZipReader Open( + public static IReader OpenReader( Stream stream, ReaderOptions? options, IEnumerable entries diff --git a/src/SharpCompress/Writers/AbstractWriter.cs b/src/SharpCompress/Writers/AbstractWriter.cs index 7dce62972..63511afad 100644 --- a/src/SharpCompress/Writers/AbstractWriter.cs +++ b/src/SharpCompress/Writers/AbstractWriter.cs @@ -8,7 +8,9 @@ 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 class AbstractWriter(ArchiveType type, WriterOptions writerOptions) : IWriter +public abstract class AbstractWriter(ArchiveType type, WriterOptions writerOptions) + : IWriter, + IAsyncWriter { private bool _isDisposed; diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.Factory.cs b/src/SharpCompress/Writers/GZip/GZipWriter.Factory.cs new file mode 100644 index 000000000..7fd8aec12 --- /dev/null +++ b/src/SharpCompress/Writers/GZip/GZipWriter.Factory.cs @@ -0,0 +1,58 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Writers.GZip; + +public partial class GZipWriter : IWriterOpenable +{ + public static IWriter OpenWriter(string filePath, GZipWriterOptions writerOptions) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenWriter(new FileInfo(filePath), writerOptions); + } + + public static IWriter OpenWriter(FileInfo fileInfo, GZipWriterOptions writerOptions) + { + fileInfo.NotNull(nameof(fileInfo)); + return new GZipWriter(fileInfo.OpenWrite(), writerOptions); + } + + public static IWriter OpenWriter(Stream stream, GZipWriterOptions writerOptions) + { + stream.NotNull(nameof(stream)); + return new GZipWriter(stream, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + string path, + GZipWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(path, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + Stream stream, + GZipWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(stream, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + FileInfo fileInfo, + GZipWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(fileInfo, writerOptions); + } +} +#endif diff --git a/src/SharpCompress/Writers/GZip/GZipWriter.cs b/src/SharpCompress/Writers/GZip/GZipWriter.cs index ad1d59d77..95ebd2530 100644 --- a/src/SharpCompress/Writers/GZip/GZipWriter.cs +++ b/src/SharpCompress/Writers/GZip/GZipWriter.cs @@ -7,7 +7,7 @@ namespace SharpCompress.Writers.GZip; -public sealed class GZipWriter : AbstractWriter +public sealed partial class GZipWriter : AbstractWriter { private bool _wroteToStream; diff --git a/src/SharpCompress/Writers/IWriter.cs b/src/SharpCompress/Writers/IWriter.cs index b51b49722..c33a3bf07 100644 --- a/src/SharpCompress/Writers/IWriter.cs +++ b/src/SharpCompress/Writers/IWriter.cs @@ -10,13 +10,18 @@ public interface IWriter : IDisposable { ArchiveType WriterType { get; } void Write(string filename, Stream source, DateTime? modificationTime); + void WriteDirectory(string directoryName, DateTime? modificationTime); +} + +public interface IAsyncWriter : IDisposable +{ + ArchiveType WriterType { get; } ValueTask WriteAsync( string filename, Stream source, DateTime? modificationTime, CancellationToken cancellationToken = default ); - void WriteDirectory(string directoryName, DateTime? modificationTime); ValueTask WriteDirectoryAsync( string directoryName, DateTime? modificationTime, diff --git a/src/SharpCompress/Writers/IWriterExtensions.cs b/src/SharpCompress/Writers/IWriterExtensions.cs index 9b8fb67b5..a178dd5d5 100644 --- a/src/SharpCompress/Writers/IWriterExtensions.cs +++ b/src/SharpCompress/Writers/IWriterExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading; @@ -8,126 +8,123 @@ namespace SharpCompress.Writers; public static class IWriterExtensions { - public static void Write(this IWriter writer, string entryPath, Stream source) => - writer.Write(entryPath, source, null); - - public static void Write(this IWriter writer, string entryPath, FileInfo source) + extension(IWriter writer) { - if (!source.Exists) - { - throw new ArgumentException("Source does not exist: " + source.FullName); - } - using var stream = source.OpenRead(); - writer.Write(entryPath, stream, source.LastWriteTime); - } + public void Write(string entryPath, Stream source) => writer.Write(entryPath, source, null); - public static void Write(this IWriter writer, string entryPath, string source) => - writer.Write(entryPath, new FileInfo(source)); - - public static void WriteAll( - this IWriter writer, - string directory, - string searchPattern = "*", - SearchOption option = SearchOption.TopDirectoryOnly - ) => writer.WriteAll(directory, searchPattern, null, option); - - public static void WriteAll( - this IWriter writer, - string directory, - string searchPattern = "*", - Func? fileSearchFunc = null, - SearchOption option = SearchOption.TopDirectoryOnly - ) - { - if (!Directory.Exists(directory)) + public void Write(string entryPath, FileInfo source) { - throw new ArgumentException("Directory does not exist: " + directory); + if (!source.Exists) + { + throw new ArgumentException("Source does not exist: " + source.FullName); + } + + using var stream = source.OpenRead(); + writer.Write(entryPath, stream, source.LastWriteTime); } - fileSearchFunc ??= n => true; - foreach ( - var file in Directory - .EnumerateFiles(directory, searchPattern, option) - .Where(fileSearchFunc) + public void Write(string entryPath, string source) => + writer.Write(entryPath, new FileInfo(source)); + + public void WriteAll( + string directory, + string searchPattern = "*", + SearchOption option = SearchOption.TopDirectoryOnly + ) => writer.WriteAll(directory, searchPattern, null, option); + + public void WriteAll( + string directory, + string searchPattern = "*", + Func? fileSearchFunc = null, + SearchOption option = SearchOption.TopDirectoryOnly ) { - writer.Write(file.Substring(directory.Length), file); - } - } + if (!Directory.Exists(directory)) + { + throw new ArgumentException("Directory does not exist: " + directory); + } - public static void WriteDirectory(this IWriter writer, string directoryName) => - writer.WriteDirectory(directoryName, null); - - // Async extensions - public static ValueTask WriteAsync( - this IWriter writer, - string entryPath, - Stream source, - CancellationToken cancellationToken = default - ) => writer.WriteAsync(entryPath, source, null, cancellationToken); - - public static async ValueTask WriteAsync( - this IWriter writer, - string entryPath, - FileInfo source, - CancellationToken cancellationToken = default - ) - { - if (!source.Exists) - { - throw new ArgumentException("Source does not exist: " + source.FullName); + fileSearchFunc ??= n => true; + foreach ( + var file in Directory + .EnumerateFiles(directory, searchPattern, option) + .Where(fileSearchFunc) + ) + { + writer.Write(file.Substring(directory.Length), file); + } } - using var stream = source.OpenRead(); - await writer - .WriteAsync(entryPath, stream, source.LastWriteTime, cancellationToken) - .ConfigureAwait(false); + + public void WriteDirectory(string directoryName) => + writer.WriteDirectory(directoryName, null); } - public static ValueTask WriteAsync( - this IWriter writer, - string entryPath, - string source, - CancellationToken cancellationToken = default - ) => writer.WriteAsync(entryPath, new FileInfo(source), cancellationToken); - - public static ValueTask WriteAllAsync( - this IWriter writer, - string directory, - string searchPattern = "*", - SearchOption option = SearchOption.TopDirectoryOnly, - CancellationToken cancellationToken = default - ) => writer.WriteAllAsync(directory, searchPattern, null, option, cancellationToken); - - public static async ValueTask WriteAllAsync( - this IWriter writer, - string directory, - string searchPattern = "*", - Func? fileSearchFunc = null, - SearchOption option = SearchOption.TopDirectoryOnly, - CancellationToken cancellationToken = default - ) + extension(IAsyncWriter writer) { - if (!Directory.Exists(directory)) - { - throw new ArgumentException("Directory does not exist: " + directory); - } + public ValueTask WriteAsync( + string entryPath, + Stream source, + CancellationToken cancellationToken = default + ) => writer.WriteAsync(entryPath, source, null, cancellationToken); - fileSearchFunc ??= n => true; - foreach ( - var file in Directory - .EnumerateFiles(directory, searchPattern, option) - .Where(fileSearchFunc) + public async ValueTask WriteAsync( + string entryPath, + FileInfo source, + CancellationToken cancellationToken = default ) { + if (!source.Exists) + { + throw new ArgumentException("Source does not exist: " + source.FullName); + } + using var stream = source.OpenRead(); await writer - .WriteAsync(file.Substring(directory.Length), file, cancellationToken) + .WriteAsync(entryPath, stream, source.LastWriteTime, cancellationToken) .ConfigureAwait(false); } - } - public static ValueTask WriteDirectoryAsync( - this IWriter writer, - string directoryName, - CancellationToken cancellationToken = default - ) => writer.WriteDirectoryAsync(directoryName, null, cancellationToken); + public ValueTask WriteAsync( + string entryPath, + string source, + CancellationToken cancellationToken = default + ) => writer.WriteAsync(entryPath, new FileInfo(source), cancellationToken); + + public ValueTask WriteAllAsync( + string directory, + string searchPattern = "*", + SearchOption option = SearchOption.TopDirectoryOnly, + CancellationToken cancellationToken = default + ) => writer.WriteAllAsync(directory, searchPattern, null, option, cancellationToken); + + public async ValueTask WriteAllAsync( + string directory, + string searchPattern = "*", + Func? fileSearchFunc = null, + SearchOption option = SearchOption.TopDirectoryOnly, + CancellationToken cancellationToken = default + ) + { + if (!Directory.Exists(directory)) + { + throw new ArgumentException("Directory does not exist: " + directory); + } + + fileSearchFunc ??= n => true; + foreach ( + var file in Directory + .EnumerateFiles(directory, searchPattern, option) + .Where(fileSearchFunc) + ) + { + await writer + .WriteAsync(file.Substring(directory.Length), file, cancellationToken) + .ConfigureAwait(false); + } + } + + public ValueTask WriteDirectoryAsync( + string directoryName, + CancellationToken cancellationToken = default + ) => writer.WriteDirectoryAsync(directoryName, null, cancellationToken); + } } diff --git a/src/SharpCompress/Writers/IWriterFactory.cs b/src/SharpCompress/Writers/IWriterFactory.cs index f933e8197..f8bd8bde6 100644 --- a/src/SharpCompress/Writers/IWriterFactory.cs +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -1,15 +1,14 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SharpCompress.Factories; namespace SharpCompress.Writers; public interface IWriterFactory : IFactory { - IWriter Open(Stream stream, WriterOptions writerOptions); + IWriter OpenWriter(Stream stream, WriterOptions writerOptions); - ValueTask OpenAsync( + IAsyncWriter OpenAsyncWriter( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Writers/IWriterOpenable.cs b/src/SharpCompress/Writers/IWriterOpenable.cs new file mode 100644 index 000000000..14b98f711 --- /dev/null +++ b/src/SharpCompress/Writers/IWriterOpenable.cs @@ -0,0 +1,41 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; + +namespace SharpCompress.Writers; + +public interface IWriterOpenable + where TWriterOptions : WriterOptions +{ + public static abstract IWriter OpenWriter(string filePath, TWriterOptions writerOptions); + + public static abstract IWriter OpenWriter(FileInfo fileInfo, TWriterOptions writerOptions); + public static abstract IWriter OpenWriter(Stream stream, TWriterOptions writerOptions); + + /// + /// Opens a Writer asynchronously. + /// + /// The stream to write to. + /// The archive type. + /// Writer options. + /// Cancellation token. + /// A task that returns an IWriter. + public static abstract IAsyncWriter OpenAsyncWriter( + Stream stream, + TWriterOptions writerOptions, + CancellationToken cancellationToken = default + ); + + public static abstract IAsyncWriter OpenAsyncWriter( + string filePath, + TWriterOptions writerOptions, + CancellationToken cancellationToken = default + ); + + public static abstract IAsyncWriter OpenAsyncWriter( + FileInfo fileInfo, + TWriterOptions writerOptions, + CancellationToken cancellationToken = default + ); +} +#endif diff --git a/src/SharpCompress/Writers/Tar/TarWriter.Factory.cs b/src/SharpCompress/Writers/Tar/TarWriter.Factory.cs new file mode 100644 index 000000000..c5f9c846b --- /dev/null +++ b/src/SharpCompress/Writers/Tar/TarWriter.Factory.cs @@ -0,0 +1,58 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Writers.Tar; + +public partial class TarWriter : IWriterOpenable +{ + public static IWriter OpenWriter(string filePath, TarWriterOptions writerOptions) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenWriter(new FileInfo(filePath), writerOptions); + } + + public static IWriter OpenWriter(FileInfo fileInfo, TarWriterOptions writerOptions) + { + fileInfo.NotNull(nameof(fileInfo)); + return new TarWriter(fileInfo.OpenWrite(), writerOptions); + } + + public static IWriter OpenWriter(Stream stream, TarWriterOptions writerOptions) + { + stream.NotNull(nameof(stream)); + return new TarWriter(stream, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + string path, + TarWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(path, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + Stream stream, + TarWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(stream, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + FileInfo fileInfo, + TarWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(fileInfo, writerOptions); + } +} +#endif diff --git a/src/SharpCompress/Writers/Tar/TarWriter.cs b/src/SharpCompress/Writers/Tar/TarWriter.cs index aec014540..ac16e66f2 100644 --- a/src/SharpCompress/Writers/Tar/TarWriter.cs +++ b/src/SharpCompress/Writers/Tar/TarWriter.cs @@ -12,7 +12,7 @@ namespace SharpCompress.Writers.Tar; -public class TarWriter : AbstractWriter +public partial class TarWriter : AbstractWriter { private readonly bool finalizeArchiveOnClose; private TarHeaderWriteFormat headerFormat; diff --git a/src/SharpCompress/Writers/WriterFactory.cs b/src/SharpCompress/Writers/WriterFactory.cs index b7ff5cd41..3d482541d 100644 --- a/src/SharpCompress/Writers/WriterFactory.cs +++ b/src/SharpCompress/Writers/WriterFactory.cs @@ -2,14 +2,69 @@ using System.IO; using System.Linq; using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; namespace SharpCompress.Writers; public static class WriterFactory { - public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions writerOptions) + public static IWriter OpenWriter( + string filePath, + ArchiveType archiveType, + WriterOptions writerOptions + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenWriter(new FileInfo(filePath), archiveType, writerOptions); + } + + public static IWriter OpenWriter( + FileInfo fileInfo, + ArchiveType archiveType, + WriterOptions writerOptions + ) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenWriter(fileInfo.OpenWrite(), archiveType, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + string filePath, + ArchiveType archiveType, + WriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenAsyncWriter( + new FileInfo(filePath), + archiveType, + writerOptions, + cancellationToken + ); + } + + public static IAsyncWriter OpenAsyncWriter( + FileInfo fileInfo, + ArchiveType archiveType, + WriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + fileInfo.NotNull(nameof(fileInfo)); + return OpenAsyncWriter( + fileInfo.Open(FileMode.Create, FileAccess.Write), + archiveType, + writerOptions, + cancellationToken + ); + } + + public static IWriter OpenWriter( + Stream stream, + ArchiveType archiveType, + WriterOptions writerOptions + ) { var factory = Factories .Factory.Factories.OfType() @@ -17,7 +72,7 @@ public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions if (factory != null) { - return factory.Open(stream, writerOptions); + return factory.OpenWriter(stream, writerOptions); } throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); @@ -31,7 +86,7 @@ public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions /// Writer options. /// Cancellation token. /// A task that returns an IWriter. - public static async ValueTask OpenAsync( + public static IAsyncWriter OpenAsyncWriter( Stream stream, ArchiveType archiveType, WriterOptions writerOptions, @@ -44,9 +99,7 @@ public static async ValueTask OpenAsync( if (factory != null) { - return await factory - .OpenAsync(stream, writerOptions, cancellationToken) - .ConfigureAwait(false); + return factory.OpenAsyncWriter(stream, writerOptions, cancellationToken); } throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.Factory.cs b/src/SharpCompress/Writers/Zip/ZipWriter.Factory.cs new file mode 100644 index 000000000..0df1a81ce --- /dev/null +++ b/src/SharpCompress/Writers/Zip/ZipWriter.Factory.cs @@ -0,0 +1,58 @@ +#if NET8_0_OR_GREATER +using System.IO; +using System.Threading; +using SharpCompress.Common; + +namespace SharpCompress.Writers.Zip; + +public partial class ZipWriter : IWriterOpenable +{ + public static IWriter OpenWriter(string filePath, ZipWriterOptions writerOptions) + { + filePath.NotNullOrEmpty(nameof(filePath)); + return OpenWriter(new FileInfo(filePath), writerOptions); + } + + public static IWriter OpenWriter(FileInfo fileInfo, ZipWriterOptions writerOptions) + { + fileInfo.NotNull(nameof(fileInfo)); + return new ZipWriter(fileInfo.OpenWrite(), writerOptions); + } + + public static IWriter OpenWriter(Stream stream, ZipWriterOptions writerOptions) + { + stream.NotNull(nameof(stream)); + return new ZipWriter(stream, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + string path, + ZipWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(path, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + Stream stream, + ZipWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(stream, writerOptions); + } + + public static IAsyncWriter OpenAsyncWriter( + FileInfo fileInfo, + ZipWriterOptions writerOptions, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return (IAsyncWriter)OpenWriter(fileInfo, writerOptions); + } +} +#endif diff --git a/src/SharpCompress/Writers/Zip/ZipWriter.cs b/src/SharpCompress/Writers/Zip/ZipWriter.cs index 845856b8f..dc8929b8e 100644 --- a/src/SharpCompress/Writers/Zip/ZipWriter.cs +++ b/src/SharpCompress/Writers/Zip/ZipWriter.cs @@ -18,7 +18,7 @@ namespace SharpCompress.Writers.Zip; -public class ZipWriter : AbstractWriter +public partial class ZipWriter : AbstractWriter { private readonly CompressionType compressionType; private readonly int compressionLevel; diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 032c15c49..413253332 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.1, )", - "resolved": "10.0.1", - "contentHash": "ISahzLHsHY7vrwqr2p1YWZ+gsxoBRtH7gWRDK8fDUst9pp2He0GiesaqEfeX0V8QMCJM3eNEHGGpnIcPjFo2NQ==" + "requested": "[10.0.0, )", + "resolved": "10.0.0", + "contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", diff --git a/tests/SharpCompress.Performance/Program.cs b/tests/SharpCompress.Performance/Program.cs index d445208af..c6d29ec64 100644 --- a/tests/SharpCompress.Performance/Program.cs +++ b/tests/SharpCompress.Performance/Program.cs @@ -30,14 +30,14 @@ for (int i = 0; i < 50; i++) { - using var found = ArchiveFactory.Open(arcs[0]); + using var found = ArchiveFactory.OpenArchive(arcs[0]); foreach (var entry in found.Entries.Where(entry => !entry.IsDirectory)) { Console.WriteLine($"Extracting {entry.Key}"); using var entryStream = entry.OpenEntryStream(); entryStream.CopyTo(Stream.Null); } - /*using var found = ReaderFactory.Open(arcs[0]); + /*using var found = ReaderFactory.OpenReader(arcs[0]); while (found.MoveToNextEntry()) { var entry = found.Entry; diff --git a/tests/SharpCompress.Test/Ace/AceReaderTests.cs b/tests/SharpCompress.Test/Ace/AceReaderTests.cs index b6cf97f2a..60c51c120 100644 --- a/tests/SharpCompress.Test/Ace/AceReaderTests.cs +++ b/tests/SharpCompress.Test/Ace/AceReaderTests.cs @@ -53,7 +53,7 @@ public void Ace_Multi_Reader() var exception = Assert.Throws(() => DoMultiReader( ["Ace.store.split.ace", "Ace.store.split.c01"], - streams => AceReader.Open(streams) + streams => AceReader.OpenReader(streams) ) ); } diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 3001b4bde..84a3596df 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -52,7 +52,7 @@ CompressionType compression { try { - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); Assert.True(archive.IsSolid); using (var reader = archive.ExtractAllEntries()) { @@ -129,7 +129,7 @@ IEnumerable testArchives throwOnDispose: true ) ) - using (var archive = archiveFactory.Open(stream, readerOptions)) + using (var archive = archiveFactory.OpenArchive(stream, readerOptions)) { try { @@ -168,7 +168,7 @@ IEnumerable testArchives ) { using ( - var archive = ArchiveFactory.Open( + var archive = ArchiveFactory.OpenArchive( testArchives.Select(a => new FileInfo(a)), readerOptions ) @@ -200,7 +200,7 @@ IEnumerable testArchives ) { using ( - var archive = ArchiveFactory.Open( + var archive = ArchiveFactory.OpenArchive( testArchives.Select(f => new FileInfo(f)), readerOptions ) @@ -235,7 +235,10 @@ IEnumerable testArchives ) { var src = testArchives.ToArray(); - using var archive = ArchiveFactory.Open(src.Select(f => new FileInfo(f)), readerOptions); + using var archive = ArchiveFactory.OpenArchive( + src.Select(f => new FileInfo(f)), + readerOptions + ); var idx = 0; foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -260,7 +263,7 @@ protected void ArchiveExtractToDirectory( ) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = ArchiveFactory.Open(new FileInfo(testArchive), readerOptions)) + using (var archive = ArchiveFactory.OpenArchive(new FileInfo(testArchive), readerOptions)) { archive.WriteToDirectory(SCRATCH_FILES_PATH); } @@ -274,7 +277,7 @@ protected void ArchiveFileRead( ) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = archiveFactory.Open(new FileInfo(testArchive), readerOptions)) + using (var archive = archiveFactory.OpenArchive(new FileInfo(testArchive), readerOptions)) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -302,7 +305,7 @@ protected void ArchiveFileSkip( } var expected = new Stack(fileOrder.Split(' ')); testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using var archive = ArchiveFactory.Open(testArchive, readerOptions); + using var archive = ArchiveFactory.OpenArchive(testArchive, readerOptions); foreach (var entry in archive.Entries) { Assert.Equal(expected.Pop(), entry.Key); @@ -315,7 +318,7 @@ protected void ArchiveFileSkip( protected void ArchiveFileReadEx(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using (var archive = ArchiveFactory.Open(testArchive)) + using (var archive = ArchiveFactory.OpenArchive(testArchive)) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -337,7 +340,7 @@ protected void ArchiveFileReadEx(string testArchive) protected void ArchiveDeltaDistanceRead(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using var archive = ArchiveFactory.Open(testArchive); + using var archive = ArchiveFactory.OpenArchive(testArchive); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -377,7 +380,25 @@ protected static IWriter CreateWriterWithLevel( { writerOptions.CompressionLevel = compressionLevel.Value; } - return WriterFactory.Open(stream, ArchiveType.Zip, writerOptions); + return WriterFactory.OpenWriter(stream, ArchiveType.Zip, writerOptions); + } + + protected static IAsyncWriter CreateWriterWithLevelAsync( + Stream stream, + CompressionType compressionType, + int? compressionLevel = null + ) + { + var writerOptions = new ZipWriterOptions(compressionType); + if (compressionLevel.HasValue) + { + writerOptions.CompressionLevel = compressionLevel.Value; + } + return WriterFactory.OpenAsyncWriter( + new AsyncOnlyStream(stream), + ArchiveType.Zip, + writerOptions + ); } /// @@ -389,7 +410,7 @@ protected void VerifyArchiveContent( ) { zipStream.Position = 0; - using var archive = ArchiveFactory.Open(zipStream); + using var archive = ArchiveFactory.OpenArchive(zipStream); Assert.Equal(expectedFiles.Count, archive.Entries.Count()); foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) @@ -540,7 +561,7 @@ CompressionType expectedCompressionType ) { zipStream.Position = 0; - using var archive = ArchiveFactory.Open(zipStream); + using var archive = ArchiveFactory.OpenArchive(zipStream); foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) { @@ -557,7 +578,7 @@ string entryName ) { zipStream.Position = 0; - using var archive = ArchiveFactory.Open(zipStream); + using var archive = ArchiveFactory.OpenArchive(zipStream); var entry = archive.Entries.FirstOrDefault(e => e.Key == entryName && !e.IsDirectory); Assert.NotNull(entry); @@ -601,7 +622,7 @@ IEnumerable testArchives ) ) await using ( - var archive = await archiveFactory.OpenAsync( + var archive = archiveFactory.OpenAsyncArchive( new AsyncOnlyStream(stream), readerOptions ) @@ -632,7 +653,7 @@ await entry.WriteToDirectoryAsync( } [Fact] - public void ArchiveFactory_Open_WithPreWrappedStream() + public async Task ArchiveFactory_Open_WithPreWrappedStream() { // Test that ArchiveFactory.Open works correctly with a stream that's already wrapped // This addresses the issue where ZIP files fail to open on Linux @@ -641,25 +662,29 @@ public void ArchiveFactory_Open_WithPreWrappedStream() // Open with a pre-wrapped stream using (var fileStream = File.OpenRead(testArchive)) using (var wrappedStream = SharpCompressStream.Create(fileStream, bufferSize: 32768)) - using (var archive = ArchiveFactory.Open(wrappedStream)) + await using ( + var archive = await ArchiveFactory.OpenAsyncArchive(new AsyncOnlyStream(wrappedStream)) + ) { Assert.Equal(ArchiveType.Zip, archive.Type); - Assert.Equal(3, archive.Entries.Count()); + Assert.Equal(3, await archive.EntriesAsync.CountAsync()); } } [Fact] - public void ArchiveFactory_Open_WithRawFileStream() + public async Task ArchiveFactory_Open_WithRawFileStream() { // Test that ArchiveFactory.Open works correctly with a raw FileStream // This is the common use case reported in the issue var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.noEmptyDirs.zip"); using (var stream = File.OpenRead(testArchive)) - using (var archive = ArchiveFactory.Open(stream)) + await using ( + var archive = await ArchiveFactory.OpenAsyncArchive(new AsyncOnlyStream(stream)) + ) { Assert.Equal(ArchiveType.Zip, archive.Type); - Assert.Equal(3, archive.Entries.Count()); + Assert.Equal(3, await archive.EntriesAsync.CountAsync()); } } } diff --git a/tests/SharpCompress.Test/Arj/ArjReaderTests.cs b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs index bad98ec80..054c18f60 100644 --- a/tests/SharpCompress.Test/Arj/ArjReaderTests.cs +++ b/tests/SharpCompress.Test/Arj/ArjReaderTests.cs @@ -54,7 +54,7 @@ public void Arj_Multi_Reader() "Arj.store.split.a04", "Arj.store.split.a05", ], - streams => ArjReader.Open(streams) + streams => ArjReader.OpenReader(streams) ) ); } diff --git a/tests/SharpCompress.Test/BZip2/BZip2ReaderTests.cs b/tests/SharpCompress.Test/BZip2/BZip2ReaderTests.cs index e0fa0583f..620f307c2 100644 --- a/tests/SharpCompress.Test/BZip2/BZip2ReaderTests.cs +++ b/tests/SharpCompress.Test/BZip2/BZip2ReaderTests.cs @@ -16,6 +16,6 @@ public void BZip2_Reader_Factory() Stream stream = new MemoryStream( new byte[] { 0x42, 0x5a, 0x68, 0x34, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0x35 } ); - Assert.Throws(typeof(InvalidOperationException), () => ReaderFactory.Open(stream)); + Assert.Throws(typeof(InvalidOperationException), () => ReaderFactory.OpenReader(stream)); } } diff --git a/tests/SharpCompress.Test/ExtractAll.cs b/tests/SharpCompress.Test/ExtractAll.cs index d2643fcd9..1e047bc07 100644 --- a/tests/SharpCompress.Test/ExtractAll.cs +++ b/tests/SharpCompress.Test/ExtractAll.cs @@ -23,7 +23,7 @@ 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.OpenAsync(testArchive); + await using var archive = await ArchiveFactory.OpenAsyncArchive(testArchive); await archive.WriteToDirectoryAsync(SCRATCH_FILES_PATH, options); } @@ -40,7 +40,7 @@ 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.Open(testArchive); + using var archive = ArchiveFactory.OpenArchive(testArchive); archive.WriteToDirectory(SCRATCH_FILES_PATH, options); } } diff --git a/tests/SharpCompress.Test/ExtractAllEntriesTests.cs b/tests/SharpCompress.Test/ExtractAllEntriesTests.cs index 007f620f7..57bbcc505 100644 --- a/tests/SharpCompress.Test/ExtractAllEntriesTests.cs +++ b/tests/SharpCompress.Test/ExtractAllEntriesTests.cs @@ -18,7 +18,7 @@ public void ExtractAllEntries_WithProgressReporting_NonSolidArchive() { var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"); - using var archive = ArchiveFactory.Open(archivePath); + using var archive = ArchiveFactory.OpenArchive(archivePath); Assert.Throws(() => { using var reader = archive.ExtractAllEntries(); @@ -30,7 +30,7 @@ public void ExtractAllEntries_WithProgressReporting_SolidArchive() { var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "Rar.solid.rar"); - using var archive = ArchiveFactory.Open(archivePath); + using var archive = ArchiveFactory.OpenArchive(archivePath); Assert.True(archive.IsSolid); // Calculate total size like user code does diff --git a/tests/SharpCompress.Test/ExtractionTests.cs b/tests/SharpCompress.Test/ExtractionTests.cs index 04b5b08f6..2edb8110a 100644 --- a/tests/SharpCompress.Test/ExtractionTests.cs +++ b/tests/SharpCompress.Test/ExtractionTests.cs @@ -27,7 +27,7 @@ public void Extraction_ShouldHandleCaseInsensitivePathsOnWindows() using (var stream = File.Create(testArchive)) { using var writer = (ZipWriter) - WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate); + WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate); // Create a test file to add to the archive var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile.txt"); @@ -39,7 +39,7 @@ public void Extraction_ShouldHandleCaseInsensitivePathsOnWindows() // Extract the archive - this should succeed regardless of path casing using (var stream = File.OpenRead(testArchive)) { - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); // This should not throw an exception even if Path.GetFullPath returns // a path with different casing than the actual directory @@ -72,7 +72,7 @@ public void Extraction_ShouldPreventPathTraversalAttacks() using (var stream = File.Create(testArchive)) { using var writer = (ZipWriter) - WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate); + WriterFactory.OpenWriter(stream, ArchiveType.Zip, CompressionType.Deflate); var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile2.txt"); File.WriteAllText(testFilePath, "Test content"); @@ -84,7 +84,7 @@ public void Extraction_ShouldPreventPathTraversalAttacks() // Extract the archive - this should throw an exception for path traversal using (var stream = File.OpenRead(testArchive)) { - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); var exception = Assert.Throws(() => reader.WriteAllToDirectory( diff --git a/tests/SharpCompress.Test/GZip/AsyncTests.cs b/tests/SharpCompress.Test/GZip/AsyncTests.cs index 3961ffc62..fc6109b3e 100644 --- a/tests/SharpCompress.Test/GZip/AsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/AsyncTests.cs @@ -26,7 +26,7 @@ public async ValueTask Reader_Async_Extract_All() #else await using var stream = File.OpenRead(testArchive); #endif - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); await reader.WriteAllToDirectoryAsync( SCRATCH_FILES_PATH, @@ -51,7 +51,7 @@ public async ValueTask Reader_Async_Extract_Single_Entry() #else await using var stream = File.OpenRead(testArchive); #endif - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); while (await reader.MoveToNextEntryAsync()) { @@ -74,9 +74,11 @@ public async ValueTask Reader_Async_Extract_Single_Entry() public async ValueTask Archive_Entry_Async_Open_Stream() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"); - using var archive = ArchiveFactory.Open(testArchive); + await using var archive = await ArchiveFactory.OpenAsyncArchive( + new AsyncOnlyStream(File.OpenRead(testArchive)) + ); - foreach (var entry in archive.Entries.Where(e => !e.IsDirectory).Take(1)) + await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory).Take(1)) { #if NETFRAMEWORK using var entryStream = await entry.OpenEntryStreamAsync(); @@ -103,7 +105,13 @@ public async ValueTask Writer_Async_Write_Single_File() #else await using (var stream = File.Create(outputPath)) #endif - using (var writer = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) + using ( + var writer = WriterFactory.OpenAsyncWriter( + new AsyncOnlyStream(stream), + ArchiveType.Zip, + CompressionType.Deflate + ) + ) { var testFile = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"); @@ -117,8 +125,8 @@ public async ValueTask Writer_Async_Write_Single_File() // Verify the archive was created and contains the entry Assert.True(File.Exists(outputPath)); - await using var archive = ZipArchive.Open(outputPath); - Assert.Single(archive.Entries.Where(e => !e.IsDirectory)); + await using var archive = ZipArchive.OpenAsyncArchive(outputPath); + Assert.Single(await archive.EntriesAsync.Where(e => !e.IsDirectory).ToListAsync()); } [Fact] @@ -133,7 +141,7 @@ public async ValueTask Async_With_Cancellation_Token() #else await using var stream = File.OpenRead(testArchive); #endif - await using var reader = await ReaderFactory.OpenAsync( + await using var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), cancellationToken: cts.Token ); @@ -187,7 +195,7 @@ public async ValueTask EntryStream_ReadAsync_Works() #else await using var stream = File.OpenRead(testArchive); #endif - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); while (await reader.MoveToNextEntryAsync()) { diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs index caa30ffa1..7ebf2425d 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveAsyncTests.cs @@ -5,6 +5,7 @@ using SharpCompress.Archives.GZip; using SharpCompress.Archives.Tar; using SharpCompress.Common; +using SharpCompress.Test.Mocks; using Xunit; namespace SharpCompress.Test.GZip; @@ -21,7 +22,7 @@ public async ValueTask GZip_Archive_Generic_Async() #else await using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) #endif - using (var archive = ArchiveFactory.Open(stream)) + using (var archive = ArchiveFactory.OpenArchive(new AsyncOnlyStream(stream))) { var entry = archive.Entries.First(); await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); @@ -47,17 +48,19 @@ public async ValueTask GZip_Archive_Async() #else await using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) #endif - await using (var archive = GZipArchive.Open(stream)) { - var entry = archive.Entries.First(); - await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); + await using (var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) + { + var entry = await archive.EntriesAsync.FirstAsync(); + await entry.WriteToFileAsync(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); - var size = entry.Size; - var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); - var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + var size = entry.Size; + var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); + var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); - Assert.Equal(size, scratch.Length); - Assert.Equal(size, test.Length); + Assert.Equal(size, scratch.Length); + Assert.Equal(size, test.Length); + } } CompareArchivesByPath( Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), @@ -74,9 +77,11 @@ public async ValueTask GZip_Archive_NoAdd_Async() #else await using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); #endif - await using var archive = GZipArchive.Open(stream); - Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); - await archive.SaveToAsync(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); + await using (var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))) + { + Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); + await archive.SaveToAsync(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); + } } [Fact] @@ -93,7 +98,7 @@ public async ValueTask GZip_Archive_Multiple_Reads_Async() inputStream.Position = 0; } - await using var archive = GZipArchive.Open(inputStream); + using var archive = GZipArchive.OpenArchive(new AsyncOnlyStream(inputStream)); var archiveEntry = archive.Entries.First(); MemoryStream tarStream; @@ -140,21 +145,21 @@ public async ValueTask GZip_Archive_Multiple_Reads_Async() } [Fact] - public void TestGzCrcWithMostSignificantBitNotNegative_Async() + public async Task TestGzCrcWithMostSignificantBitNotNegative_Async() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var archive = GZipArchive.Open(stream); - foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + await using var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) { Assert.InRange(entry.Crc, 0L, 0xFFFFFFFFL); } } [Fact] - public void TestGzArchiveTypeGzip_Async() + public async Task TestGzArchiveTypeGzip_Async() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var archive = GZipArchive.Open(stream); + await using var archive = GZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); Assert.Equal(archive.Type, ArchiveType.GZip); } } diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs index 201effcbc..d1d7fbf1c 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs @@ -10,7 +10,7 @@ public class GZipArchiveDirectoryTests : TestBase [Fact] public void GZipArchive_AddDirectoryEntry_ThrowsNotSupportedException() { - using var archive = GZipArchive.Create(); + using var archive = GZipArchive.CreateArchive(); Assert.Throws(() => archive.AddDirectoryEntry("test-dir", DateTime.Now) diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs index c8a87327d..448406a29 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs @@ -17,7 +17,7 @@ public class GZipArchiveTests : ArchiveTests public void GZip_Archive_Generic() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = ArchiveFactory.Open(stream)) + using (var archive = ArchiveFactory.OpenArchive(stream)) { var entry = archive.Entries.First(); entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); @@ -39,7 +39,7 @@ public void GZip_Archive_Generic() public void GZip_Archive() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz"))) - using (var archive = GZipArchive.Open(stream)) + using (var archive = GZipArchive.OpenArchive(stream)) { var entry = archive.Entries.First(); entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); @@ -62,7 +62,7 @@ public void GZip_Archive_NoAdd() { var jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg"); using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var archive = GZipArchive.Open(stream); + using var archive = GZipArchive.OpenArchive(stream); Assert.Throws(() => archive.AddEntry("jpg\\test.jpg", jpg)); archive.SaveTo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); } @@ -76,7 +76,7 @@ public void GZip_Archive_Multiple_Reads() fileStream.CopyTo(inputStream); inputStream.Position = 0; } - using var archive = GZipArchive.Open(inputStream); + using var archive = GZipArchive.OpenArchive(inputStream); var archiveEntry = archive.Entries.First(); MemoryStream tarStream; @@ -110,7 +110,7 @@ public void GZip_Archive_Multiple_Reads() public void TestGzCrcWithMostSignificantBitNotNegative() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var archive = GZipArchive.Open(stream); + using var archive = GZipArchive.OpenArchive(stream); //process all entries in solid archive until the one we want to test foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -122,7 +122,7 @@ public void TestGzCrcWithMostSignificantBitNotNegative() public void TestGzArchiveTypeGzip() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var archive = GZipArchive.Open(stream); + using var archive = GZipArchive.OpenArchive(stream); Assert.Equal(archive.Type, ArchiveType.GZip); } @@ -137,7 +137,7 @@ public void GZip_Archive_NonSeekableStream() // Create a non-seekable wrapper around the MemoryStream using var nonSeekableStream = new NonSeekableStream(buffer); - using var reader = SharpCompress.Readers.GZip.GZipReader.Open(nonSeekableStream); + using var reader = SharpCompress.Readers.GZip.GZipReader.OpenReader(nonSeekableStream); // Verify we can move to the first entry and read it without exceptions Assert.True(reader.MoveToNextEntry()); diff --git a/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs index befd1440b..175a89902 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderAsyncTests.cs @@ -22,7 +22,7 @@ public async ValueTask GZip_Reader_Generic2_Async() { //read only as GZip item using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); while (await reader.MoveToNextEntryAsync()) { Assert.NotEqual(0, reader.Entry.Size); @@ -70,7 +70,7 @@ ReaderOptions options bufferSize: options.BufferSize ); using var testStream = new TestStream(protectedStream); - await using (var reader = await ReaderFactory.OpenAsync(testStream, options, default)) + await using (var reader = ReaderFactory.OpenAsyncReader(testStream, options, default)) { await UseReaderAsync(reader, expectedCompression); protectedStream.ThrowOnDispose = false; diff --git a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs index 187cd80c0..c382a471f 100644 --- a/tests/SharpCompress.Test/GZip/GZipReaderTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipReaderTests.cs @@ -18,7 +18,7 @@ public void GZip_Reader_Generic2() { //read only as GZip itme using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.gz")); - using var reader = GZipReader.Open(new SharpCompressStream(stream)); + using var reader = GZipReader.OpenReader(new SharpCompressStream(stream)); while (reader.MoveToNextEntry()) // Crash here { Assert.NotEqual(0, reader.Entry.Size); diff --git a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs index 3ada3c524..d23c379a4 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Test.Mocks; using SharpCompress.Writers; using SharpCompress.Writers.GZip; using Xunit; @@ -22,7 +23,13 @@ public async ValueTask GZip_Writer_Generic_Async() FileAccess.Write ) ) - using (var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.GZip)) + using ( + var writer = WriterFactory.OpenAsyncWriter( + new AsyncOnlyStream(stream), + ArchiveType.GZip, + CompressionType.GZip + ) + ) { await writer.WriteAsync("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); } @@ -42,7 +49,7 @@ public async ValueTask GZip_Writer_Async() FileAccess.Write ) ) - using (var writer = new GZipWriter(stream)) + using (var writer = new GZipWriter(new AsyncOnlyStream(stream))) { await writer.WriteAsync("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); } @@ -57,7 +64,11 @@ public void GZip_Writer_Generic_Bad_Compression_Async() => Assert.Throws(() => { using Stream stream = File.OpenWrite(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); - using var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.BZip2); + using var writer = WriterFactory.OpenWriter( + new AsyncOnlyStream(stream), + ArchiveType.GZip, + CompressionType.BZip2 + ); }); [Fact] @@ -70,7 +81,7 @@ public async ValueTask GZip_Writer_Entry_Path_With_Dir_Async() FileAccess.Write ) ) - using (var writer = new GZipWriter(stream)) + using (var writer = new GZipWriter(new AsyncOnlyStream(stream))) { var path = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"); await writer.WriteAsync(path, path); diff --git a/tests/SharpCompress.Test/GZip/GZipWriterTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterTests.cs index f4ca13d38..4d73e6e18 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterTests.cs @@ -21,7 +21,9 @@ public void GZip_Writer_Generic() FileAccess.Write ) ) - using (var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.GZip)) + using ( + var writer = WriterFactory.OpenWriter(stream, ArchiveType.GZip, CompressionType.GZip) + ) { writer.Write("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); } @@ -56,7 +58,11 @@ public void GZip_Writer_Generic_Bad_Compression() => Assert.Throws(() => { using Stream stream = File.OpenWrite(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar.gz")); - using var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.BZip2); + using var writer = WriterFactory.OpenWriter( + stream, + ArchiveType.GZip, + CompressionType.BZip2 + ); }); [Fact] diff --git a/tests/SharpCompress.Test/LazyAsyncReadOnlyCollectionTests.cs b/tests/SharpCompress.Test/LazyAsyncReadOnlyCollectionTests.cs new file mode 100644 index 000000000..aa9c2af4b --- /dev/null +++ b/tests/SharpCompress.Test/LazyAsyncReadOnlyCollectionTests.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace SharpCompress.Test; + +public class LazyAsyncReadOnlyCollectionTests +{ + // Helper class to track how many times items are enumerated from the source + private class TrackingAsyncEnumerable : IAsyncEnumerable + { + private readonly List _items; + public int EnumerationCount { get; private set; } + public int ItemsRequestedCount { get; private set; } + + public TrackingAsyncEnumerable(params T[] items) + { + _items = new List(items); + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + EnumerationCount++; + return new TrackingEnumerator(this, cancellationToken); + } + + private class TrackingEnumerator : IAsyncEnumerator + { + private readonly TrackingAsyncEnumerable _parent; + private readonly CancellationToken _cancellationToken; + private int _index = -1; + + public TrackingEnumerator( + TrackingAsyncEnumerable parent, + CancellationToken cancellationToken + ) + { + _parent = parent; + _cancellationToken = cancellationToken; + } + + public T Current => _parent._items[_index]; + + public async ValueTask MoveNextAsync() + { + _cancellationToken.ThrowIfCancellationRequested(); + await Task.Yield(); // Simulate async behavior + _index++; + if (_index < _parent._items.Count) + { + _parent.ItemsRequestedCount++; + return true; + } + return false; + } + + public ValueTask DisposeAsync() => default; + } + } + + [Fact] + public async Task BasicEnumeration_IteratesThroughAllItems() + { + // Arrange + var source = new TrackingAsyncEnumerable(1, 2, 3, 4, 5); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act + var results = new List(); + await foreach (var item in collection) + { + results.Add(item); + } + + // Assert + Assert.Equal(5, results.Count); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, results); + Assert.Equal(1, source.EnumerationCount); + Assert.Equal(5, source.ItemsRequestedCount); + } + + [Fact] + public async Task MultipleEnumerations_UsesCachedBackingList() + { + // Arrange + var source = new TrackingAsyncEnumerable("a", "b", "c"); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act - First enumeration + var firstResults = new List(); + await foreach (var item in collection) + { + firstResults.Add(item); + } + + var itemsRequestedAfterFirst = source.ItemsRequestedCount; + + // Act - Second enumeration + var secondResults = new List(); + await foreach (var item in collection) + { + secondResults.Add(item); + } + + // Assert + Assert.Equal(firstResults, secondResults); + Assert.Equal(new[] { "a", "b", "c" }, secondResults); + + // Source should only be enumerated once + Assert.Equal(1, source.EnumerationCount); + + // Items should only be requested from source during first enumeration + Assert.Equal(itemsRequestedAfterFirst, source.ItemsRequestedCount); + } + + [Fact] + public async Task EnsureFullyLoaded_LoadsAllItemsIntoBackingList() + { + // Arrange + var source = new TrackingAsyncEnumerable(10, 20, 30, 40); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act + await collection.EnsureFullyLoaded(); + var loaded = collection.GetLoaded().ToList(); + + // Assert + Assert.Equal(4, loaded.Count); + Assert.Equal(new[] { 10, 20, 30, 40 }, loaded); + Assert.Equal(4, source.ItemsRequestedCount); + } + + [Fact] + public async Task GetLoaded_ReturnsOnlyLoadedItemsBeforeFullEnumeration() + { + // Arrange + var source = new TrackingAsyncEnumerable(1, 2, 3, 4, 5); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act - Partially enumerate (only first 2 items) + var enumerator = collection.GetAsyncEnumerator(); + await enumerator.MoveNextAsync(); // Load item 1 + await enumerator.MoveNextAsync(); // Load item 2 + + var loadedItems = collection.GetLoaded().ToList(); + + // Continue enumeration + await enumerator.MoveNextAsync(); // Load item 3 + var loadedItemsAfter = collection.GetLoaded().ToList(); + + await enumerator.DisposeAsync(); + + // Assert + Assert.Equal(2, loadedItems.Count); + Assert.Equal(new[] { 1, 2 }, loadedItems); + + Assert.Equal(3, loadedItemsAfter.Count); + Assert.Equal(new[] { 1, 2, 3 }, loadedItemsAfter); + } + + [Fact] + public async Task CancellationToken_PassedToGetAsyncEnumerator_HonorsToken() + { + // Arrange + var cts = new CancellationTokenSource(); + var source = new TrackingAsyncEnumerable(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act & Assert + var results = new List(); + var exception = await Assert.ThrowsAsync(async () => + { + await foreach (var item in collection.WithCancellation(cts.Token)) + { + results.Add(item); + if (item == 3) + { + cts.Cancel(); + } + } + }); + + Assert.Equal(3, results.Count); + Assert.Equal(new[] { 1, 2, 3 }, results); + } + + [Fact] + public async Task CancellationDuringMoveNextAsync_ThrowsOperationCanceledException() + { + // Arrange + var cts = new CancellationTokenSource(); + var source = CreateDelayedAsyncEnumerable( + new[] { 1, 2, 3, 4, 5 }, + TimeSpan.FromMilliseconds(50) + ); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act & Assert + await Assert.ThrowsAsync(async () => + { + var enumerator = collection.GetAsyncEnumerator(cts.Token); + await enumerator.MoveNextAsync(); + await enumerator.MoveNextAsync(); + + cts.Cancel(); + + await enumerator.MoveNextAsync(); // Should throw + }); + } + + [Fact] + public async Task EmptySourceEnumerable_ReturnsNoItems() + { + // Arrange + var source = new TrackingAsyncEnumerable(); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act + var results = new List(); + await foreach (var item in collection) + { + results.Add(item); + } + + // Assert + Assert.Empty(results); + Assert.Equal(1, source.EnumerationCount); + } + + [Fact] + public async Task SingleItemSourceEnumerable_ReturnsSingleItem() + { + // Arrange + var source = new TrackingAsyncEnumerable("only"); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act + var results = new List(); + await foreach (var item in collection) + { + results.Add(item); + } + + // Assert + Assert.Single(results); + Assert.Equal("only", results[0]); + } + + [Fact] + public async Task PartialEnumeration_ThenGetLoaded_ReturnsOnlyEnumeratedItems() + { + // Arrange + var source = new TrackingAsyncEnumerable(10, 20, 30, 40, 50); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act - Enumerate only first 3 items + await using (var enumerator = collection.GetAsyncEnumerator()) + { + var hasMore = await enumerator.MoveNextAsync(); + Assert.True(hasMore); + hasMore = await enumerator.MoveNextAsync(); + Assert.True(hasMore); + hasMore = await enumerator.MoveNextAsync(); + Assert.True(hasMore); + } + + var loadedItems = collection.GetLoaded().ToList(); + + // Assert + Assert.Equal(3, loadedItems.Count); + Assert.Equal(new[] { 10, 20, 30 }, loadedItems); + Assert.Equal(3, source.ItemsRequestedCount); + } + + [Fact] + public async Task ConcurrentEnumerations_ShareBackingList() + { + // Arrange + var source = new TrackingAsyncEnumerable(1, 2, 3, 4, 5); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act - Fully load the collection first, then enumerate from two threads + await collection.EnsureFullyLoaded(); + + var task1 = Task.Run(async () => + { + var results = new List(); + await foreach (var item in collection) + { + results.Add(item); + await Task.Delay(5); + } + return results; + }); + + var task2 = Task.Run(async () => + { + var results = new List(); + await foreach (var item in collection) + { + results.Add(item); + await Task.Delay(5); + } + return results; + }); + + var results1 = await task1; + var results2 = await task2; + + // Assert - Both enumerations should see all items from the shared backing list + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, results1); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, results2); + Assert.Equal(5, source.ItemsRequestedCount); + } + + [Fact] + public async Task DisposeAsync_OnLazyLoader_CompletesSuccessfully() + { + // Arrange + var source = new TrackingAsyncEnumerable(1, 2, 3); + var collection = new LazyAsyncReadOnlyCollection(source); + + // Act + await using (var enumerator = collection.GetAsyncEnumerator()) + { + await enumerator.MoveNextAsync(); + var firstItem = enumerator.Current; + Assert.Equal(1, firstItem); + + // Dispose is called automatically by await using + } + + // Assert - should be able to enumerate again after disposal + var results = new List(); + await foreach (var item in collection) + { + results.Add(item); + } + + Assert.Equal(new[] { 1, 2, 3 }, results); + } + + // Helper method to create an async enumerable with delays + private static async IAsyncEnumerable CreateDelayedAsyncEnumerable( + IEnumerable items, + TimeSpan delay + ) + { + foreach (var item in items) + { + await Task.Delay(delay); + yield return item; + } + } +} diff --git a/tests/SharpCompress.Test/LazyReadOnlyCollectionTests.cs b/tests/SharpCompress.Test/LazyReadOnlyCollectionTests.cs new file mode 100644 index 000000000..e71dc3eba --- /dev/null +++ b/tests/SharpCompress.Test/LazyReadOnlyCollectionTests.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace SharpCompress.Test; + +public class LazyReadOnlyCollectionTests +{ + // Helper class to track how many times items are enumerated from the source + private class TrackingEnumerable : IEnumerable + { + private readonly List _items; + public int EnumerationCount { get; private set; } + public int ItemsRequestedCount { get; private set; } + + public TrackingEnumerable(params T[] items) + { + _items = new List(items); + } + + public IEnumerator GetEnumerator() + { + EnumerationCount++; + return new TrackingEnumerator(this); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => + GetEnumerator(); + + private class TrackingEnumerator : IEnumerator + { + private readonly TrackingEnumerable _parent; + private int _index = -1; + + public TrackingEnumerator(TrackingEnumerable parent) + { + _parent = parent; + } + + public T Current => _parent._items[_index]; + + object? System.Collections.IEnumerator.Current => Current; + + public bool MoveNext() + { + _index++; + if (_index < _parent._items.Count) + { + _parent.ItemsRequestedCount++; + return true; + } + return false; + } + + public void Reset() => throw new NotSupportedException(); + + public void Dispose() { } + } + } + + [Fact] + public void BasicEnumeration_IteratesThroughAllItems() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3, 4, 5); + var collection = new LazyReadOnlyCollection(source); + + // Act + var results = new List(); + foreach (var item in collection) + { + results.Add(item); + } + + // Assert + Assert.Equal(5, results.Count); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, results); + Assert.Equal(1, source.EnumerationCount); + Assert.Equal(5, source.ItemsRequestedCount); + } + + [Fact] + public void MultipleEnumerations_UsesCachedBackingList() + { + // Arrange + var source = new TrackingEnumerable("a", "b", "c"); + var collection = new LazyReadOnlyCollection(source); + + // Act - First enumeration + var firstResults = new List(); + foreach (var item in collection) + { + firstResults.Add(item); + } + + var itemsRequestedAfterFirst = source.ItemsRequestedCount; + + // Act - Second enumeration + var secondResults = new List(); + foreach (var item in collection) + { + secondResults.Add(item); + } + + // Assert + Assert.Equal(firstResults, secondResults); + Assert.Equal(new[] { "a", "b", "c" }, secondResults); + + // Source should only be enumerated once + Assert.Equal(1, source.EnumerationCount); + + // Items should only be requested from source during first enumeration + Assert.Equal(itemsRequestedAfterFirst, source.ItemsRequestedCount); + } + + [Fact] + public void EnsureFullyLoaded_LoadsAllItemsIntoBackingList() + { + // Arrange + var source = new TrackingEnumerable(10, 20, 30, 40); + var collection = new LazyReadOnlyCollection(source); + + // Act + collection.EnsureFullyLoaded(); + var loaded = collection.GetLoaded().ToList(); + + // Assert + Assert.Equal(4, loaded.Count); + Assert.Equal(new[] { 10, 20, 30, 40 }, loaded); + Assert.Equal(4, source.ItemsRequestedCount); + } + + [Fact] + public void GetLoaded_ReturnsOnlyLoadedItemsBeforeFullEnumeration() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3, 4, 5); + var collection = new LazyReadOnlyCollection(source); + + // Act - Partially enumerate (only first 2 items) + using var enumerator = collection.GetEnumerator(); + enumerator.MoveNext(); // Load item 1 + enumerator.MoveNext(); // Load item 2 + + var loadedItems = collection.GetLoaded().ToList(); + + // Continue enumeration + enumerator.MoveNext(); // Load item 3 + var loadedItemsAfter = collection.GetLoaded().ToList(); + + // Assert + Assert.Equal(2, loadedItems.Count); + Assert.Equal(new[] { 1, 2 }, loadedItems); + + Assert.Equal(3, loadedItemsAfter.Count); + Assert.Equal(new[] { 1, 2, 3 }, loadedItemsAfter); + } + + [Fact] + public void Count_TriggersFullLoad() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3, 4, 5); + var collection = new LazyReadOnlyCollection(source); + + // Act + var count = collection.Count; + + // Assert + Assert.Equal(5, count); + Assert.Equal(5, source.ItemsRequestedCount); + Assert.Equal(5, collection.GetLoaded().Count()); + } + + [Fact] + public void Contains_TriggersFullLoadAndSearches() + { + // Arrange + var source = new TrackingEnumerable("apple", "banana", "cherry"); + var collection = new LazyReadOnlyCollection(source); + + // Act + var containsBanana = collection.Contains("banana"); + var containsOrange = collection.Contains("orange"); + + // Assert + Assert.True(containsBanana); + Assert.False(containsOrange); + Assert.Equal(3, source.ItemsRequestedCount); + Assert.Equal(3, collection.GetLoaded().Count()); + } + + [Fact] + public void CopyTo_TriggersFullLoadAndCopiesArray() + { + // Arrange + var source = new TrackingEnumerable(10, 20, 30); + var collection = new LazyReadOnlyCollection(source); + var array = new int[5]; + + // Act + collection.CopyTo(array, 1); + + // Assert + Assert.Equal(0, array[0]); + Assert.Equal(10, array[1]); + Assert.Equal(20, array[2]); + Assert.Equal(30, array[3]); + Assert.Equal(0, array[4]); + Assert.Equal(3, source.ItemsRequestedCount); + } + + [Fact] + public void EmptySourceEnumerable_ReturnsNoItems() + { + // Arrange + var source = new TrackingEnumerable(); + var collection = new LazyReadOnlyCollection(source); + + // Act + var results = new List(); + foreach (var item in collection) + { + results.Add(item); + } + + // Assert + Assert.Empty(results); + Assert.Equal(0, collection.Count); + Assert.Equal(1, source.EnumerationCount); + } + + [Fact] + public void SingleItemSourceEnumerable_ReturnsSingleItem() + { + // Arrange + var source = new TrackingEnumerable("only"); + var collection = new LazyReadOnlyCollection(source); + + // Act + var results = new List(); + foreach (var item in collection) + { + results.Add(item); + } + + // Assert + Assert.Single(results); + Assert.Equal("only", results[0]); + Assert.Equal(1, collection.Count); + } + + [Fact] + public void PartialEnumeration_ThenGetLoaded_ReturnsOnlyEnumeratedItems() + { + // Arrange + var source = new TrackingEnumerable(10, 20, 30, 40, 50); + var collection = new LazyReadOnlyCollection(source); + + // Act - Enumerate only first 3 items + using (var enumerator = collection.GetEnumerator()) + { + var hasMore = enumerator.MoveNext(); + Assert.True(hasMore); + hasMore = enumerator.MoveNext(); + Assert.True(hasMore); + hasMore = enumerator.MoveNext(); + Assert.True(hasMore); + } + + var loadedItems = collection.GetLoaded().ToList(); + + // Assert + Assert.Equal(3, loadedItems.Count); + Assert.Equal(new[] { 10, 20, 30 }, loadedItems); + Assert.Equal(3, source.ItemsRequestedCount); + } + + [Fact] + public void Reset_ThrowsNotSupportedException() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act & Assert + using var enumerator = collection.GetEnumerator(); + enumerator.MoveNext(); + Assert.Throws(() => enumerator.Reset()); + } + + [Fact] + public void Dispose_OnEnumerator_CompletesSuccessfully() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act + using (var enumerator = collection.GetEnumerator()) + { + enumerator.MoveNext(); + var firstItem = enumerator.Current; + Assert.Equal(1, firstItem); + + // Dispose is called automatically by using + } + + // Assert - should be able to enumerate again after disposal + var results = new List(); + foreach (var item in collection) + { + results.Add(item); + } + + Assert.Equal(new[] { 1, 2, 3 }, results); + } + + [Fact] + public void IsReadOnly_ReturnsTrue() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act & Assert + Assert.True(collection.IsReadOnly); + } + + [Fact] + public void Add_ThrowsNotSupportedException() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act & Assert + Assert.Throws(() => collection.Add(4)); + } + + [Fact] + public void Clear_ThrowsNotSupportedException() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act & Assert + Assert.Throws(() => collection.Clear()); + } + + [Fact] + public void Remove_ThrowsNotSupportedException() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act & Assert + Assert.Throws(() => collection.Remove(1)); + } + + [Fact] + public void NonGenericEnumerator_WorksCorrectly() + { + // Arrange + var source = new TrackingEnumerable(1, 2, 3); + var collection = new LazyReadOnlyCollection(source); + + // Act - Use non-generic IEnumerator + var results = new List(); + var enumerable = (System.Collections.IEnumerable)collection; + foreach (var item in enumerable) + { + results.Add((int)item); + } + + // Assert + Assert.Equal(new[] { 1, 2, 3 }, results); + } +} diff --git a/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs b/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs index d0b363e0c..a7fb32eb9 100644 --- a/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs +++ b/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs @@ -27,35 +27,41 @@ public override long Position public override void Flush() => _stream.Flush(); - public override int Read(byte[] buffer, int offset, int count) - { + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("Synchronous Read is not supported"); - } public override Task ReadAsync( byte[] buffer, int offset, int count, CancellationToken cancellationToken - ) - { - return _stream.ReadAsync(buffer, offset, count, cancellationToken); - } + ) => _stream.ReadAsync(buffer, offset, count, cancellationToken); -#if !NETFRAMEWORK && !NETSTANDARD2_0 +#if NET8_0_OR_GREATER public override ValueTask ReadAsync( Memory buffer, CancellationToken cancellationToken = default - ) - { - return _stream.ReadAsync(buffer, cancellationToken); - } + ) => _stream.ReadAsync(buffer, cancellationToken); #endif public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); public override void SetLength(long value) => _stream.SetLength(value); + public override Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) => throw new NotSupportedException("Synchronous Read is not supported"); + +#if NET8_0_OR_GREATER + public override ValueTask WriteAsync( + ReadOnlyMemory buffer, + CancellationToken cancellationToken = default + ) => _stream.WriteAsync(buffer, cancellationToken); +#endif + public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count); diff --git a/tests/SharpCompress.Test/ProgressReportTests.cs b/tests/SharpCompress.Test/ProgressReportTests.cs index 2f0dd8021..805cbcf8f 100644 --- a/tests/SharpCompress.Test/ProgressReportTests.cs +++ b/tests/SharpCompress.Test/ProgressReportTests.cs @@ -110,7 +110,7 @@ public void Zip_Read_ReportsProgress() archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; - using (var reader = ReaderFactory.Open(archiveStream, readerOptions)) + using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { @@ -148,7 +148,7 @@ public void ZipArchive_Entry_WriteTo_ReportsProgress() // Now open as archive and extract entry with progress as parameter archiveStream.Position = 0; - using var archive = ZipArchive.Open(archiveStream); + using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -184,7 +184,7 @@ public async ValueTask ZipArchive_Entry_WriteToAsync_ReportsProgress() // Now open as archive and extract entry async with progress as parameter archiveStream.Position = 0; - using var archive = ZipArchive.Open(archiveStream); + using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -237,7 +237,7 @@ public void ReaderOptions_WithoutProgress_DoesNotThrow() var readerOptions = new ReaderOptions(); Assert.Null(readerOptions.Progress); - using (var reader = ReaderFactory.Open(archiveStream, readerOptions)) + using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { @@ -267,7 +267,7 @@ public void ZipArchive_WithoutProgress_DoesNotThrow() // Open archive and extract without progress archiveStream.Position = 0; - using var archive = ZipArchive.Open(archiveStream); + using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -326,7 +326,7 @@ public void Tar_Read_ReportsProgress() archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; - using (var reader = ReaderFactory.Open(archiveStream, readerOptions)) + using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { @@ -367,7 +367,7 @@ public void TarArchive_Entry_WriteTo_ReportsProgress() // Now open as archive and extract entry with progress as parameter archiveStream.Position = 0; - using var archive = SharpCompress.Archives.Tar.TarArchive.Open(archiveStream); + using var archive = SharpCompress.Archives.Tar.TarArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -406,7 +406,7 @@ public async ValueTask TarArchive_Entry_WriteToAsync_ReportsProgress() // Now open as archive and extract entry async with progress as parameter archiveStream.Position = 0; - using var archive = SharpCompress.Archives.Tar.TarArchive.Open(archiveStream); + using var archive = SharpCompress.Archives.Tar.TarArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -447,7 +447,7 @@ public void Zip_Read_MultipleEntries_ReportsProgress() archiveStream.Position = 0; var readerOptions = new ReaderOptions { Progress = progress }; - using (var reader = ReaderFactory.Open(archiveStream, readerOptions)) + using (var reader = ReaderFactory.OpenReader(archiveStream, readerOptions)) { while (reader.MoveToNextEntry()) { @@ -496,7 +496,7 @@ public void ZipArchive_MultipleEntries_WriteTo_ReportsProgress() // Now open as archive and extract entries with progress as parameter archiveStream.Position = 0; - using var archive = ZipArchive.Open(archiveStream); + using var archive = ZipArchive.OpenArchive(archiveStream); foreach (var entry in archive.Entries) { if (!entry.IsDirectory) @@ -541,7 +541,7 @@ public async ValueTask Zip_ReadAsync_ReportsProgress() var readerOptions = new ReaderOptions { Progress = progress }; await using ( - var reader = await ReaderFactory.OpenAsync( + var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(archiveStream), readerOptions ) diff --git a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs index acfe4c27a..949fb0cc9 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs @@ -68,7 +68,7 @@ private async ValueTask ReadRarPasswordAsync(string testArchive, string? passwor { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive))) using ( - var archive = RarArchive.Open( + var archive = RarArchive.OpenArchive( stream, new ReaderOptions { Password = password, LeaveStreamOpen = true } ) @@ -98,7 +98,7 @@ await ArchiveFileReadPasswordAsync("Rar.EncryptedParts.part01.rar", "test") protected async Task ArchiveFileReadPasswordAsync(string archiveName, string password) { using ( - var archive = RarArchive.Open( + var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, archiveName), new ReaderOptions { Password = password, LeaveStreamOpen = true } ) @@ -137,7 +137,7 @@ public async ValueTask Rar_test_invalid_exttime_ArchiveStreamRead_Async() => private async ValueTask DoRar_test_invalid_exttime_ArchiveStreamReadAsync(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync( @@ -151,7 +151,9 @@ await entry.WriteToDirectoryAsync( public async ValueTask Rar_Jpg_ArchiveStreamRead_Async() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg")); - using (var archive = RarArchive.Open(stream, new ReaderOptions { LookForHeader = true })) + using ( + var archive = RarArchive.OpenArchive(stream, new ReaderOptions { LookForHeader = true }) + ) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -176,7 +178,7 @@ private async ValueTask DoRar_IsSolidArchiveCheckAsync(string filename) { using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -196,7 +198,7 @@ public async ValueTask Rar_IsSolidEntryStreamCheck_Async() => private async ValueTask DoRar_IsSolidEntryStreamCheckAsync(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); Assert.True(archive.IsSolid); IArchiveEntry[] entries = archive.Entries.Where(a => !a.IsDirectory).ToArray(); Assert.NotInRange(entries.Length, 0, 1); @@ -264,7 +266,7 @@ await DoRar_Multi_ArchiveStreamReadAsync( private async ValueTask DoRar_Multi_ArchiveStreamReadAsync(string[] archives, bool isSolid) { - using var archive = RarArchive.Open( + using var archive = RarArchive.OpenArchive( archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).Select(File.OpenRead) ); Assert.Equal(archive.IsSolid, isSolid); @@ -316,7 +318,7 @@ public async ValueTask Rar5_ArchiveFileRead_HasDirectories_Async() => private Task DoRar_ArchiveFileRead_HasDirectoriesAsync(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); Assert.False(archive.IsSolid); Assert.Contains(true, archive.Entries.Select(entry => entry.IsDirectory)); return Task.CompletedTask; @@ -326,7 +328,7 @@ private Task DoRar_ArchiveFileRead_HasDirectoriesAsync(string filename) public async ValueTask Rar_Jpg_ArchiveFileRead_Async() { using ( - var archive = RarArchive.Open( + var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), new ReaderOptions { LookForHeader = true } ) @@ -386,7 +388,7 @@ public void Rar15_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar15.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(1, archive.MinVersion); Assert.Equal(1, archive.MaxVersion); } @@ -396,7 +398,7 @@ public void Rar2_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar2.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(2, archive.MinVersion); Assert.Equal(2, archive.MaxVersion); } @@ -406,7 +408,7 @@ public void Rar4_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar4.multi.part01.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(3, archive.MinVersion); Assert.Equal(4, archive.MaxVersion); } @@ -416,7 +418,7 @@ public void Rar5_ArchiveVersionTest_Async() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(5, archive.MinVersion); Assert.Equal(6, archive.MaxVersion); } @@ -573,7 +575,7 @@ public void Rar5_IsFirstVolume_True_Async() => private void DoRar_IsFirstVolume_True(string firstFilename) { - using var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, firstFilename)); + using var archive = RarArchive.OpenArchive(Path.Combine(TEST_ARCHIVES_PATH, firstFilename)); Assert.True(archive.IsMultipartVolume()); Assert.True(archive.IsFirstVolume()); } @@ -588,7 +590,9 @@ public void Rar5_IsFirstVolume_False_Async() => private void DoRar_IsFirstVolume_False(string notFirstFilename) { - using var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename)); + using var archive = RarArchive.OpenArchive( + Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename) + ); Assert.True(archive.IsMultipartVolume()); Assert.False(archive.IsFirstVolume()); } @@ -631,7 +635,7 @@ private async ValueTask ArchiveStreamReadAsync(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using var stream = File.OpenRead(testArchive); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync( @@ -649,7 +653,7 @@ CompressionType compression { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using var stream = File.OpenRead(testArchive); - await using var archive = await ArchiveFactory.OpenAsync(stream); + await using var archive = await ArchiveFactory.OpenAsyncArchive(stream); Assert.True(await archive.IsSolidAsync()); await using (var reader = await archive.ExtractAllEntriesAsync()) { @@ -680,7 +684,7 @@ await entry.WriteToDirectoryAsync( private async ValueTask ArchiveFileReadAsync(string testArchive) { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); - using var archive = ArchiveFactory.Open(testArchive); + using var archive = ArchiveFactory.OpenArchive(testArchive); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync( @@ -697,7 +701,10 @@ params string[] testArchives ) { var paths = testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)); - using var archive = ArchiveFactory.Open(paths.Select(a => new FileInfo(a)), readerOptions); + using var archive = ArchiveFactory.OpenArchive( + paths.Select(a => new FileInfo(a)), + readerOptions + ); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync( @@ -714,7 +721,10 @@ params string[] testArchives ) { var paths = testArchives.Select(x => Path.Combine(TEST_ARCHIVES_PATH, x)); - using var archive = ArchiveFactory.Open(paths.Select(f => new FileInfo(f)), readerOptions); + using var archive = ArchiveFactory.OpenArchive( + paths.Select(f => new FileInfo(f)), + readerOptions + ); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { await entry.WriteToDirectoryAsync( diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index ba42f649c..c87f27be4 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -68,7 +68,7 @@ private void ReadRarPassword(string testArchive, string? password) { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive))) using ( - var archive = RarArchive.Open( + var archive = RarArchive.OpenArchive( stream, new ReaderOptions { Password = password, LeaveStreamOpen = true } ) @@ -98,7 +98,7 @@ public void Rar_Multi_Archive_Encrypted() => protected void ArchiveFileReadPassword(string archiveName, string password) { using ( - var archive = RarArchive.Open( + var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, archiveName), new ReaderOptions { Password = password, LeaveStreamOpen = true } ) @@ -134,7 +134,7 @@ public void Rar_test_invalid_exttime_ArchiveStreamRead() => private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { entry.WriteToDirectory( @@ -148,7 +148,9 @@ private void DoRar_test_invalid_exttime_ArchiveStreamRead(string filename) public void Rar_Jpg_ArchiveStreamRead() { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg")); - using (var archive = RarArchive.Open(stream, new ReaderOptions { LookForHeader = true })) + using ( + var archive = RarArchive.OpenArchive(stream, new ReaderOptions { LookForHeader = true }) + ) { foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -171,7 +173,7 @@ private void DoRar_IsSolidArchiveCheck(string filename) { using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) { - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); Assert.False(archive.IsSolid); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -191,7 +193,7 @@ private void DoRar_IsSolidArchiveCheck(string filename) private void DoRar_IsSolidEntryStreamCheck(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); Assert.True(archive.IsSolid); IArchiveEntry[] entries = archive.Entries.Where(a => !a.IsDirectory).ToArray(); Assert.NotInRange(entries.Length, 0, 1); @@ -258,7 +260,7 @@ public void Rar5_Multi_ArchiveStreamRead() => private void DoRar_Multi_ArchiveStreamRead(string[] archives, bool isSolid) { - using var archive = RarArchive.Open( + using var archive = RarArchive.OpenArchive( archives.Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)).Select(File.OpenRead) ); Assert.Equal(archive.IsSolid, isSolid); @@ -308,7 +310,7 @@ public void Rar5_ArchiveFileRead_HasDirectories() => private void DoRar_ArchiveFileRead_HasDirectories(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); Assert.False(archive.IsSolid); Assert.Contains(true, archive.Entries.Select(entry => entry.IsDirectory)); } @@ -317,7 +319,7 @@ private void DoRar_ArchiveFileRead_HasDirectories(string filename) public void Rar_Jpg_ArchiveFileRead() { using ( - var archive = RarArchive.Open( + var archive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"), new ReaderOptions { LookForHeader = true } ) @@ -374,7 +376,7 @@ public void Rar15_ArchiveVersionTest() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar15.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(1, archive.MinVersion); Assert.Equal(1, archive.MaxVersion); } @@ -384,7 +386,7 @@ public void Rar2_ArchiveVersionTest() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar2.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(2, archive.MinVersion); Assert.Equal(2, archive.MaxVersion); } @@ -394,7 +396,7 @@ public void Rar4_ArchiveVersionTest() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar4.multi.part01.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(3, archive.MinVersion); Assert.Equal(4, archive.MaxVersion); } @@ -404,7 +406,7 @@ public void Rar5_ArchiveVersionTest() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.rar"); - using var archive = RarArchive.Open(testArchive); + using var archive = RarArchive.OpenArchive(testArchive); Assert.Equal(5, archive.MinVersion); Assert.Equal(6, archive.MaxVersion); } @@ -585,7 +587,7 @@ public void Rar4_Multi_ArchiveOpenEntryVolumeIndexTest() => private void DoRar_IsFirstVolume_True(string firstFilename) { - using var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, firstFilename)); + using var archive = RarArchive.OpenArchive(Path.Combine(TEST_ARCHIVES_PATH, firstFilename)); Assert.True(archive.IsMultipartVolume()); Assert.True(archive.IsFirstVolume()); } @@ -598,7 +600,9 @@ private void DoRar_IsFirstVolume_True(string firstFilename) private void DoRar_IsFirstVolume_False(string notFirstFilename) { - using var archive = RarArchive.Open(Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename)); + using var archive = RarArchive.OpenArchive( + Path.Combine(TEST_ARCHIVES_PATH, notFirstFilename) + ); Assert.True(archive.IsMultipartVolume()); Assert.False(archive.IsFirstVolume()); } @@ -639,7 +643,7 @@ public void Rar5_Encrypted_Iterate_Archive() => [Fact] public void Rar_TestEncryptedDetection() { - using var passwordProtectedFilesArchive = RarArchive.Open( + using var passwordProtectedFilesArchive = RarArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Rar.encrypted_filesOnly.rar") ); Assert.True(passwordProtectedFilesArchive.IsEncrypted); @@ -662,7 +666,7 @@ public void Rar_StreamValidation_OnlyThrowsOnPrematureEnd() foreach (var testFile in testFiles) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile)); - using var archive = RarArchive.Open(stream); + using var archive = RarArchive.OpenArchive(stream); // Extract all entries and read them completely foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) @@ -704,7 +708,7 @@ public void Rar_StreamValidation_ThrowsOnTruncatedStream() // when we try to read beyond the truncated data var exception = Assert.Throws(() => { - using var archive = RarArchive.Open(truncatedStream); + using var archive = RarArchive.OpenArchive(truncatedStream); foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) { using var entryStream = entry.OpenEntryStream(); diff --git a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs index 6fd783146..ce168746d 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs @@ -39,13 +39,14 @@ await DoRar_Multi_Reader_Async([ private async ValueTask DoRar_Multi_Reader_Async(string[] archives) { using ( - var reader = RarReader.Open( + IReader baseReader = RarReader.OpenReader( archives .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)) ) ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { await reader.WriteEntryToDirectoryAsync( @@ -71,7 +72,7 @@ await Assert.ThrowsAsync(async () => "Rar.EncryptedParts.part06.rar", ]; using ( - var reader = RarReader.Open( + IReader baseReader = RarReader.OpenReader( archives .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)), @@ -79,6 +80,7 @@ await Assert.ThrowsAsync(async () => ) ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { await reader.WriteEntryToDirectoryAsync( @@ -125,8 +127,9 @@ private async ValueTask DoRar_Multi_Reader_Delete_Files_Async(string[] archives) .Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)) .Select(File.OpenRead) .ToList(); - using (var reader = RarReader.Open(streams)) + using (IReader baseReader = RarReader.OpenReader(streams)) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { await reader.WriteEntryToDirectoryAsync( @@ -205,7 +208,7 @@ await ReadAsync( private async ValueTask DoRar_Entry_Stream_Async(string filename) { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - await using (var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream))) + await using (var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream))) { while (await reader.MoveToNextEntryAsync()) { @@ -250,7 +253,7 @@ public async ValueTask Rar_Reader_Audio_program_Async() var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar")) ) await using ( - var reader = await ReaderFactory.OpenAsync( + var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), new ReaderOptions { LookForHeader = true } ) @@ -275,8 +278,14 @@ await reader.WriteEntryToDirectoryAsync( public async ValueTask Rar_Jpg_Reader_Async() { using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) - using (var reader = RarReader.Open(stream, new ReaderOptions { LookForHeader = true })) + using ( + IReader baseReader = RarReader.OpenReader( + stream, + new ReaderOptions { LookForHeader = true } + ) + ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { Assert.Equal(CompressionType.Rar, reader.Entry.CompressionType); @@ -316,7 +325,7 @@ public async ValueTask Rar5_Solid_Skip_Reader_Async() => private async ValueTask DoRar_Solid_Skip_Reader_Async(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - await using var reader = await ReaderFactory.OpenAsync( + await using var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), new ReaderOptions { LookForHeader = true } ); @@ -342,7 +351,7 @@ await reader.WriteEntryToDirectoryAsync( private async ValueTask DoRar_Reader_Skip_Async(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - await using var reader = await ReaderFactory.OpenAsync( + await using var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), new ReaderOptions { LookForHeader = true } ); @@ -367,7 +376,7 @@ private async ValueTask ReadAsync( { testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive); using Stream stream = File.OpenRead(testArchive); - await using var reader = await ReaderFactory.OpenAsync( + await using var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), readerOptions ?? new ReaderOptions() ); diff --git a/tests/SharpCompress.Test/Rar/RarReaderTests.cs b/tests/SharpCompress.Test/Rar/RarReaderTests.cs index b3c98f2e0..fd0be5933 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderTests.cs @@ -37,7 +37,7 @@ public void Rar5_Multi_Reader() => private void DoRar_Multi_Reader(string[] archives) { using ( - var reader = RarReader.Open( + var reader = RarReader.OpenReader( archives .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)) @@ -70,7 +70,7 @@ private void DoRar_Multi_Reader_Encrypted(string[] archives) => Assert.Throws(() => { using ( - var reader = RarReader.Open( + var reader = RarReader.OpenReader( archives .Select(s => Path.Combine(TEST_ARCHIVES_PATH, s)) .Select(p => File.OpenRead(p)), @@ -124,7 +124,7 @@ private void DoRar_Multi_Reader_Delete_Files(string[] archives) .Select(s => Path.Combine(SCRATCH2_FILES_PATH, s)) .Select(File.OpenRead) .ToList(); - using (var reader = RarReader.Open(streams)) + using (var reader = RarReader.OpenReader(streams)) { while (reader.MoveToNextEntry()) { @@ -193,7 +193,7 @@ private void ReadRar(string testArchive, string password) => private void DoRar_Entry_Stream(string filename) { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename))) - using (var reader = ReaderFactory.Open(stream)) + using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -226,7 +226,12 @@ public void Rar_Reader_Audio_program() using ( var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.Audio_program.rar")) ) - using (var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true })) + using ( + var reader = ReaderFactory.OpenReader( + stream, + new ReaderOptions { LookForHeader = true } + ) + ) { while (reader.MoveToNextEntry()) { @@ -247,7 +252,9 @@ public void Rar_Reader_Audio_program() public void Rar_Jpg_Reader() { using (var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Rar.jpeg.jpg"))) - using (var reader = RarReader.Open(stream, new ReaderOptions { LookForHeader = true })) + using ( + var reader = RarReader.OpenReader(stream, new ReaderOptions { LookForHeader = true }) + ) { while (reader.MoveToNextEntry()) { @@ -282,7 +289,10 @@ public void Rar_Jpg_Reader() private void DoRar_Solid_Skip_Reader(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true }); + using var reader = ReaderFactory.OpenReader( + stream, + new ReaderOptions { LookForHeader = true } + ); while (reader.MoveToNextEntry()) { if (reader.Entry.Key.NotNull().Contains("jpg")) @@ -305,7 +315,10 @@ private void DoRar_Solid_Skip_Reader(string filename) private void DoRar_Reader_Skip(string filename) { using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); - using var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true }); + using var reader = ReaderFactory.OpenReader( + stream, + new ReaderOptions { LookForHeader = true } + ); while (reader.MoveToNextEntry()) { if (reader.Entry.Key.NotNull().Contains("jpg")) @@ -325,7 +338,10 @@ public void Rar_SkipEncryptedFilesWithoutPassword() using var stream = File.OpenRead( Path.Combine(TEST_ARCHIVES_PATH, "Rar.encrypted_filesOnly.rar") ); - using var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true }); + using var reader = ReaderFactory.OpenReader( + stream, + new ReaderOptions { LookForHeader = true } + ); while (reader.MoveToNextEntry()) { // @@ -397,7 +413,7 @@ public void Rar_Iterate_Multipart() Path.Combine("exe", "test.exe"), } ); - using var reader = RarReader.Open([ + using var reader = RarReader.OpenReader([ Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part01.rar"), Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part02.rar"), Path.Combine(TEST_ARCHIVES_PATH, "Rar.multi.part03.rar"), diff --git a/tests/SharpCompress.Test/ReaderTests.cs b/tests/SharpCompress.Test/ReaderTests.cs index 67c4371c0..489d87f77 100644 --- a/tests/SharpCompress.Test/ReaderTests.cs +++ b/tests/SharpCompress.Test/ReaderTests.cs @@ -70,7 +70,7 @@ private void ReadImplCore(string testArchive, ReaderOptions options, Action !entry.IsDirectory)) { @@ -41,7 +41,7 @@ public async ValueTask SevenZipArchive_LZMA2_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.LZMA2.7z"); using var stream = File.OpenRead(testArchive); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -66,7 +66,7 @@ public async ValueTask SevenZipArchive_Solid_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); using var stream = File.OpenRead(testArchive); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -91,7 +91,7 @@ public async ValueTask SevenZipArchive_BZip2_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.BZip2.7z"); using var stream = File.OpenRead(testArchive); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { @@ -116,7 +116,7 @@ public async ValueTask SevenZipArchive_PPMd_AsyncStreamExtraction() { var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.PPMd.7z"); using var stream = File.OpenRead(testArchive); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs index af21ff94e..6cbdc87e9 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveTests.cs @@ -132,7 +132,7 @@ public void SevenZipArchive_BZip2_Split_FirstFileRead() => public void SevenZipArchive_Copy_CompressionType() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "7Zip.Copy.7z"))) - using (var archive = SevenZipArchive.Open(stream)) + using (var archive = SevenZipArchive.OpenArchive(stream)) { foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) { @@ -205,7 +205,7 @@ public void SevenZipArchive_Delta_Distance() => public void SevenZipArchive_Tar_PathRead() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "7Zip.Tar.tar.7z"))) - using (var archive = SevenZipArchive.Open(stream)) + using (var archive = SevenZipArchive.OpenArchive(stream)) { var entry = archive.Entries.First(); entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key.NotNull())); @@ -227,7 +227,7 @@ public void SevenZipArchive_Tar_PathRead() [Fact] public void SevenZipArchive_TestEncryptedDetection() { - using var passwordProtectedFilesArchive = SevenZipArchive.Open( + using var passwordProtectedFilesArchive = SevenZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "7Zip.encryptedFiles.7z") ); Assert.True(passwordProtectedFilesArchive.IsEncrypted); @@ -236,17 +236,17 @@ public void SevenZipArchive_TestEncryptedDetection() [Fact] public void SevenZipArchive_TestSolidDetection() { - using var oneBlockSolidArchive = SevenZipArchive.Open( + using var oneBlockSolidArchive = SevenZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.1block.7z") ); Assert.True(oneBlockSolidArchive.IsSolid); - using var solidArchive = SevenZipArchive.Open( + using var solidArchive = SevenZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z") ); Assert.True(solidArchive.IsSolid); - using var nonSolidArchive = SevenZipArchive.Open( + using var nonSolidArchive = SevenZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "7Zip.nonsolid.7z") ); Assert.False(nonSolidArchive.IsSolid); diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index 9fae1c582..0e6ef3e63 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -8,6 +8,7 @@ using SharpCompress.Common; using SharpCompress.Readers; using SharpCompress.Readers.Tar; +using SharpCompress.Test.Mocks; using SharpCompress.Writers; using SharpCompress.Writers.Tar; using Xunit; @@ -32,7 +33,13 @@ public async ValueTask Tar_FileName_Exactly_100_Characters_Async() // 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.Open(stream, ArchiveType.Tar, CompressionType.None)) + using ( + var writer = WriterFactory.OpenAsyncWriter( + stream, + ArchiveType.Tar, + CompressionType.None + ) + ) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -45,12 +52,19 @@ public async ValueTask Tar_FileName_Exactly_100_Characters_Async() // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.Open(unmodified)) + await using ( + var archive2 = TarArchive.OpenAsyncArchive( + new AsyncOnlyStream(File.OpenRead(unmodified)) + ) + ) { - Assert.Equal(1, archive2.Entries.Count); - Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key)); + Assert.Equal(1, await archive2.EntriesAsync.CountAsync()); + Assert.Contains( + filename, + await archive2.EntriesAsync.Select(entry => entry.Key).ToListAsync() + ); - foreach (var entry in archive2.Entries) + await foreach (var entry in archive2.EntriesAsync) { Assert.Equal( "dummy filecontent", @@ -76,7 +90,13 @@ public async ValueTask Tar_VeryLongFilepathReadback_Async() // 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.Open(stream, ArchiveType.Tar, CompressionType.None)) + using ( + var writer = WriterFactory.OpenAsyncWriter( + stream, + ArchiveType.Tar, + CompressionType.None + ) + ) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -89,12 +109,19 @@ public async ValueTask Tar_VeryLongFilepathReadback_Async() // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.Open(unmodified)) + await using ( + var archive2 = TarArchive.OpenAsyncArchive( + new AsyncOnlyStream(File.OpenRead(unmodified)) + ) + ) { - Assert.Equal(1, archive2.Entries.Count); - Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key)); + Assert.Equal(1, await archive2.EntriesAsync.CountAsync()); + Assert.Contains( + longFilename, + await archive2.EntriesAsync.Select(entry => entry.Key).ToListAsync() + ); - foreach (var entry in archive2.Entries) + await foreach (var entry in archive2.EntriesAsync) { Assert.Equal( "dummy filecontent", @@ -110,7 +137,7 @@ public async ValueTask Tar_Create_New_Async() var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - using (var archive = TarArchive.Create()) + await using (var archive = TarArchive.CreateAsyncArchive()) { archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); var twopt = new TarWriterOptions(CompressionType.None, true); @@ -128,7 +155,7 @@ public async ValueTask Tar_Random_Write_Add_Async() var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - using (var archive = TarArchive.Open(unmodified)) + await using (var archive = TarArchive.OpenAsyncArchive(unmodified)) { archive.AddEntry("jpg\\test.jpg", jpg); await archive.SaveToAsync(scratchPath, new WriterOptions(CompressionType.None)); @@ -143,9 +170,9 @@ public async ValueTask Tar_Random_Write_Remove_Async() var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - using (var archive = TarArchive.Open(unmodified)) + await using (var archive = TarArchive.OpenAsyncArchive(unmodified)) { - var entry = archive.Entries.Single(x => + var entry = await archive.EntriesAsync.SingleAsync(x => x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ); archive.RemoveEntry(entry); @@ -172,9 +199,9 @@ public async ValueTask Tar_Japanese_Name_Async(int length) using (var inputMemory = new MemoryStream(mstm.ToArray())) { var tropt = new ReaderOptions { ArchiveEncoding = enc }; - using (var tr = TarReader.Open(inputMemory, tropt)) + await using (var tr = ReaderFactory.OpenAsyncReader(inputMemory, tropt)) { - while (tr.MoveToNextEntry()) + while (await tr.MoveToNextEntryAsync()) { Assert.Equal(fname, tr.Entry.Key); } @@ -205,9 +232,11 @@ public async ValueTask Tar_Read_One_At_A_Time_Async() var numberOfEntries = 0; - using (var archiveFactory = TarArchive.Open(memoryStream)) + await using ( + var archiveFactory = TarArchive.OpenAsyncArchive(new AsyncOnlyStream(memoryStream)) + ) { - foreach (var entry in archiveFactory.Entries) + await foreach (var entry in archiveFactory.EntriesAsync) { ++numberOfEntries; diff --git a/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs index 3046246d4..f5d3ad73b 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs @@ -12,7 +12,7 @@ public class TarArchiveDirectoryTests : TestBase [Fact] public void TarArchive_AddDirectoryEntry_CreatesDirectoryEntry() { - using var archive = TarArchive.Create(); + using var archive = TarArchive.CreateArchive(); archive.AddDirectoryEntry("test-dir", DateTime.Now); @@ -25,7 +25,7 @@ public void TarArchive_AddDirectoryEntry_CreatesDirectoryEntry() [Fact] public void TarArchive_AddDirectoryEntry_MultipleDirectories() { - using var archive = TarArchive.Create(); + using var archive = TarArchive.CreateArchive(); archive.AddDirectoryEntry("dir1", DateTime.Now); archive.AddDirectoryEntry("dir2", DateTime.Now); @@ -39,7 +39,7 @@ public void TarArchive_AddDirectoryEntry_MultipleDirectories() [Fact] public void TarArchive_AddDirectoryEntry_MixedWithFiles() { - using var archive = TarArchive.Create(); + using var archive = TarArchive.CreateArchive(); archive.AddDirectoryEntry("dir1", DateTime.Now); @@ -62,7 +62,7 @@ public void TarArchive_AddDirectoryEntry_SaveAndReload() { var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "tar-directory-test.tar"); - using (var archive = TarArchive.Create()) + using (var archive = TarArchive.CreateArchive()) { archive.AddDirectoryEntry("dir1", DateTime.Now); archive.AddDirectoryEntry("dir2", DateTime.Now); @@ -84,7 +84,7 @@ public void TarArchive_AddDirectoryEntry_SaveAndReload() } } - using (var archive = TarArchive.Open(scratchPath)) + using (var archive = TarArchive.OpenArchive(scratchPath)) { var entries = archive.Entries.OrderBy(e => e.Key).ToList(); Assert.Equal(3, entries.Count); @@ -103,7 +103,7 @@ public void TarArchive_AddDirectoryEntry_SaveAndReload() [Fact] public void TarArchive_AddDirectoryEntry_DuplicateKey_ThrowsException() { - using var archive = TarArchive.Create(); + using var archive = TarArchive.CreateArchive(); archive.AddDirectoryEntry("test-dir", DateTime.Now); diff --git a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs index efbde4723..af33dff7a 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveTests.cs @@ -34,7 +34,7 @@ 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.Open(stream, ArchiveType.Tar, CompressionType.None)) + using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, CompressionType.None)) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -47,9 +47,9 @@ public void Tar_FileName_Exactly_100_Characters() // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.Open(unmodified)) + using (var archive2 = TarArchive.OpenArchive(unmodified)) { - Assert.Equal(1, archive2.Entries.Count); + Assert.Equal(1, archive2.Entries.Count()); Assert.Contains(filename, archive2.Entries.Select(entry => entry.Key)); foreach (var entry in archive2.Entries) @@ -66,8 +66,8 @@ public void Tar_FileName_Exactly_100_Characters() public void Tar_NonUstarArchiveWithLongNameDoesNotSkipEntriesAfterTheLongOne() { var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "very long filename.tar"); - using var archive = TarArchive.Open(unmodified); - Assert.Equal(5, archive.Entries.Count); + using var archive = TarArchive.OpenArchive(unmodified); + Assert.Equal(5, archive.Entries.Count()); Assert.Contains("very long filename/", archive.Entries.Select(entry => entry.Key)); Assert.Contains( "very long filename/very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename very long filename.jpg", @@ -94,7 +94,7 @@ 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.Open(stream, ArchiveType.Tar, CompressionType.None)) + using (var writer = WriterFactory.OpenWriter(stream, ArchiveType.Tar, CompressionType.None)) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -107,9 +107,9 @@ public void Tar_VeryLongFilepathReadback() // Step 2: check if the written tar file can be read correctly var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); - using (var archive2 = TarArchive.Open(unmodified)) + using (var archive2 = TarArchive.OpenArchive(unmodified)) { - Assert.Equal(1, archive2.Entries.Count); + Assert.Equal(1, archive2.Entries.Count()); Assert.Contains(longFilename, archive2.Entries.Select(entry => entry.Key)); foreach (var entry in archive2.Entries) @@ -126,8 +126,8 @@ public void Tar_VeryLongFilepathReadback() public void Tar_UstarArchivePathReadLongName() { var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "ustar with long names.tar"); - using var archive = TarArchive.Open(unmodified); - Assert.Equal(6, archive.Entries.Count); + using var archive = TarArchive.OpenArchive(unmodified); + Assert.Equal(6, archive.Entries.Count()); Assert.Contains("Directory/", archive.Entries.Select(entry => entry.Key)); Assert.Contains( "Directory/Some file with veeeeeeeeeery loooooooooong name", @@ -159,7 +159,7 @@ public void Tar_Create_New() // var aropt = new Ar - using (var archive = TarArchive.Create()) + using (var archive = TarArchive.CreateArchive()) { archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); var twopt = new TarWriterOptions(CompressionType.None, true); @@ -177,7 +177,7 @@ public void Tar_Random_Write_Add() var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - using (var archive = TarArchive.Open(unmodified)) + using (var archive = TarArchive.OpenArchive(unmodified)) { archive.AddEntry("jpg\\test.jpg", jpg); archive.SaveTo(scratchPath, CompressionType.None); @@ -192,7 +192,7 @@ public void Tar_Random_Write_Remove() var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar"); - using (var archive = TarArchive.Open(unmodified)) + using (var archive = TarArchive.OpenArchive(unmodified)) { var entry = archive.Entries.Single(x => x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) @@ -208,7 +208,7 @@ public void Tar_Containing_Rar_Archive() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); Assert.True(archive.Type == ArchiveType.Tar); } @@ -217,7 +217,7 @@ public void Tar_Empty_Archive() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.Empty.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var archive = ArchiveFactory.Open(stream); + using var archive = ArchiveFactory.OpenArchive(stream); Assert.True(archive.Type == ArchiveType.Tar); } @@ -239,7 +239,7 @@ public void Tar_Japanese_Name(int length) using (var inputMemory = new MemoryStream(mstm.ToArray())) { var tropt = new ReaderOptions { ArchiveEncoding = enc }; - using (var tr = TarReader.Open(inputMemory, tropt)) + using (var tr = TarReader.OpenReader(inputMemory, tropt)) { while (tr.MoveToNextEntry()) { @@ -272,7 +272,7 @@ public void Tar_Read_One_At_A_Time() var numberOfEntries = 0; - using (var archiveFactory = TarArchive.Open(memoryStream)) + using (var archiveFactory = TarArchive.OpenArchive(memoryStream)) { foreach (var entry in archiveFactory.Entries) { diff --git a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs index 4be595845..7ce0d9b25 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs @@ -23,7 +23,7 @@ public async ValueTask Tar_Skip_Async() using Stream stream = new ForwardOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")) ); - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var x = 0; while (await reader.MoveToNextEntryAsync()) { @@ -73,7 +73,7 @@ public async ValueTask Tar_GZip_OldGnu_Reader_Async() => public async ValueTask Tar_BZip2_Entry_Stream_Async() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) - using (var reader = TarReader.Open(stream)) + await using (var reader = ReaderFactory.OpenAsyncReader(stream)) { while (await reader.MoveToNextEntryAsync()) { @@ -110,7 +110,7 @@ public void Tar_LongNamesWithLongNameExtension_Async() Path.Combine(TEST_ARCHIVES_PATH, "Tar.LongPathsWithLongNameExtension.tar") ) ) - using (var reader = TarReader.Open(stream)) + using (var reader = TarReader.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -137,7 +137,7 @@ public void Tar_LongNamesWithLongNameExtension_Async() public void Tar_BZip2_Skip_Entry_Stream_Async() { using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2")); - using var reader = TarReader.Open(stream); + using var reader = TarReader.OpenReader(stream); var names = new List(); while (reader.MoveToNextEntry()) { @@ -157,7 +157,7 @@ public void Tar_Containing_Rar_Reader_Async() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); Assert.True(reader.ArchiveType == ArchiveType.Tar); } @@ -166,7 +166,7 @@ public void Tar_With_TarGz_With_Flushed_EntryStream_Async() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsTarGz.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); Assert.True(reader.MoveToNextEntry()); Assert.Equal("inner.tar.gz", reader.Entry.Key); @@ -174,7 +174,7 @@ public void Tar_With_TarGz_With_Flushed_EntryStream_Async() using var flushingStream = new FlushOnDisposeStream(entryStream); // Extract inner.tar.gz - using var innerReader = ReaderFactory.Open(flushingStream); + using var innerReader = ReaderFactory.OpenReader(flushingStream); Assert.True(innerReader.MoveToNextEntry()); Assert.Equal("test", innerReader.Entry.Key); } @@ -184,7 +184,7 @@ public async ValueTask Tar_Broken_Stream_Async() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"); using Stream stream = File.OpenRead(archiveFullPath); - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var memoryStream = new MemoryStream(); Assert.True(await reader.MoveToNextEntryAsync()); @@ -201,7 +201,7 @@ public async ValueTask Tar_Corrupted_Async() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "TarCorrupted.tar"); using Stream stream = File.OpenRead(archiveFullPath); - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var memoryStream = new MemoryStream(); Assert.True(await reader.MoveToNextEntryAsync()); @@ -220,7 +220,7 @@ public async ValueTask Tar_GZip_With_Symlink_Entries_Async() using Stream stream = File.OpenRead( Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz") ); - await using var reader = await ReaderFactory.OpenAsync( + await using var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(stream), new ReaderOptions { LookForHeader = true } ); diff --git a/tests/SharpCompress.Test/Tar/TarReaderTests.cs b/tests/SharpCompress.Test/Tar/TarReaderTests.cs index c4763b3c1..2f0b92480 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderTests.cs @@ -22,7 +22,7 @@ public void Tar_Skip() using Stream stream = new ForwardOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")) ); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); var x = 0; while (reader.MoveToNextEntry()) { @@ -65,7 +65,7 @@ public void Tar_Skip() public void Tar_BZip2_Entry_Stream() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"))) - using (var reader = TarReader.Open(stream)) + using (var reader = TarReader.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -102,7 +102,7 @@ public void Tar_LongNamesWithLongNameExtension() Path.Combine(TEST_ARCHIVES_PATH, "Tar.LongPathsWithLongNameExtension.tar") ) ) - using (var reader = TarReader.Open(stream)) + using (var reader = TarReader.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -129,7 +129,7 @@ public void Tar_LongNamesWithLongNameExtension() public void Tar_BZip2_Skip_Entry_Stream() { using Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2")); - using var reader = TarReader.Open(stream); + using var reader = TarReader.OpenReader(stream); var names = new List(); while (reader.MoveToNextEntry()) { @@ -149,7 +149,7 @@ public void Tar_Containing_Rar_Reader() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsRar.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); Assert.True(reader.ArchiveType == ArchiveType.Tar); } @@ -158,7 +158,7 @@ public void Tar_With_TarGz_With_Flushed_EntryStream() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.ContainsTarGz.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); Assert.True(reader.MoveToNextEntry()); Assert.Equal("inner.tar.gz", reader.Entry.Key); @@ -166,7 +166,7 @@ public void Tar_With_TarGz_With_Flushed_EntryStream() using var flushingStream = new FlushOnDisposeStream(entryStream); // Extract inner.tar.gz - using var innerReader = ReaderFactory.Open(flushingStream); + using var innerReader = ReaderFactory.OpenReader(flushingStream); Assert.True(innerReader.MoveToNextEntry()); Assert.Equal("test", innerReader.Entry.Key); } @@ -176,7 +176,7 @@ public void Tar_Broken_Stream() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); var memoryStream = new MemoryStream(); Assert.True(reader.MoveToNextEntry()); @@ -191,7 +191,7 @@ public void Tar_Corrupted() { var archiveFullPath = Path.Combine(TEST_ARCHIVES_PATH, "TarCorrupted.tar"); using Stream stream = File.OpenRead(archiveFullPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); var memoryStream = new MemoryStream(); Assert.True(reader.MoveToNextEntry()); @@ -208,7 +208,7 @@ public void Tar_GZip_With_Symlink_Entries() using Stream stream = File.OpenRead( Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz") ); - using var reader = TarReader.Open(stream); + using var reader = TarReader.OpenReader(stream); while (reader.MoveToNextEntry()) { if (reader.Entry.IsDirectory) @@ -304,7 +304,7 @@ public void Tar_Malformed_LongName_Excessive_Size() // The important thing is it doesn't cause OutOfMemoryException Assert.Throws(() => { - using var reader = TarReader.Open(stream); + using var reader = TarReader.OpenReader(stream); reader.MoveToNextEntry(); }); } diff --git a/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs index 290a01edc..d9884543e 100644 --- a/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading.Tasks; using SharpCompress.Common; +using SharpCompress.Test.Mocks; using SharpCompress.Writers.Tar; using Xunit; @@ -66,7 +67,7 @@ public async ValueTask Tar_Finalize_Archive_Async(bool finalizeArchive) using Stream content = File.OpenRead(Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg")); using ( var writer = new TarWriter( - stream, + new AsyncOnlyStream(stream), new TarWriterOptions(CompressionType.None, finalizeArchive) ) ) diff --git a/tests/SharpCompress.Test/Tar/TarWriterDirectoryTests.cs b/tests/SharpCompress.Test/Tar/TarWriterDirectoryTests.cs index e0b5d8212..b900f9b59 100644 --- a/tests/SharpCompress.Test/Tar/TarWriterDirectoryTests.cs +++ b/tests/SharpCompress.Test/Tar/TarWriterDirectoryTests.cs @@ -25,7 +25,7 @@ public void TarWriter_WriteDirectory_CreatesDirectoryEntry() } memoryStream.Position = 0; - using var archive = TarArchive.Open(memoryStream); + using var archive = TarArchive.OpenArchive(memoryStream); var entries = archive.Entries.ToList(); Assert.Single(entries); @@ -48,7 +48,7 @@ public void TarWriter_WriteDirectory_WithTrailingSlash() } memoryStream.Position = 0; - using var archive = TarArchive.Open(memoryStream); + using var archive = TarArchive.OpenArchive(memoryStream); var entries = archive.Entries.ToList(); Assert.Single(entries); @@ -71,7 +71,7 @@ public void TarWriter_WriteDirectory_WithBackslash() } memoryStream.Position = 0; - using var archive = TarArchive.Open(memoryStream); + using var archive = TarArchive.OpenArchive(memoryStream); var entries = archive.Entries.ToList(); Assert.Single(entries); @@ -94,7 +94,7 @@ public void TarWriter_WriteDirectory_EmptyString_IsSkipped() } memoryStream.Position = 0; - using var archive = TarArchive.Open(memoryStream); + using var archive = TarArchive.OpenArchive(memoryStream); Assert.Empty(archive.Entries); } @@ -116,7 +116,7 @@ public void TarWriter_WriteDirectory_MultipleDirectories() } memoryStream.Position = 0; - using var archive = TarArchive.Open(memoryStream); + using var archive = TarArchive.OpenArchive(memoryStream); var entries = archive.Entries.OrderBy(e => e.Key).ToList(); Assert.Equal(3, entries.Count); @@ -150,7 +150,7 @@ public void TarWriter_WriteDirectory_MixedWithFiles() } memoryStream.Position = 0; - using var archive = TarArchive.Open(memoryStream); + using var archive = TarArchive.OpenArchive(memoryStream); var entries = archive.Entries.OrderBy(e => e.Key).ToList(); Assert.Equal(3, entries.Count); diff --git a/tests/SharpCompress.Test/TestBase.cs b/tests/SharpCompress.Test/TestBase.cs index 702d6bca0..e2a98f13f 100644 --- a/tests/SharpCompress.Test/TestBase.cs +++ b/tests/SharpCompress.Test/TestBase.cs @@ -228,8 +228,8 @@ protected void CompareArchivesByPath(string file1, string file2, Encoding? encod //don't compare the order. OS X reads files from the file system in a different order therefore makes the archive ordering different var archive1Entries = new List(); var archive2Entries = new List(); - using (var archive1 = ReaderFactory.Open(File.OpenRead(file1), readerOptions)) - using (var archive2 = ReaderFactory.Open(File.OpenRead(file2), readerOptions)) + using (var archive1 = ReaderFactory.OpenReader(File.OpenRead(file1), readerOptions)) + using (var archive2 = ReaderFactory.OpenReader(File.OpenRead(file2), readerOptions)) { while (archive1.MoveToNextEntry()) { diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index 984d3b91a..45a41e7f6 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -29,7 +29,7 @@ protected void Write( writerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using var writer = WriterFactory.Open(stream, _type, writerOptions); + using var writer = WriterFactory.OpenWriter(stream, _type, writerOptions); writer.WriteAll(ORIGINAL_FILES_PATH, "*", SearchOption.AllDirectories); } CompareArchivesByPath( @@ -43,7 +43,7 @@ protected void Write( readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using var reader = ReaderFactory.Open( + using var reader = ReaderFactory.OpenReader( SharpCompressStream.Create(stream, leaveOpen: true), readerOptions ); @@ -73,7 +73,7 @@ protected async Task WriteAsync( writerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using var writer = WriterFactory.Open(stream, _type, writerOptions); + using var writer = WriterFactory.OpenAsyncWriter(stream, _type, writerOptions); await writer.WriteAllAsync( ORIGINAL_FILES_PATH, "*", @@ -92,7 +92,7 @@ await writer.WriteAllAsync( readerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - await using var reader = await ReaderFactory.OpenAsync( + await using var reader = ReaderFactory.OpenAsyncReader( new AsyncOnlyStream(SharpCompressStream.Create(stream, leaveOpen: true)), readerOptions, cancellationToken diff --git a/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs b/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs index 2c9435533..f1b53f616 100644 --- a/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64AsyncTests.cs @@ -177,7 +177,7 @@ bool forwardOnly using var zip = File.OpenWrite(filename); using var st = forwardOnly ? (Stream)new ForwardOnlyStream(zip) : zip; - using var zipWriter = (ZipWriter)WriterFactory.Open(st, ArchiveType.Zip, opts); + using var zipWriter = (ZipWriter)WriterFactory.OpenWriter(st, ArchiveType.Zip, opts); for (var i = 0; i < files; i++) { using var str = zipWriter.WriteToStream(i.ToString(), eo); @@ -198,28 +198,31 @@ public async ValueTask> ReadForwardOnlyAsync(string filename) long size = 0; ZipEntry? prev = null; using (var fs = File.OpenRead(filename)) - using (var rd = ZipReader.Open(fs, new ReaderOptions { LookForHeader = false })) { - while (await rd.MoveToNextEntryAsync()) + var rd = ReaderFactory.OpenAsyncReader(fs, new ReaderOptions { LookForHeader = false }); + await using (rd) { -#if NETFRAMEWORK || NETSTANDARD2_0 - using (var entryStream = await rd.OpenEntryStreamAsync()) + while (await rd.MoveToNextEntryAsync()) { - await entryStream.SkipEntryAsync(); - } +#if NETFRAMEWORK || NETSTANDARD2_0 + using (var entryStream = await rd.OpenEntryStreamAsync()) + { + await entryStream.SkipEntryAsync(); + } #else - await using (var entryStream = await rd.OpenEntryStreamAsync()) - { - await entryStream.SkipEntryAsync(); - } + await using (var entryStream = await rd.OpenEntryStreamAsync()) + { + await entryStream.SkipEntryAsync(); + } #endif - count++; - if (prev != null) - { - size += prev.Size; - } + count++; + if (prev != null) + { + size += prev.Size; + } - prev = rd.Entry; + prev = (ZipEntry)rd.Entry; + } } } @@ -233,7 +236,7 @@ public async ValueTask> ReadForwardOnlyAsync(string filename) public Tuple ReadArchive(string filename) { - using var archive = ArchiveFactory.Open(filename); + using var archive = ArchiveFactory.OpenArchive(filename); return new Tuple( archive.Entries.Count(), archive.Entries.Select(x => x.Size).Sum() diff --git a/tests/SharpCompress.Test/Zip/Zip64Tests.cs b/tests/SharpCompress.Test/Zip/Zip64Tests.cs index 43dc18743..6242b8773 100644 --- a/tests/SharpCompress.Test/Zip/Zip64Tests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64Tests.cs @@ -162,7 +162,7 @@ bool forwardOnly using var zip = File.OpenWrite(filename); using var st = forwardOnly ? (Stream)new ForwardOnlyStream(zip) : zip; - using var zipWriter = (ZipWriter)WriterFactory.Open(st, ArchiveType.Zip, opts); + using var zipWriter = (ZipWriter)WriterFactory.OpenWriter(st, ArchiveType.Zip, opts); for (var i = 0; i < files; i++) { using var str = zipWriter.WriteToStream(i.ToString(), eo); @@ -180,9 +180,9 @@ public Tuple ReadForwardOnly(string filename) { long count = 0; long size = 0; - ZipEntry? prev = null; + IEntry? prev = null; using (var fs = File.OpenRead(filename)) - using (var rd = ZipReader.Open(fs, new ReaderOptions { LookForHeader = false })) + using (var rd = ZipReader.OpenReader(fs, new ReaderOptions { LookForHeader = false })) { while (rd.MoveToNextEntry()) { @@ -208,7 +208,7 @@ public Tuple ReadForwardOnly(string filename) public Tuple ReadArchive(string filename) { - using var archive = ArchiveFactory.Open(filename); + using var archive = ArchiveFactory.OpenArchive(filename); return new Tuple( archive.Entries.Count(), archive.Entries.Select(x => x.Size).Sum() diff --git a/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs b/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs index b8451a45c..fa26a76ee 100644 --- a/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs +++ b/tests/SharpCompress.Test/Zip/Zip64VersionConsistencyTests.cs @@ -37,7 +37,7 @@ public void Zip64_Small_File_With_UseZip64_Should_Have_Matching_Versions() UseZip64 = true, }; - ZipArchive zipArchive = ZipArchive.Create(); + var zipArchive = ZipArchive.CreateArchive(); zipArchive.AddEntry("empty", new MemoryStream()); zipArchive.SaveTo(filename, writerOptions); @@ -141,7 +141,7 @@ public void Zip64_Small_File_Without_UseZip64_Should_Have_Version_20() UseZip64 = false, }; - ZipArchive zipArchive = ZipArchive.Create(); + var zipArchive = ZipArchive.CreateArchive(); zipArchive.AddEntry("empty", new MemoryStream()); zipArchive.SaveTo(filename, writerOptions); @@ -192,7 +192,7 @@ public void LZMA_Compression_Should_Use_Version_63() UseZip64 = false, }; - ZipArchive zipArchive = ZipArchive.Create(); + var zipArchive = ZipArchive.CreateArchive(); var data = new byte[100]; new Random(42).NextBytes(data); zipArchive.AddEntry("test.bin", new MemoryStream(data)); @@ -245,7 +245,7 @@ public void PPMd_Compression_Should_Use_Version_63() UseZip64 = false, }; - ZipArchive zipArchive = ZipArchive.Create(); + var zipArchive = ZipArchive.CreateArchive(); var data = new byte[100]; new Random(42).NextBytes(data); zipArchive.AddEntry("test.bin", new MemoryStream(data)); @@ -298,7 +298,7 @@ public void Zip64_Multiple_Small_Files_With_UseZip64_Should_Have_Matching_Versio UseZip64 = true, }; - ZipArchive zipArchive = ZipArchive.Create(); + var zipArchive = ZipArchive.CreateArchive(); for (int i = 0; i < 5; i++) { var data = new byte[100]; diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs index 41420f983..1d114f780 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveAsyncTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using SharpCompress; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; @@ -125,9 +126,9 @@ public async ValueTask Zip_Random_Write_Remove_Async() var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); - using (var archive = ZipArchive.Open(unmodified)) + await using (var archive = ZipArchive.OpenAsyncArchive(unmodified)) { - var entry = archive.Entries.Single(x => + var entry = await archive.EntriesAsync.SingleAsync(x => x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ); archive.RemoveEntry(entry); @@ -148,7 +149,7 @@ public async ValueTask Zip_Random_Write_Add_Async() var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using (var archive = ZipArchive.Open(unmodified)) + await using (var archive = ZipArchive.OpenAsyncArchive(unmodified)) { archive.AddEntry("jpg\\test.jpg", jpg); @@ -166,7 +167,7 @@ public async ValueTask Zip_Create_New_Async() var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using (var archive = ZipArchive.Create()) + await using (var archive = (ZipArchive)ZipArchive.CreateAsyncArchive()) { archive.DeflateCompressionLevel = CompressionLevel.BestSpeed; archive.AddAllFromDirectory(ORIGINAL_FILES_PATH); @@ -183,14 +184,21 @@ public async ValueTask Zip_Create_New_Async() public async ValueTask Zip_Deflate_Entry_Stream_Async() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) - await using (var archive = await ZipArchive.OpenAsync(new AsyncOnlyStream(stream))) { - await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) + IAsyncArchive archive = ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + try { - await entry.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) + { + await entry.WriteToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); + } + } + finally + { + await archive.DisposeAsync(); } } VerifyFiles(); @@ -200,12 +208,19 @@ await entry.WriteToDirectoryAsync( public async ValueTask Zip_Deflate_Archive_WriteToDirectoryAsync() { using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) - await using (var archive = await ZipArchive.OpenAsync(new AsyncOnlyStream(stream))) { - await archive.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true } - ); + IAsyncArchive archive = ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + try + { + await archive.WriteToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true } + ); + } + finally + { + await archive.DisposeAsync(); + } } VerifyFiles(); } @@ -217,13 +232,20 @@ public async ValueTask Zip_Deflate_Archive_WriteToDirectoryAsync_WithProgress() var progress = new Progress(report => progressReports.Add(report)); using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.zip"))) - await using (var archive = await ZipArchive.OpenAsync(new AsyncOnlyStream(stream))) { - await archive.WriteToDirectoryAsync( - SCRATCH_FILES_PATH, - new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, - progress - ); + IAsyncArchive archive = ZipArchive.OpenAsyncArchive(new AsyncOnlyStream(stream)); + try + { + await archive.WriteToDirectoryAsync( + SCRATCH_FILES_PATH, + new ExtractionOptions { ExtractFullPath = true, Overwrite = true }, + progress + ); + } + finally + { + await archive.DisposeAsync(); + } } VerifyFiles(); diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs index 45bab5e3b..183312cd9 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs @@ -12,7 +12,7 @@ public class ZipArchiveDirectoryTests : TestBase [Fact] public void ZipArchive_AddDirectoryEntry_CreatesDirectoryEntry() { - using var archive = ZipArchive.Create(); + using var archive = ZipArchive.CreateArchive(); archive.AddDirectoryEntry("test-dir", DateTime.Now); @@ -25,7 +25,7 @@ public void ZipArchive_AddDirectoryEntry_CreatesDirectoryEntry() [Fact] public void ZipArchive_AddDirectoryEntry_MultipleDirectories() { - using var archive = ZipArchive.Create(); + using var archive = ZipArchive.CreateArchive(); archive.AddDirectoryEntry("dir1", DateTime.Now); archive.AddDirectoryEntry("dir2", DateTime.Now); @@ -39,7 +39,7 @@ public void ZipArchive_AddDirectoryEntry_MultipleDirectories() [Fact] public void ZipArchive_AddDirectoryEntry_MixedWithFiles() { - using var archive = ZipArchive.Create(); + using var archive = ZipArchive.CreateArchive(); archive.AddDirectoryEntry("dir1", DateTime.Now); @@ -62,7 +62,7 @@ public void ZipArchive_AddDirectoryEntry_SaveAndReload() { var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "zip-directory-test.zip"); - using (var archive = ZipArchive.Create()) + using (var archive = ZipArchive.CreateArchive()) { archive.AddDirectoryEntry("dir1", DateTime.Now); archive.AddDirectoryEntry("dir2", DateTime.Now); @@ -84,7 +84,7 @@ public void ZipArchive_AddDirectoryEntry_SaveAndReload() } } - using (var archive = ZipArchive.Open(scratchPath)) + using (var archive = ZipArchive.OpenArchive(scratchPath)) { var entries = archive.Entries.OrderBy(e => e.Key).ToList(); Assert.Equal(3, entries.Count); @@ -103,7 +103,7 @@ public void ZipArchive_AddDirectoryEntry_SaveAndReload() [Fact] public void ZipArchive_AddDirectoryEntry_DuplicateKey_ThrowsException() { - using var archive = ZipArchive.Create(); + using var archive = ZipArchive.CreateArchive(); archive.AddDirectoryEntry("test-dir", DateTime.Now); diff --git a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs index 067e99537..1c0998df3 100644 --- a/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipArchiveTests.cs @@ -5,6 +5,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; +using SharpCompress.Common.Zip; using SharpCompress.Readers; using SharpCompress.Writers; using SharpCompress.Writers.Zip; @@ -234,7 +235,7 @@ public void Zip_Random_Write_Remove() var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); - using (var archive = ZipArchive.Open(unmodified)) + using (var archive = ZipArchive.OpenArchive(unmodified)) { var entry = archive.Entries.Single(x => x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) @@ -257,7 +258,7 @@ public void Zip_Random_Write_Add() var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod.zip"); var modified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.mod2.zip"); - using (var archive = ZipArchive.Open(unmodified)) + using (var archive = ZipArchive.OpenArchive(unmodified)) { archive.AddEntry("jpg\\test.jpg", jpg); @@ -275,7 +276,7 @@ public void Zip_Save_Twice() var scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); var scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); - using (var arc = ZipArchive.Create()) + using (var arc = ZipArchive.CreateArchive()) { var str = "test.txt"; var source = new MemoryStream(Encoding.UTF8.GetBytes(str)); @@ -292,7 +293,7 @@ public void Zip_Removal_Poly() { var scratchPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using var vfs = (ZipArchive)ArchiveFactory.Open(scratchPath); + using var vfs = (ZipArchive)ArchiveFactory.OpenArchive(scratchPath); var e = vfs.Entries.First(v => v.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ); @@ -312,7 +313,7 @@ public void Zip_Removal_Poly() [Fact] public void Zip_Create_NoDups() { - using var arc = ZipArchive.Create(); + using var arc = ZipArchive.CreateArchive(); arc.AddEntry("1.txt", new MemoryStream()); Assert.Throws(() => arc.AddEntry("\\1.txt", new MemoryStream())); } @@ -323,7 +324,7 @@ public void Zip_Create_Same_Stream() var scratchPath1 = Path.Combine(SCRATCH_FILES_PATH, "a.zip"); var scratchPath2 = Path.Combine(SCRATCH_FILES_PATH, "b.zip"); - using (var arc = ZipArchive.Create()) + using (var arc = ZipArchive.CreateArchive()) { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes("qwert"))) { @@ -369,7 +370,7 @@ var file in Directory.EnumerateFiles( var scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using (var archive = ZipArchive.Create()) + using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory(SCRATCH_FILES_PATH); @@ -388,7 +389,7 @@ var file in Directory.EnumerateFiles( [Fact] public void Zip_Create_Empty_And_Read() { - var archive = ZipArchive.Create(); + var archive = ZipArchive.CreateArchive(); var archiveStream = new MemoryStream(); @@ -396,7 +397,7 @@ public void Zip_Create_Empty_And_Read() archiveStream.Position = 0; - var readArchive = ArchiveFactory.Open(archiveStream); + var readArchive = ArchiveFactory.OpenArchive(archiveStream); var count = readArchive.Entries.Count(); @@ -434,7 +435,7 @@ var file in Directory.EnumerateFiles( } var scratchPath = Path.Combine(SCRATCH2_FILES_PATH, "Zip.deflate.noEmptyDirs.zip"); - using (var archive = ZipArchive.Create()) + using (var archive = ZipArchive.CreateArchive()) { archive.AddAllFromDirectory(SCRATCH_FILES_PATH); archive.RemoveEntry( @@ -454,7 +455,7 @@ var file in Directory.EnumerateFiles( public void Zip_Deflate_WinzipAES_Read() { using ( - var reader = ZipArchive.Open( + var reader = ZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"), new ReaderOptions { Password = "test" } ) @@ -474,7 +475,7 @@ public void Zip_Deflate_WinzipAES_Read() [Fact] public void Zip_Deflate_WinzipAES_MultiOpenEntryStream() { - using var reader = ZipArchive.Open( + using var reader = ZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES2.zip"), new ReaderOptions { Password = "test" } ); @@ -490,23 +491,23 @@ public void Zip_Deflate_WinzipAES_MultiOpenEntryStream() [Fact] public void Zip_Read_Volume_Comment() { - using var reader = ZipArchive.Open( + using var reader = ZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.zip"), new ReaderOptions { Password = "test" } ); var isComplete = reader.IsComplete; - Assert.Equal(1, reader.Volumes.Count); + Assert.Equal(1, reader.Volumes.Count()); var expectedComment = "Encoding:utf-8 || Compression:Deflate levelDefault || Encrypt:None || ZIP64:Always\r\nCreated at 2017-Jan-23 14:10:43 || DotNetZip Tool v1.9.1.8\r\nTest zip64 archive"; - Assert.Equal(expectedComment, reader.Volumes.First().Comment); + Assert.Equal(expectedComment, ((ZipVolume)reader.Volumes.First()).Comment); } [Fact] public void Zip_BZip2_Pkware_Read() { using ( - var reader = ZipArchive.Open( + var reader = ZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"), new ReaderOptions { Password = "test" } ) @@ -528,7 +529,7 @@ public void Zip_Random_Entry_Access() { var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.noEmptyDirs.zip"); - var a = ZipArchive.Open(unmodified); + var a = ZipArchive.OpenArchive(unmodified); var count = 0; foreach (var e in a.Entries) { @@ -539,7 +540,7 @@ public void Zip_Random_Entry_Access() Assert.Equal(3, count); a.Dispose(); - a = ZipArchive.Open(unmodified); + a = ZipArchive.OpenArchive(unmodified); var count2 = 0; foreach (var e in a.Entries) @@ -571,7 +572,7 @@ public void Zip_Deflate_PKWear_Multipy_Entry_Access() var zipFile = Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.pkware.zip"); using var fileStream = File.OpenRead(zipFile); - using var archive = ArchiveFactory.Open( + using var archive = ArchiveFactory.OpenArchive( fileStream, new ReaderOptions { Password = "12345678" } ); @@ -595,7 +596,7 @@ public void Zip_Evil_Throws_Exception_Windows() Assert.ThrowsAny(() => { - using var archive = ZipArchive.Open(zipFile); + using var archive = ZipArchive.OpenArchive(zipFile); foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) { entry.WriteToDirectory( @@ -617,7 +618,13 @@ public void TestSharpCompressWithEmptyStream() { MemoryStream stream = new NonSeekableMemoryStream(); - using (var zipWriter = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) + using ( + var zipWriter = WriterFactory.OpenWriter( + stream, + ArchiveType.Zip, + CompressionType.Deflate + ) + ) { zipWriter.Write("foo.txt", new MemoryStream(Array.Empty())); zipWriter.Write("foo2.txt", new MemoryStream(new byte[10])); @@ -626,7 +633,7 @@ public void TestSharpCompressWithEmptyStream() stream = new MemoryStream(stream.ToArray()); File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), stream.ToArray()); - using (var zipArchive = ZipArchive.Open(stream)) + using (var zipArchive = ZipArchive.OpenArchive(stream)) { foreach (var entry in zipArchive.Entries) { @@ -648,7 +655,7 @@ public void Zip_BadLocalExtra_Read() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.badlocalextra.zip"); - using var za = ZipArchive.Open(zipPath); + using var za = ZipArchive.OpenArchive(zipPath); var ex = Record.Exception(() => { var firstEntry = za.Entries.First(x => x.Key == "first.txt"); @@ -668,7 +675,7 @@ public void Zip_NoCompression_DataDescriptors_Read() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.datadescriptors.zip"); - using var za = ZipArchive.Open(zipPath); + using var za = ZipArchive.OpenArchive(zipPath); var firstEntry = za.Entries.First(x => x.Key == "first.txt"); var buffer = new byte[4096]; @@ -707,8 +714,8 @@ public void Zip_LongComment_Read() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.LongComment.zip"); - using var za = ZipArchive.Open(zipPath); - var count = za.Entries.Count; + using var za = ZipArchive.OpenArchive(zipPath); + var count = za.Entries.Count(); Assert.Equal(1, count); } @@ -717,7 +724,7 @@ public void Zip_Zip64_CompressedSizeExtraOnly_Read() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.zip64.compressedonly.zip"); - using var za = ZipArchive.Open(zipPath); + using var za = ZipArchive.OpenArchive(zipPath); var firstEntry = za.Entries.First(x => x.Key == "test/test.txt"); using var memoryStream = new MemoryStream(); @@ -731,7 +738,7 @@ public void Zip_Uncompressed_Read_All() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); using var stream = File.OpenRead(zipPath); - var reader = ReaderFactory.Open(stream); + var reader = ReaderFactory.OpenReader(stream); var entries = 0; while (reader.MoveToNextEntry()) { @@ -759,7 +766,7 @@ public void Zip_Uncompressed_Skip_All() }; var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); using var stream = File.OpenRead(zipPath); - var reader = ReaderFactory.Open(stream); + var reader = ReaderFactory.OpenReader(stream); var x = 0; while (reader.MoveToNextEntry()) { @@ -776,7 +783,7 @@ public void Zip_Forced_Ignores_UnicodePathExtra() var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.UnicodePathExtra.zip"); using (var stream = File.OpenRead(zipPath)) { - var reader = ReaderFactory.Open( + var reader = ReaderFactory.OpenReader( stream, new ReaderOptions { @@ -791,7 +798,7 @@ public void Zip_Forced_Ignores_UnicodePathExtra() } using (var stream = File.OpenRead(zipPath)) { - var reader = ReaderFactory.Open( + var reader = ReaderFactory.OpenReader( stream, new ReaderOptions { @@ -809,7 +816,7 @@ public void Zip_Forced_Ignores_UnicodePathExtra() [Fact] public void TestDataDescriptorRead() { - using var archive = ArchiveFactory.Open( + using var archive = ArchiveFactory.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.datadescriptors.zip") ); var firstEntry = archive.Entries.First(); @@ -821,10 +828,10 @@ public void TestDataDescriptorRead() [Fact] public void Zip_EntryCommentAfterEntryRead() { - using var archive = ZipArchive.Open( + using var archive = ZipArchive.OpenArchive( Path.Combine(TEST_ARCHIVES_PATH, "Zip.EntryComment.zip") ); - var firstEntry = archive.Entries.First(); + var firstEntry = (ZipArchiveEntry)archive.Entries.First(); Assert.Equal(29, firstEntry.Comment!.Length); using var _ = firstEntry.OpenEntryStream(); Assert.Equal(29, firstEntry.Comment.Length); @@ -833,7 +840,9 @@ public void Zip_EntryCommentAfterEntryRead() [Fact] public void Zip_FilePermissions() { - using var archive = ArchiveFactory.Open(Path.Combine(TEST_ARCHIVES_PATH, "Zip.644.zip")); + using var archive = ArchiveFactory.OpenArchive( + Path.Combine(TEST_ARCHIVES_PATH, "Zip.644.zip") + ); var firstEntry = archive.Entries.First(); const int S_IFREG = 0x8000; diff --git a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs index c04c9e930..5824ebec3 100644 --- a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs @@ -60,7 +60,9 @@ float expectedRatio // Create zip archive in memory using var zipStream = new MemoryStream(); - using (var writer = CreateWriterWithLevel(zipStream, compressionType, compressionLevel)) + using ( + var writer = CreateWriterWithLevelAsync(zipStream, compressionType, compressionLevel) + ) { await writer.WriteAsync($"file1_{sizeMb}MiB.txt", new MemoryStream(file1Data)); await writer.WriteAsync($"data/file2_{sizeMb * 2}MiB.txt", new MemoryStream(file2Data)); @@ -129,7 +131,9 @@ float expectedRatio CompressionLevel = compressionLevel, }; - using (var writer = WriterFactory.Open(zipStream, ArchiveType.Zip, writerOptions)) + using ( + var writer = WriterFactory.OpenAsyncWriter(zipStream, ArchiveType.Zip, writerOptions) + ) { await writer.WriteAsync( $"{compressionType}_level_{compressionLevel}_{sizeMb}MiB.txt", @@ -150,7 +154,7 @@ await writer.WriteAsync( // Verify the archive zipStream.Position = 0; - using var archive = ZipArchive.Open(zipStream); + using var archive = ZipArchive.OpenArchive(zipStream); var entry = archive.Entries.Single(e => !e.IsDirectory); using var entryStream = await entry.OpenEntryStreamAsync(); @@ -191,7 +195,9 @@ float expectedRatio // Create archive with specified compression and level using var zipStream = new MemoryStream(); - using (var writer = CreateWriterWithLevel(zipStream, compressionType, compressionLevel)) + using ( + var writer = CreateWriterWithLevelAsync(zipStream, compressionType, compressionLevel) + ) { await writer.WriteAsync( $"{compressionType}_{compressionLevel}_{sizeMb}MiB.txt", @@ -205,7 +211,7 @@ await writer.WriteAsync( // Verify the archive zipStream.Position = 0; - using var archive = ZipArchive.Open(zipStream); + using var archive = ZipArchive.OpenArchive(zipStream); var entry = archive.Entries.Single(e => !e.IsDirectory); using var entryStream = await entry.OpenEntryStreamAsync(); @@ -244,7 +250,7 @@ private async ValueTask VerifyArchiveContentAsync( ) { zipStream.Position = 0; - using var archive = ZipArchive.Open(zipStream); + using var archive = ZipArchive.OpenArchive(zipStream); foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) { diff --git a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs index 0e66edc26..fb9226bed 100644 --- a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcTests.cs @@ -125,7 +125,7 @@ float expectedRatio CompressionLevel = compressionLevel, }; - using (var writer = WriterFactory.Open(zipStream, ArchiveType.Zip, writerOptions)) + using (var writer = WriterFactory.OpenWriter(zipStream, ArchiveType.Zip, writerOptions)) { writer.Write( $"{compressionType}_level_{compressionLevel}_{sizeMb}MiB.txt", @@ -146,7 +146,7 @@ float expectedRatio // Verify the archive zipStream.Position = 0; - using var archive = ZipArchive.Open(zipStream); + using var archive = ZipArchive.OpenArchive(zipStream); var entry = archive.Entries.Single(e => !e.IsDirectory); using var entryStream = entry.OpenEntryStream(); @@ -201,7 +201,7 @@ float expectedRatio // Verify the archive zipStream.Position = 0; - using var archive = ZipArchive.Open(zipStream); + using var archive = ZipArchive.OpenArchive(zipStream); var entry = archive.Entries.Single(e => !e.IsDirectory); using var entryStream = entry.OpenEntryStream(); diff --git a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs index 33a673f47..3aec7ea9d 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderAsyncTests.cs @@ -20,7 +20,7 @@ public async ValueTask Issue_269_Double_Skip_Async() { var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip"); using Stream stream = new ForwardOnlyStream(File.OpenRead(path)); - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var count = 0; while (await reader.MoveToNextEntryAsync()) { @@ -65,7 +65,7 @@ public async ValueTask Zip_Deflate_Streamed_Skip_Async() using Stream stream = new ForwardOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); - await using var reader = await ReaderFactory.OpenAsync(new AsyncOnlyStream(stream)); + await using var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream)); var x = 0; while (await reader.MoveToNextEntryAsync()) { @@ -121,8 +121,14 @@ public async ValueTask Zip_BZip2_PkwareEncryption_Read_Async() using ( Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip")) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using ( + IReader baseReader = ZipReader.OpenReader( + stream, + new ReaderOptions { Password = "test" } + ) + ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) @@ -144,7 +150,7 @@ 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.OpenAsync(new AsyncOnlyStream(stream))) + await using (var reader = ReaderFactory.OpenAsyncReader(new AsyncOnlyStream(stream))) { while (await reader.MoveToNextEntryAsync()) { @@ -168,7 +174,7 @@ public async ValueTask Zip_Reader_Disposal_Test2_Async() File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ) ); - await using var reader = await ReaderFactory.OpenAsync(stream); + await using var reader = ReaderFactory.OpenAsyncReader(stream); while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) @@ -191,8 +197,14 @@ await Assert.ThrowsAsync(async () => File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip")) ) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using ( + IReader baseReader = ZipReader.OpenReader( + stream, + new ReaderOptions { Password = "test" } + ) + ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) @@ -216,8 +228,14 @@ public async ValueTask Zip_Deflate_WinzipAES_Read_Async() File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip")) ) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using ( + IReader baseReader = ZipReader.OpenReader( + stream, + new ReaderOptions { Password = "test" } + ) + ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) @@ -242,8 +260,14 @@ public async ValueTask Zip_Deflate_ZipCrypto_Read_Async() File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip")) ) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using ( + IReader baseReader = ZipReader.OpenReader( + stream, + new ReaderOptions { Password = "test" } + ) + ) { + IAsyncReader reader = (IAsyncReader)baseReader; while (await reader.MoveToNextEntryAsync()) { if (!reader.Entry.IsDirectory) diff --git a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs index fc87dc57d..c3b53e113 100644 --- a/tests/SharpCompress.Test/Zip/ZipReaderTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipReaderTests.cs @@ -22,7 +22,7 @@ public void Issue_269_Double_Skip() { var path = Path.Combine(TEST_ARCHIVES_PATH, "PrePostHeaders.zip"); using Stream stream = new ForwardOnlyStream(File.OpenRead(path)); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); var count = 0; while (reader.MoveToNextEntry()) { @@ -62,7 +62,7 @@ public void Zip_Deflate_Streamed_Skip() using Stream stream = new ForwardOnlyStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); var x = 0; while (reader.MoveToNextEntry()) { @@ -111,7 +111,7 @@ public void Zip_BZip2_PkwareEncryption_Read() using ( Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip")) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using (var reader = ZipReader.OpenReader(stream, new ReaderOptions { Password = "test" })) { while (reader.MoveToNextEntry()) { @@ -134,7 +134,7 @@ 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.Open(stream)) + using (var reader = ReaderFactory.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -156,7 +156,7 @@ public void Zip_Reader_Disposal_Test2() using var stream = new TestStream( File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.dd.zip")) ); - var reader = ReaderFactory.Open(stream); + var reader = ReaderFactory.OpenReader(stream); while (reader.MoveToNextEntry()) { if (!reader.Entry.IsDirectory) @@ -179,7 +179,9 @@ public void Zip_LZMA_WinzipAES_Read() => Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip") ) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using ( + var reader = ZipReader.OpenReader(stream, new ReaderOptions { Password = "test" }) + ) { while (reader.MoveToNextEntry()) { @@ -204,7 +206,7 @@ public void Zip_Deflate_WinzipAES_Read() Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip") ) ) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using (var reader = ZipReader.OpenReader(stream, new ReaderOptions { Password = "test" })) { while (reader.MoveToNextEntry()) { @@ -226,7 +228,7 @@ public void Zip_Deflate_ZipCrypto_Read() { var count = 0; using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "zipcrypto.zip"))) - using (var reader = ZipReader.Open(stream, new ReaderOptions { Password = "test" })) + using (var reader = ZipReader.OpenReader(stream, new ReaderOptions { Password = "test" })) { while (reader.MoveToNextEntry()) { @@ -256,7 +258,13 @@ public void TestSharpCompressWithEmptyStream() using var memory = new MemoryStream(); Stream stream = new TestStream(memory, read: true, write: true, seek: false); - using (var zipWriter = WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate)) + using ( + var zipWriter = WriterFactory.OpenWriter( + stream, + ArchiveType.Zip, + CompressionType.Deflate + ) + ) { zipWriter.Write(expected[0].Item1, new MemoryStream(expected[0].Item2)); zipWriter.Write(expected[1].Item1, new MemoryStream(expected[1].Item2)); @@ -265,7 +273,7 @@ public void TestSharpCompressWithEmptyStream() stream = new MemoryStream(memory.ToArray()); File.WriteAllBytes(Path.Combine(SCRATCH_FILES_PATH, "foo.zip"), memory.ToArray()); - using IReader zipReader = ZipReader.Open( + using IReader zipReader = ZipReader.OpenReader( SharpCompressStream.Create(stream, leaveOpen: true, throwOnDispose: true) ); var i = 0; @@ -297,7 +305,7 @@ public void Zip_None_Issue86_Streamed_Read() using Stream stream = File.OpenRead( Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.issue86.zip") ); - using var reader = ZipReader.Open(stream); + using var reader = ZipReader.OpenReader(stream); foreach (var key in keys) { reader.MoveToNextEntry(); @@ -319,7 +327,7 @@ public void Zip_ReaderMoveToNextEntry() var keys = new[] { "version", "sizehint", "data/0/metadata", "data/0/records" }; using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "test_477.zip")); - using var reader = ZipReader.Open(fileStream); + using var reader = ZipReader.OpenReader(fileStream); foreach (var key in keys) { reader.MoveToNextEntry(); @@ -333,7 +341,7 @@ public void Issue_685() { var count = 0; using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Issue_685.zip")); - using var reader = ZipReader.Open(fileStream); + using var reader = ZipReader.OpenReader(fileStream); while (reader.MoveToNextEntry()) { count++; @@ -347,7 +355,7 @@ public void Zip_ReaderFactory_Uncompressed_Read_All() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); using var stream = File.OpenRead(zipPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); while (reader.MoveToNextEntry()) { var target = new MemoryStream(); @@ -360,7 +368,7 @@ public void Zip_ReaderFactory_Uncompressed_Skip_All() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "Zip.uncompressed.zip"); using var stream = File.OpenRead(zipPath); - using var reader = ReaderFactory.Open(stream); + using var reader = ReaderFactory.OpenReader(stream); while (reader.MoveToNextEntry()) { } } @@ -371,10 +379,10 @@ public void Zip_Uncompressed_64bit() { var zipPath = Path.Combine(TEST_ARCHIVES_PATH, "64bitstream.zip.7z"); using var stream = File.OpenRead(zipPath); - var archive = ArchiveFactory.Open(stream); + var archive = ArchiveFactory.OpenArchive(stream); var reader = archive.ExtractAllEntries(); reader.MoveToNextEntry(); - var zipReader = ZipReader.Open(reader.OpenEntryStream()); + var zipReader = ZipReader.OpenReader(reader.OpenEntryStream()); var x = 0; while (zipReader.MoveToNextEntry()) { @@ -387,7 +395,7 @@ public void Zip_Uncompressed_64bit() [Fact] public void Zip_Uncompressed_Encrypted_Read() { - using var reader = ReaderFactory.Open( + using var reader = ReaderFactory.OpenReader( Path.Combine(TEST_ARCHIVES_PATH, "Zip.none.encrypted.zip"), new ReaderOptions { Password = "test" } ); @@ -415,7 +423,7 @@ public void ZipReader_Returns_Same_Entries_As_ZipArchive() var readerKeys = new List(); using (var stream = File.OpenRead(path)) - using (var reader = ZipReader.Open(stream)) + using (var reader = ZipReader.OpenReader(stream)) { while (reader.MoveToNextEntry()) { @@ -424,7 +432,7 @@ public void ZipReader_Returns_Same_Entries_As_ZipArchive() } var archiveKeys = new List(); - using (var archive = Archives.Zip.ZipArchive.Open(path)) + using (var archive = Archives.Zip.ZipArchive.OpenArchive(path)) { foreach (var entry in archive.Entries) { diff --git a/tests/SharpCompress.Test/Zip/ZipWriterDirectoryTests.cs b/tests/SharpCompress.Test/Zip/ZipWriterDirectoryTests.cs index a3b2788e4..14bc143c9 100644 --- a/tests/SharpCompress.Test/Zip/ZipWriterDirectoryTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipWriterDirectoryTests.cs @@ -22,7 +22,7 @@ public void ZipWriter_WriteDirectory_CreatesDirectoryEntry() } memoryStream.Position = 0; - using var archive = ZipArchive.Open(memoryStream); + using var archive = ZipArchive.OpenArchive(memoryStream); var entries = archive.Entries.ToList(); Assert.Single(entries); @@ -42,7 +42,7 @@ public void ZipWriter_WriteDirectory_WithTrailingSlash() } memoryStream.Position = 0; - using var archive = ZipArchive.Open(memoryStream); + using var archive = ZipArchive.OpenArchive(memoryStream); var entries = archive.Entries.ToList(); Assert.Single(entries); @@ -62,7 +62,7 @@ public void ZipWriter_WriteDirectory_WithBackslash() } memoryStream.Position = 0; - using var archive = ZipArchive.Open(memoryStream); + using var archive = ZipArchive.OpenArchive(memoryStream); var entries = archive.Entries.ToList(); Assert.Single(entries); @@ -82,7 +82,7 @@ public void ZipWriter_WriteDirectory_EmptyString_IsSkipped() } memoryStream.Position = 0; - using var archive = ZipArchive.Open(memoryStream); + using var archive = ZipArchive.OpenArchive(memoryStream); Assert.Empty(archive.Entries); } @@ -101,7 +101,7 @@ public void ZipWriter_WriteDirectory_MultipleDirectories() } memoryStream.Position = 0; - using var archive = ZipArchive.Open(memoryStream); + using var archive = ZipArchive.OpenArchive(memoryStream); var entries = archive.Entries.OrderBy(e => e.Key).ToList(); Assert.Equal(3, entries.Count); @@ -132,7 +132,7 @@ public void ZipWriter_WriteDirectory_MixedWithFiles() } memoryStream.Position = 0; - using var archive = ZipArchive.Open(memoryStream); + using var archive = ZipArchive.OpenArchive(memoryStream); var entries = archive.Entries.OrderBy(e => e.Key).ToList(); Assert.Equal(3, entries.Count); diff --git a/tests/SharpCompress.Test/Zip/ZipWriterTests.cs b/tests/SharpCompress.Test/Zip/ZipWriterTests.cs index 82e7caa2f..70c2632d1 100644 --- a/tests/SharpCompress.Test/Zip/ZipWriterTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipWriterTests.cs @@ -21,7 +21,7 @@ public void Zip_BZip2_Write_EmptyFile() ArchiveEncoding = new ArchiveEncoding { Default = new UTF8Encoding(false) }, }; - using (var writer = WriterFactory.Open(memoryStream, ArchiveType.Zip, options)) + using (var writer = WriterFactory.OpenWriter(memoryStream, ArchiveType.Zip, options)) { writer.Write("test-folder/zero-byte-file.txt", Stream.Null); } @@ -39,7 +39,7 @@ public void Zip_BZip2_Write_EmptyFolder() ArchiveEncoding = new ArchiveEncoding { Default = new UTF8Encoding(false) }, }; - using (var writer = WriterFactory.Open(memoryStream, ArchiveType.Zip, options)) + using (var writer = WriterFactory.OpenWriter(memoryStream, ArchiveType.Zip, options)) { writer.Write("test-empty-folder/", Stream.Null); }