Compare commits

..

5 Commits

Author SHA1 Message Date
Adam Hathcock
158460bc77 fix benchmarks 2026-02-12 13:22:13 +00:00
Adam Hathcock
b5bd4cbf53 fmt 2026-02-12 12:15:00 +00:00
Adam Hathcock
2dfe535e0b add async read and write for files 2026-02-12 12:10:43 +00:00
Adam Hathcock
92c04a9ba4 don't leave created streams open 2026-02-12 11:59:11 +00:00
Adam Hathcock
5f3031db4a Open for writing should be async 2026-02-12 11:53:19 +00:00
26 changed files with 305 additions and 147 deletions

View File

@@ -77,7 +77,9 @@ public static partial class ArchiveFactory
var factory = await FindFactoryAsync<IMultiArchiveFactory>(fileInfo, cancellationToken)
.ConfigureAwait(false);
return factory.OpenAsyncArchive(filesArray, options);
return await factory
.OpenAsyncArchive(filesArray, options, cancellationToken)
.ConfigureAwait(false);
}
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
@@ -106,7 +108,9 @@ public static partial class ArchiveFactory
var factory = await FindFactoryAsync<IMultiArchiveFactory>(firstStream, cancellationToken)
.ConfigureAwait(false);
return factory.OpenAsyncArchive(streamsArray, options);
return await factory
.OpenAsyncArchive(streamsArray, options, cancellationToken)
.ConfigureAwait(false);
}
public static ValueTask<T> FindFactoryAsync<T>(

View File

@@ -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
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions">reading options.</param>
IAsyncArchive OpenAsyncArchive(
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> containing the opened async archive.</returns>
ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> streams,
ReaderOptions? readerOptions = null
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
/// <summary>
@@ -50,8 +54,11 @@ public interface IMultiArchiveFactory : IFactory
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions">reading options.</param>
IAsyncArchive OpenAsyncArchive(
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> containing the opened async archive.</returns>
ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
);
}

View File

@@ -164,12 +164,6 @@ public partial class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, Sev
var folder = entry.FilePart.Folder;
// If folder is null (empty stream entry), return empty stream
if (folder is null)
{
return CreateEntryStream(Stream.Null);
}
// Check if we're starting a new folder - dispose old folder stream if needed
if (folder != _currentFolder)
{

View File

@@ -95,10 +95,17 @@ public class GZipFactory
) => GZipArchive.OpenArchive(streams, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> 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);
}
/// <inheritdoc/>
public IArchive OpenArchive(
@@ -107,10 +114,17 @@ public class GZipFactory
) => GZipArchive.OpenArchive(fileInfos, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> 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
}
/// <inheritdoc/>
public IAsyncWriter OpenAsyncWriter(
public ValueTask<IAsyncWriter> 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

View File

@@ -90,10 +90,17 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
) => RarArchive.OpenArchive(streams, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> 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);
}
/// <inheritdoc/>
public IArchive OpenArchive(
@@ -102,12 +109,16 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
) => RarArchive.OpenArchive(fileInfos, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> 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

View File

@@ -85,10 +85,17 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory
) => SevenZipArchive.OpenArchive(streams, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> 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);
}
/// <inheritdoc/>
public IArchive OpenArchive(
@@ -97,10 +104,17 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory
) => SevenZipArchive.OpenArchive(fileInfos, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> 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

View File

@@ -195,10 +195,17 @@ public class TarFactory
) => TarArchive.OpenArchive(streams, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> 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);
}
/// <inheritdoc/>
public IArchive OpenArchive(
@@ -207,10 +214,17 @@ public class TarFactory
) => TarArchive.OpenArchive(fileInfos, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> 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
}
/// <inheritdoc/>
public IAsyncWriter OpenAsyncWriter(
public ValueTask<IAsyncWriter> 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

View File

@@ -163,10 +163,17 @@ public class ZipFactory
) => ZipArchive.OpenArchive(streams, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<Stream> 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);
}
/// <inheritdoc/>
public IArchive OpenArchive(
@@ -175,10 +182,17 @@ public class ZipFactory
) => ZipArchive.OpenArchive(fileInfos, readerOptions);
/// <inheritdoc/>
public IAsyncArchive OpenAsyncArchive(
public async ValueTask<IAsyncArchive> OpenAsyncArchive(
IReadOnlyList<FileInfo> 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
}
/// <inheritdoc/>
public IAsyncWriter OpenAsyncWriter(
public ValueTask<IAsyncWriter> 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

View File

@@ -206,22 +206,8 @@ internal partial class SharpCompressStream : Stream, IStreamStack
throw new NotSupportedException();
}
public override long Length
{
get
{
if (_isPassthrough)
{
return stream.Length;
}
if (_ringBuffer is not null)
{
return _ringBuffer.Length;
}
throw new NotSupportedException();
}
}
public override long Length =>
_isPassthrough ? stream.Length : throw new NotSupportedException();
public override long Position
{

View File

@@ -35,14 +35,15 @@ public static partial class ReaderFactory
/// <param name="options"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static ValueTask<IAsyncReader> OpenAsyncReader(
public static async ValueTask<IAsyncReader> OpenAsyncReader(
FileInfo fileInfo,
ReaderOptions? options = null,
CancellationToken cancellationToken = default
)
{
options ??= ReaderOptions.ForOwnedFile;
return OpenAsyncReader(fileInfo.OpenRead(), options, cancellationToken);
var stream = fileInfo.OpenAsyncReadStream(cancellationToken);
return await OpenAsyncReader(stream, options, cancellationToken);
}
public static async ValueTask<IAsyncReader> OpenAsyncReader(

View File

@@ -121,14 +121,15 @@ public partial class TarReader
return new TarReader(sharpCompressStream, options, CompressionType.None);
}
public static ValueTask<IAsyncReader> OpenAsyncReader(
public static async ValueTask<IAsyncReader> OpenAsyncReader(
FileInfo fileInfo,
ReaderOptions? readerOptions = null,
CancellationToken cancellationToken = default
)
{
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
return OpenAsyncReader(fileInfo.OpenRead(), readerOptions, cancellationToken);
var stream = fileInfo.OpenAsyncReadStream(cancellationToken);
return await OpenAsyncReader(stream, readerOptions, cancellationToken);
}
public static IReader OpenReader(string filePath, ReaderOptions? readerOptions = null)

View File

@@ -119,4 +119,108 @@ internal static partial class Utility
return (total >= count);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="path">The file path to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
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, //default
FileOptions.Asynchronous
);
#endif
}
/// <summary>
/// Opens a file stream for asynchronous writing from a FileInfo.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="fileInfo">The FileInfo to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncWriteStream(
this FileInfo fileInfo,
CancellationToken cancellationToken
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncWriteStream(fileInfo.FullName, cancellationToken);
}
/// <summary>
/// Opens a file stream for asynchronous reading.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="path">The file path to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncReadStream(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.Open,
FileAccess.Read,
FileShare.Read,
FileOptions.Asynchronous
);
return new FileStream(handle, FileAccess.Read);
#else
// For legacy .NET, use FileStream constructor with async options
return new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
FileOptions.Asynchronous
);
#endif
}
/// <summary>
/// Opens a file stream for asynchronous reading from a FileInfo.
/// Uses File.OpenHandle with FileOptions.Asynchronous on .NET 8.0+ for optimal performance.
/// Falls back to FileStream constructor with async options on legacy frameworks.
/// </summary>
/// <param name="fileInfo">The FileInfo to open.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A FileStream configured for asynchronous operations.</returns>
public static Stream OpenAsyncReadStream(
this FileInfo fileInfo,
CancellationToken cancellationToken
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncReadStream(fileInfo.FullName, cancellationToken);
}
}

