diff --git a/src/SharpCompress/Archives/ArchiveFactory.Async.cs b/src/SharpCompress/Archives/ArchiveFactory.Async.cs index 4af94a57..04f009cb 100644 --- a/src/SharpCompress/Archives/ArchiveFactory.Async.cs +++ b/src/SharpCompress/Archives/ArchiveFactory.Async.cs @@ -77,7 +77,9 @@ public static partial class ArchiveFactory var factory = await FindFactoryAsync(fileInfo, cancellationToken) .ConfigureAwait(false); - return factory.OpenAsyncArchive(filesArray, options); + return await factory + .OpenAsyncArchive(filesArray, options, cancellationToken) + .ConfigureAwait(false); } public static async ValueTask OpenAsyncArchive( @@ -106,7 +108,9 @@ public static partial class ArchiveFactory var factory = await FindFactoryAsync(firstStream, cancellationToken) .ConfigureAwait(false); - return factory.OpenAsyncArchive(streamsArray, options); + return await factory + .OpenAsyncArchive(streamsArray, options, cancellationToken) + .ConfigureAwait(false); } public static ValueTask FindFactoryAsync( diff --git a/src/SharpCompress/Archives/IMultiArchiveFactory.cs b/src/SharpCompress/Archives/IMultiArchiveFactory.cs index 936222e6..3f9e0ec1 100644 --- a/src/SharpCompress/Archives/IMultiArchiveFactory.cs +++ b/src/SharpCompress/Archives/IMultiArchiveFactory.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Tasks; using SharpCompress.Factories; using SharpCompress.Readers; @@ -33,9 +34,12 @@ public interface IMultiArchiveFactory : IFactory /// /// /// reading options. - IAsyncArchive OpenAsyncArchive( + /// Cancellation token. + /// A containing the opened async archive. + ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); /// @@ -50,8 +54,11 @@ public interface IMultiArchiveFactory : IFactory /// /// /// reading options. - IAsyncArchive OpenAsyncArchive( + /// Cancellation token. + /// A containing the opened async archive. + ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ); } diff --git a/src/SharpCompress/Factories/GZipFactory.cs b/src/SharpCompress/Factories/GZipFactory.cs index ab40438c..447e15f0 100644 --- a/src/SharpCompress/Factories/GZipFactory.cs +++ b/src/SharpCompress/Factories/GZipFactory.cs @@ -95,10 +95,17 @@ public class GZipFactory ) => GZipArchive.OpenArchive(streams, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await GZipArchive + .OpenAsyncArchive(streams, readerOptions, cancellationToken) + .ConfigureAwait(false); + } /// public IArchive OpenArchive( @@ -107,10 +114,17 @@ public class GZipFactory ) => GZipArchive.OpenArchive(fileInfos, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await GZipArchive + .OpenAsyncArchive(fileInfos, readerOptions, cancellationToken) + .ConfigureAwait(false); + } #endregion @@ -187,14 +201,15 @@ public class GZipFactory } /// - public IAsyncWriter OpenAsyncWriter( + public ValueTask OpenAsyncWriter( Stream stream, IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncWriter)OpenWriter(stream, writerOptions); + var writer = OpenWriter(stream, writerOptions); + return new((IAsyncWriter)writer); } #endregion diff --git a/src/SharpCompress/Factories/RarFactory.cs b/src/SharpCompress/Factories/RarFactory.cs index c81e54a7..615f7fb4 100644 --- a/src/SharpCompress/Factories/RarFactory.cs +++ b/src/SharpCompress/Factories/RarFactory.cs @@ -90,10 +90,17 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade ) => RarArchive.OpenArchive(streams, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await RarArchive + .OpenAsyncArchive(streams, readerOptions, cancellationToken) + .ConfigureAwait(false); + } /// public IArchive OpenArchive( @@ -102,12 +109,16 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade ) => RarArchive.OpenArchive(fileInfos, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default ) { - return (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + cancellationToken.ThrowIfCancellationRequested(); + return await RarArchive + .OpenAsyncArchive(fileInfos, readerOptions, cancellationToken) + .ConfigureAwait(false); } #endregion diff --git a/src/SharpCompress/Factories/SevenZipFactory.cs b/src/SharpCompress/Factories/SevenZipFactory.cs index 93826468..e4942041 100644 --- a/src/SharpCompress/Factories/SevenZipFactory.cs +++ b/src/SharpCompress/Factories/SevenZipFactory.cs @@ -85,10 +85,17 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory ) => SevenZipArchive.OpenArchive(streams, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await SevenZipArchive + .OpenAsyncArchive(streams, readerOptions, cancellationToken) + .ConfigureAwait(false); + } /// public IArchive OpenArchive( @@ -97,10 +104,17 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory ) => SevenZipArchive.OpenArchive(fileInfos, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await SevenZipArchive + .OpenAsyncArchive(fileInfos, readerOptions, cancellationToken) + .ConfigureAwait(false); + } #endregion diff --git a/src/SharpCompress/Factories/TarFactory.cs b/src/SharpCompress/Factories/TarFactory.cs index fd2f528f..00ba6be2 100644 --- a/src/SharpCompress/Factories/TarFactory.cs +++ b/src/SharpCompress/Factories/TarFactory.cs @@ -195,10 +195,17 @@ public class TarFactory ) => TarArchive.OpenArchive(streams, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await TarArchive + .OpenAsyncArchive(streams, readerOptions, cancellationToken) + .ConfigureAwait(false); + } /// public IArchive OpenArchive( @@ -207,10 +214,17 @@ public class TarFactory ) => TarArchive.OpenArchive(fileInfos, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await TarArchive + .OpenAsyncArchive(fileInfos, readerOptions, cancellationToken) + .ConfigureAwait(false); + } #endregion @@ -298,14 +312,15 @@ public class TarFactory } /// - public IAsyncWriter OpenAsyncWriter( + public ValueTask OpenAsyncWriter( Stream stream, IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncWriter)OpenWriter(stream, writerOptions); + var writer = OpenWriter(stream, writerOptions); + return new((IAsyncWriter)writer); } #endregion diff --git a/src/SharpCompress/Factories/ZipFactory.cs b/src/SharpCompress/Factories/ZipFactory.cs index 6c683a38..057bfb65 100644 --- a/src/SharpCompress/Factories/ZipFactory.cs +++ b/src/SharpCompress/Factories/ZipFactory.cs @@ -163,10 +163,17 @@ public class ZipFactory ) => ZipArchive.OpenArchive(streams, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList streams, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(streams, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await ZipArchive + .OpenAsyncArchive(streams, readerOptions, cancellationToken) + .ConfigureAwait(false); + } /// public IArchive OpenArchive( @@ -175,10 +182,17 @@ public class ZipFactory ) => ZipArchive.OpenArchive(fileInfos, readerOptions); /// - public IAsyncArchive OpenAsyncArchive( + public async ValueTask OpenAsyncArchive( IReadOnlyList fileInfos, - ReaderOptions? readerOptions = null - ) => (IAsyncArchive)OpenArchive(fileInfos, readerOptions); + ReaderOptions? readerOptions = null, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return await ZipArchive + .OpenAsyncArchive(fileInfos, readerOptions, cancellationToken) + .ConfigureAwait(false); + } #endregion @@ -219,14 +233,15 @@ public class ZipFactory } /// - public IAsyncWriter OpenAsyncWriter( + public ValueTask OpenAsyncWriter( Stream stream, IWriterOptions writerOptions, CancellationToken cancellationToken = default ) { cancellationToken.ThrowIfCancellationRequested(); - return (IAsyncWriter)OpenWriter(stream, writerOptions); + var writer = OpenWriter(stream, writerOptions); + return new((IAsyncWriter)writer); } #endregion diff --git a/src/SharpCompress/Utility.Async.cs b/src/SharpCompress/Utility.Async.cs index e8e6b0c3..bc90ac96 100644 --- a/src/SharpCompress/Utility.Async.cs +++ b/src/SharpCompress/Utility.Async.cs @@ -119,4 +119,42 @@ internal static partial class Utility return (total >= count); } } + + /// + /// Opens a file stream for asynchronous writing. + /// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance. + /// Falls back to FileStream constructor with async options on legacy frameworks. + /// + /// The file path to open. + /// Cancellation token. + /// A FileStream configured for asynchronous operations. + public static Stream OpenAsyncWriteStream( + string path, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + +#if !LEGACY_DOTNET + // Use File.OpenHandle with async options for .NET 8.0+ + var handle = File.OpenHandle( + path, + FileMode.Create, + FileAccess.Write, + FileShare.None, + FileOptions.Asynchronous + ); + return new FileStream(handle, FileAccess.Write); +#else + // For legacy .NET, use FileStream constructor with async options + return new FileStream( + path, + FileMode.Create, + FileAccess.Write, + FileShare.None, + bufferSize: 4096, + FileOptions.Asynchronous + ); +#endif + } } diff --git a/src/SharpCompress/Writers/IWriterFactory.cs b/src/SharpCompress/Writers/IWriterFactory.cs index fc2ef301..c9eef005 100644 --- a/src/SharpCompress/Writers/IWriterFactory.cs +++ b/src/SharpCompress/Writers/IWriterFactory.cs @@ -1,5 +1,6 @@ using System.IO; using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common.Options; using SharpCompress.Factories; @@ -9,7 +10,7 @@ public interface IWriterFactory : IFactory { IWriter OpenWriter(Stream stream, IWriterOptions writerOptions); - IAsyncWriter OpenAsyncWriter( + ValueTask OpenAsyncWriter( Stream stream, IWriterOptions writerOptions, CancellationToken cancellationToken = default diff --git a/src/SharpCompress/Writers/WriterFactory.cs b/src/SharpCompress/Writers/WriterFactory.cs index 1d392797..f923df51 100644 --- a/src/SharpCompress/Writers/WriterFactory.cs +++ b/src/SharpCompress/Writers/WriterFactory.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Common.Options; @@ -29,7 +30,7 @@ public static class WriterFactory return OpenWriter(fileInfo.OpenWrite(), archiveType, writerOptions); } - public static IAsyncWriter OpenAsyncWriter( + public static async ValueTask OpenAsyncWriter( string filePath, ArchiveType archiveType, IWriterOptions writerOptions, @@ -37,7 +38,7 @@ public static class WriterFactory ) { filePath.NotNullOrEmpty(nameof(filePath)); - return OpenAsyncWriter( + return await OpenAsyncWriter( new FileInfo(filePath), archiveType, writerOptions, @@ -45,7 +46,7 @@ public static class WriterFactory ); } - public static IAsyncWriter OpenAsyncWriter( + public static async ValueTask OpenAsyncWriter( FileInfo fileInfo, ArchiveType archiveType, IWriterOptions writerOptions, @@ -53,12 +54,8 @@ public static class WriterFactory ) { fileInfo.NotNull(nameof(fileInfo)); - return OpenAsyncWriter( - fileInfo.Open(FileMode.Create, FileAccess.Write), - archiveType, - writerOptions, - cancellationToken - ); + var stream = Utility.OpenAsyncWriteStream(fileInfo.FullName, cancellationToken); + return await OpenAsyncWriter(stream, archiveType, writerOptions, cancellationToken); } public static IWriter OpenWriter( @@ -86,8 +83,8 @@ public static class WriterFactory /// The archive type. /// Writer options. /// Cancellation token. - /// A task that returns an IWriter. - public static IAsyncWriter OpenAsyncWriter( + /// A containing the async writer. + public static async ValueTask OpenAsyncWriter( Stream stream, ArchiveType archiveType, IWriterOptions writerOptions, @@ -100,7 +97,7 @@ public static class WriterFactory if (factory != null) { - return factory.OpenAsyncWriter(stream, writerOptions, cancellationToken); + return await factory.OpenAsyncWriter(stream, writerOptions, cancellationToken); } throw new NotSupportedException("Archive Type does not have a Writer: " + archiveType); diff --git a/tests/SharpCompress.Test/ArchiveTests.cs b/tests/SharpCompress.Test/ArchiveTests.cs index 0d98e805..270900b9 100644 --- a/tests/SharpCompress.Test/ArchiveTests.cs +++ b/tests/SharpCompress.Test/ArchiveTests.cs @@ -383,7 +383,7 @@ public class ArchiveTests : ReaderTests return WriterFactory.OpenWriter(stream, ArchiveType.Zip, writerOptions); } - protected static IAsyncWriter CreateWriterWithLevelAsync( + protected static async ValueTask CreateWriterWithLevelAsync( Stream stream, CompressionType compressionType, int? compressionLevel = null @@ -392,7 +392,7 @@ public class ArchiveTests : ReaderTests var writerOptions = compressionLevel.HasValue ? new WriterOptions(compressionType, compressionLevel.Value) { LeaveStreamOpen = true } : new WriterOptions(compressionType) { LeaveStreamOpen = true }; - return WriterFactory.OpenAsyncWriter( + return await WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.Zip, writerOptions diff --git a/tests/SharpCompress.Test/GZip/AsyncTests.cs b/tests/SharpCompress.Test/GZip/AsyncTests.cs index 8c0d19e1..cf334ef9 100644 --- a/tests/SharpCompress.Test/GZip/AsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/AsyncTests.cs @@ -104,7 +104,7 @@ public class AsyncTests : TestBase await using (var stream = File.Create(outputPath)) #endif using ( - var writer = WriterFactory.OpenAsyncWriter( + var writer = await WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.Zip, new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = false } diff --git a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs index 60e115c0..a5d81899 100644 --- a/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs @@ -24,7 +24,7 @@ public class GZipWriterAsyncTests : WriterTests ) ) using ( - var writer = WriterFactory.OpenAsyncWriter( + var writer = await WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.GZip, new WriterOptions(CompressionType.GZip) diff --git a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs index 194a07d8..84d8ca40 100644 --- a/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs @@ -35,7 +35,7 @@ public class TarArchiveAsyncTests : ArchiveTests using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) { using ( - var writer = WriterFactory.OpenAsyncWriter( + var writer = await WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.Tar, new WriterOptions(CompressionType.None) { LeaveStreamOpen = false } @@ -94,7 +94,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.OpenAsyncWriter( + var writer = await WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(stream), ArchiveType.Tar, new WriterOptions(CompressionType.None) { LeaveStreamOpen = false } diff --git a/tests/SharpCompress.Test/WriterTests.cs b/tests/SharpCompress.Test/WriterTests.cs index 786cc41d..5fc18c6c 100644 --- a/tests/SharpCompress.Test/WriterTests.cs +++ b/tests/SharpCompress.Test/WriterTests.cs @@ -70,7 +70,7 @@ public class WriterTests : TestBase writerOptions.ArchiveEncoding.Default = encoding ?? Encoding.Default; - using var writer = WriterFactory.OpenAsyncWriter(stream, _type, writerOptions); + using var writer = await WriterFactory.OpenAsyncWriter(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 c57d5026..52a0d98d 100644 --- a/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs +++ b/tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs @@ -62,7 +62,11 @@ public class ZipTypesLevelsWithCrcRatioAsyncTests : ArchiveTests // Create zip archive in memory using var zipStream = new MemoryStream(); using ( - var writer = CreateWriterWithLevelAsync(zipStream, compressionType, compressionLevel) + var writer = await CreateWriterWithLevelAsync( + zipStream, + compressionType, + compressionLevel + ) ) { await writer.WriteAsync($"file1_{sizeMb}MiB.txt", new MemoryStream(file1Data)); @@ -133,7 +137,7 @@ public class ZipTypesLevelsWithCrcRatioAsyncTests : ArchiveTests }; using ( - var writer = WriterFactory.OpenAsyncWriter( + var writer = await WriterFactory.OpenAsyncWriter( new AsyncOnlyStream(zipStream), ArchiveType.Zip, writerOptions @@ -201,7 +205,11 @@ public class ZipTypesLevelsWithCrcRatioAsyncTests : ArchiveTests // Create archive with specified compression and level using var zipStream = new MemoryStream(); using ( - var writer = CreateWriterWithLevelAsync(zipStream, compressionType, compressionLevel) + var writer = await CreateWriterWithLevelAsync( + zipStream, + compressionType, + compressionLevel + ) ) { await writer.WriteAsync(