From 9cf2b3129cf5c040f755b1f35e9c060a612805d2 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 13 Jan 2026 13:54:15 +0000 Subject: [PATCH] fixed up async writer --- src/SharpCompress/Factories/GZipFactory.cs | 4 +- src/SharpCompress/Factories/TarFactory.cs | 4 +- src/SharpCompress/Factories/ZipFactory.cs | 4 +- src/SharpCompress/Writers/AbstractWriter.cs | 2 +- src/SharpCompress/Writers/IWriter.cs | 7 +- .../Writers/IWriterExtensions.cs | 212 +++++++++--------- src/SharpCompress/Writers/IWriterFactory.cs | 2 +- src/SharpCompress/Writers/WriterFactory.cs | 2 +- tests/SharpCompress.Test/ArchiveTests.cs | 14 ++ .../GZip/GZipWriterAsyncTests.cs | 2 +- .../Tar/TarArchiveAsyncTests.cs | 4 +- tests/SharpCompress.Test/WriterTests.cs | 2 +- .../Zip/ZipMemoryArchiveWithCrcAsyncTests.cs | 6 +- 13 files changed, 138 insertions(+), 127 deletions(-) diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index f8eada3f..f222fc31 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -182,7 +182,7 @@ public class GZipFactory } /// - public IWriter OpenAsync( + public IAsyncWriter OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default @@ -193,7 +193,7 @@ public class GZipFactory { throw new InvalidFormatException("GZip archives only support GZip compression type."); } - return Open(stream, writerOptions); + return (IAsyncWriter)Open(stream, writerOptions); } #endregion diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index 85832bab..a664b243 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -294,14 +294,14 @@ public class TarFactory new TarWriter(stream, new TarWriterOptions(writerOptions)); /// - public IWriter OpenAsync( + public IAsyncWriter OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return Open(stream, writerOptions); + return (IAsyncWriter)Open(stream, writerOptions); } #endregion diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index d0774a74..bdf8fedd 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -218,14 +218,14 @@ public class ZipFactory new ZipWriter(stream, new ZipWriterOptions(writerOptions)); /// - public IWriter OpenAsync( + public IAsyncWriter OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return Open(stream, writerOptions); + return (IAsyncWriter)Open(stream, writerOptions); } #endregion diff --git a/src/SharpCompress/Writers/AbstractWriter.cs b/src/SharpCompress/Writers/AbstractWriter.cs index 7dce6297..fec2f56f 100644 --- a/src/SharpCompress/Writers/AbstractWriter.cs +++ b/src/SharpCompress/Writers/AbstractWriter.cs @@ -8,7 +8,7 @@ using SharpCompress.IO; namespace SharpCompress.Writers; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. -public abstract 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/IWriter.cs b/src/SharpCompress/Writers/IWriter.cs index b51b4972..c33a3bf0 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 9b8fb67b..e2c66e8e 100644 --- a/src/SharpCompress/Writers/IWriterExtensions.cs +++ b/src/SharpCompress/Writers/IWriterExtensions.cs @@ -8,126 +8,118 @@ 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) + public void Write(string entryPath, Stream source) => + writer.Write(entryPath, source, null); + + public void Write(string entryPath, FileInfo source) { - throw new ArgumentException("Source does not exist: " + source.FullName); - } - using var stream = source.OpenRead(); - writer.Write(entryPath, stream, source.LastWriteTime); - } + if (!source.Exists) + { + throw new ArgumentException("Source does not exist: " + source.FullName); + } - 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)) - { - throw new ArgumentException("Directory does not exist: " + directory); + 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); + } + + fileSearchFunc ??= n => true; + foreach ( + var file in Directory + .EnumerateFiles(directory, searchPattern, option) + .Where(fileSearchFunc) + ) + { + writer.Write(file.Substring(directory.Length), file); + } } + + public void WriteDirectory(string directoryName) => + writer.WriteDirectory(directoryName, null); } - public static void WriteDirectory(this IWriter writer, string directoryName) => - writer.WriteDirectory(directoryName, null); + extension(IAsyncWriter writer) + { + public ValueTask WriteAsync(string entryPath, + Stream source, + CancellationToken cancellationToken = default + ) => writer.WriteAsync(entryPath, source, null, cancellationToken); + + 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(entryPath, stream, source.LastWriteTime, cancellationToken) + .ConfigureAwait(false); + } + + 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); + } // 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); - } - using var stream = source.OpenRead(); - await writer - .WriteAsync(entryPath, stream, source.LastWriteTime, cancellationToken) - .ConfigureAwait(false); - } - - 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 - ) - { - 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 static ValueTask WriteDirectoryAsync( - this IWriter writer, - 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 2c68c249..9615f078 100644 --- a/src/SharpCompress/Writers/IWriterFactory.cs +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -8,7 +8,7 @@ public interface IWriterFactory : IFactory { IWriter Open(Stream stream, WriterOptions writerOptions); - IWriter OpenAsync( + IAsyncWriter OpenAsync( Stream stream, WriterOptions writerOptions, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Writers/WriterFactory.cs b/src/SharpCompress/Writers/WriterFactory.cs index 52b62e7d..be2a7780 100644 --- a/src/SharpCompress/Writers/WriterFactory.cs +++ b/src/SharpCompress/Writers/WriterFactory.cs @@ -31,7 +31,7 @@ public static class WriterFactory /// Writer options. /// Cancellation token. /// A task that returns an IWriter. - public static IWriter OpenAsync( + public static IAsyncWriter OpenAsync( Stream stream, ArchiveType archiveType, WriterOptions writerOptions, diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 5853e82c..ee291145 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -380,6 +380,20 @@ public class ArchiveTests : ReaderTests return WriterFactory.Open(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.OpenAsync(stream, ArchiveType.Zip, writerOptions); + } + /// /// Verifies archive content against expected files with CRC32 validation /// diff --git a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs index 3ada3c52..ff246dc2 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs @@ -22,7 +22,7 @@ public class GZipWriterAsyncTests : WriterTests FileAccess.Write ) ) - using (var writer = WriterFactory.Open(stream, ArchiveType.GZip, CompressionType.GZip)) + using (var writer = WriterFactory.OpenAsync(stream, ArchiveType.GZip, CompressionType.GZip)) { await writer.WriteAsync("Tar.tar", Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); } diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index d140c788..f29d4553 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -32,7 +32,7 @@ public class TarArchiveAsyncTests : ArchiveTests // 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.OpenAsync(stream, ArchiveType.Tar, CompressionType.None)) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); @@ -76,7 +76,7 @@ public class TarArchiveAsyncTests : ArchiveTests // 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.OpenAsync(stream, ArchiveType.Tar, CompressionType.None)) using (Stream inputStream = new MemoryStream()) { var sw = new StreamWriter(inputStream); diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index 9a6a43de..95d82da3 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -73,7 +73,7 @@ public class WriterTests : TestBase writerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using var writer = WriterFactory.Open(stream, _type, writerOptions); + using var writer = WriterFactory.OpenAsync(stream, _type, writerOptions); await writer.WriteAllAsync( ORIGINAL_FILES_PATH, "*", diff --git a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs index c04c9e93..e7c80a8e 100644 --- a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs @@ -60,7 +60,7 @@ public class ZipTypesLevelsWithCrcRatioAsyncTests : ArchiveTests // 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 +129,7 @@ public class ZipTypesLevelsWithCrcRatioAsyncTests : ArchiveTests CompressionLevel = compressionLevel, }; - using (var writer = WriterFactory.Open(zipStream, ArchiveType.Zip, writerOptions)) + using (var writer = WriterFactory.OpenAsync(zipStream, ArchiveType.Zip, writerOptions)) { await writer.WriteAsync( $"{compressionType}_level_{compressionLevel}_{sizeMb}MiB.txt", @@ -191,7 +191,7 @@ public class ZipTypesLevelsWithCrcRatioAsyncTests : ArchiveTests // 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",