View File

@@ -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<IAsyncWriter> OpenAsyncWriter(
Stream stream,
IWriterOptions writerOptions,
CancellationToken cancellationToken = default

View File

@@ -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;
@@ -26,10 +27,14 @@ public static class WriterFactory
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenWriter(fileInfo.OpenWrite(), archiveType, writerOptions);
return OpenWriter(
fileInfo.OpenWrite(),
archiveType,
writerOptions.WithLeaveStreamOpen(false)
);
}
public static IAsyncWriter OpenAsyncWriter(
public static async ValueTask<IAsyncWriter> OpenAsyncWriter(
string filePath,
ArchiveType archiveType,
IWriterOptions writerOptions,
@@ -37,7 +42,7 @@ public static class WriterFactory
)
{
filePath.NotNullOrEmpty(nameof(filePath));
return OpenAsyncWriter(
return await OpenAsyncWriter(
new FileInfo(filePath),
archiveType,
writerOptions,
@@ -45,7 +50,7 @@ public static class WriterFactory
);
}
public static IAsyncWriter OpenAsyncWriter(
public static async ValueTask<IAsyncWriter> OpenAsyncWriter(
FileInfo fileInfo,
ArchiveType archiveType,
IWriterOptions writerOptions,
@@ -53,10 +58,11 @@ public static class WriterFactory
)
{
fileInfo.NotNull(nameof(fileInfo));
return OpenAsyncWriter(
fileInfo.Open(FileMode.Create, FileAccess.Write),
var stream = fileInfo.OpenAsyncWriteStream(cancellationToken);
return await OpenAsyncWriter(
stream,
archiveType,
writerOptions,
writerOptions.WithLeaveStreamOpen(false),
cancellationToken
);
}
@@ -86,8 +92,8 @@ public static class WriterFactory
/// <param name="archiveType">The archive type.</param>
/// <param name="writerOptions">Writer options.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that returns an IWriter.</returns>
public static IAsyncWriter OpenAsyncWriter(
/// <returns>A <see cref="ValueTask{TResult}"/> containing the async writer.</returns>
public static async ValueTask<IAsyncWriter> OpenAsyncWriter(
Stream stream,
ArchiveType archiveType,
IWriterOptions writerOptions,
@@ -100,7 +106,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);

View File

@@ -1,6 +1,9 @@
using System;
using SharpCompress.Common;
using SharpCompress.Common.Options;
using SharpCompress.Writers.GZip;
using SharpCompress.Writers.Tar;
using SharpCompress.Writers.Zip;
namespace SharpCompress.Writers;
@@ -20,6 +23,29 @@ public static class WriterOptionsExtensions
bool leaveStreamOpen
) => options with { LeaveStreamOpen = leaveStreamOpen };
/// <summary>
/// Creates a copy with the specified LeaveStreamOpen value.
/// Works with any IWriterOptions implementation.
/// </summary>
/// <param name="options">The source options.</param>
/// <param name="leaveStreamOpen">Whether to leave the stream open.</param>
/// <returns>A new options instance with the specified LeaveStreamOpen value.</returns>
public static IWriterOptions WithLeaveStreamOpen(
this IWriterOptions options,
bool leaveStreamOpen
) =>
options switch
{
WriterOptions writerOptions => writerOptions with { LeaveStreamOpen = leaveStreamOpen },
ZipWriterOptions zipOptions => zipOptions with { LeaveStreamOpen = leaveStreamOpen },
TarWriterOptions tarOptions => tarOptions with { LeaveStreamOpen = leaveStreamOpen },
GZipWriterOptions gzipOptions => gzipOptions with { LeaveStreamOpen = leaveStreamOpen },
_ => throw new NotSupportedException(
$"Cannot set LeaveStreamOpen on options of type {options.GetType().Name}. "
+ "Options must be a record type implementing IWriterOptions."
),
};
/// <summary>
/// Creates a copy with the specified compression level.
/// </summary>

View File

@@ -216,9 +216,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "sXdDtMf2qcnbygw9OdE535c2lxSxrZP8gO4UhDJ0xiJbl1wIqXS1OTcTDFTIJPOFd6Mhcm8gPEthqWGUxBsTqw=="
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -264,9 +264,9 @@
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.23, )",
"resolved": "8.0.23",
"contentHash": "GqHiB1HbbODWPbY/lc5xLQH8siEEhNA0ptpJCC6X6adtAYNEzu5ZlqV3YHA3Gh7fuEwgA8XqVwMtH2KNtuQM1Q=="
"requested": "[8.0.22, )",
"resolved": "8.0.22",
"contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",

View File

@@ -122,7 +122,7 @@ public class TarBenchmarks : ArchiveBenchmarkBase
public async Task TarCreateSmallFilesAsync()
{
using var outputStream = new MemoryStream();
await using var writer = WriterFactory.OpenAsyncWriter(
await using var writer = await WriterFactory.OpenAsyncWriter(
outputStream,
ArchiveType.Tar,
new WriterOptions(CompressionType.None) { LeaveStreamOpen = true }

View File

@@ -98,7 +98,7 @@ public class ZipBenchmarks : ArchiveBenchmarkBase
public async Task ZipCreateSmallFilesAsync()
{
using var outputStream = new MemoryStream();
await using var writer = WriterFactory.OpenAsyncWriter(
await using var writer = await WriterFactory.OpenAsyncWriter(
outputStream,
ArchiveType.Zip,
new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = true }

View File

@@ -383,7 +383,7 @@ public class ArchiveTests : ReaderTests
return WriterFactory.OpenWriter(stream, ArchiveType.Zip, writerOptions);
}
protected static IAsyncWriter CreateWriterWithLevelAsync(
protected static async ValueTask<IAsyncWriter> 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

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ using System.Linq;
using SharpCompress.Archives;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Common;
using SharpCompress.Common.SevenZip;
using SharpCompress.Factories;
using SharpCompress.Readers;
using Xunit;
@@ -345,53 +344,4 @@ public class SevenZipArchiveTests : ArchiveTests
// The critical check: within a single folder, the stream should NEVER be recreated
Assert.Equal(0, streamRecreationsWithinFolder); // Folder stream should remain the same for all entries in the same folder
}
[Fact]
public void SevenZipArchive_EmptyStream_WriteToDirectory()
{
// This test specifically verifies that archives with empty-stream entries
// (files with size 0 and no compressed data) can be extracted without throwing
// NullReferenceException. This was previously failing because the folder was null
// for empty-stream entries.
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.EmptyStream.7z");
using var archive = SevenZipArchive.OpenArchive(testArchive);
var emptyStreamFileCount = 0;
foreach (var entry in archive.Entries)
{
if (!entry.IsDirectory)
{
// Verify this is actually an empty-stream entry (HasStream == false)
var sevenZipEntry = entry as SevenZipEntry;
if (sevenZipEntry?.FilePart.Header.HasStream == false)
{
emptyStreamFileCount++;
}
// This should not throw NullReferenceException
entry.WriteToDirectory(SCRATCH_FILES_PATH);
}
}
// Ensure we actually tested empty-stream entries
Assert.True(
emptyStreamFileCount > 0,
"Test archive should contain at least one empty-stream entry"
);
// Verify that empty files were created
var extractedFiles = Directory.GetFiles(
SCRATCH_FILES_PATH,
"*",
SearchOption.AllDirectories
);
Assert.NotEmpty(extractedFiles);
// All extracted files should be empty (0 bytes)
foreach (var file in extractedFiles)
{
var fileInfo = new FileInfo(file);
Assert.Equal(0, fileInfo.Length);
}
}
}

View File

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

View File

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

View File

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