mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-12 21:22:26 +00:00
Compare commits
5 Commits
master
...
adam/write
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
158460bc77 | ||
|
|
b5bd4cbf53 | ||
|
|
2dfe535e0b | ||
|
|
92c04a9ba4 | ||
|
|
5f3031db4a |
@@ -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>(
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@ public class ZipEntry : Entry
|
||||
{
|
||||
private readonly ZipFilePart? _filePart;
|
||||
|
||||
// WinZip AES extra data constants
|
||||
private const int MinimumWinZipAesExtraDataLength = 7;
|
||||
private const int WinZipAesCompressionMethodOffset = 5;
|
||||
|
||||
internal ZipEntry(ZipFilePart? filePart, IReaderOptions readerOptions)
|
||||
: base(readerOptions)
|
||||
{
|
||||
@@ -37,54 +33,24 @@ public class ZipEntry : Entry
|
||||
CreatedTime = times?.UnicodeTimes.Item3;
|
||||
}
|
||||
|
||||
public override CompressionType CompressionType
|
||||
{
|
||||
get
|
||||
public override CompressionType CompressionType =>
|
||||
_filePart?.Header.CompressionMethod switch
|
||||
{
|
||||
var compressionMethod = GetActualCompressionMethod();
|
||||
return compressionMethod switch
|
||||
{
|
||||
ZipCompressionMethod.BZip2 => CompressionType.BZip2,
|
||||
ZipCompressionMethod.Deflate => CompressionType.Deflate,
|
||||
ZipCompressionMethod.Deflate64 => CompressionType.Deflate64,
|
||||
ZipCompressionMethod.LZMA => CompressionType.LZMA,
|
||||
ZipCompressionMethod.PPMd => CompressionType.PPMd,
|
||||
ZipCompressionMethod.None => CompressionType.None,
|
||||
ZipCompressionMethod.Shrink => CompressionType.Shrink,
|
||||
ZipCompressionMethod.Reduce1 => CompressionType.Reduce1,
|
||||
ZipCompressionMethod.Reduce2 => CompressionType.Reduce2,
|
||||
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
|
||||
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
|
||||
ZipCompressionMethod.Explode => CompressionType.Explode,
|
||||
ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
|
||||
_ => CompressionType.Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private ZipCompressionMethod GetActualCompressionMethod()
|
||||
{
|
||||
if (_filePart?.Header.CompressionMethod != ZipCompressionMethod.WinzipAes)
|
||||
{
|
||||
return _filePart?.Header.CompressionMethod ?? ZipCompressionMethod.None;
|
||||
}
|
||||
|
||||
// For WinZip AES, the actual compression method is stored in the extra data
|
||||
var aesExtraData = _filePart.Header.Extra.FirstOrDefault(x =>
|
||||
x.Type == ExtraDataType.WinZipAes
|
||||
);
|
||||
|
||||
if (aesExtraData is null || aesExtraData.DataBytes.Length < MinimumWinZipAesExtraDataLength)
|
||||
{
|
||||
return ZipCompressionMethod.WinzipAes;
|
||||
}
|
||||
|
||||
// The compression method is at offset 5 in the extra data
|
||||
return (ZipCompressionMethod)
|
||||
System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(
|
||||
aesExtraData.DataBytes.AsSpan(WinZipAesCompressionMethodOffset)
|
||||
);
|
||||
}
|
||||
ZipCompressionMethod.BZip2 => CompressionType.BZip2,
|
||||
ZipCompressionMethod.Deflate => CompressionType.Deflate,
|
||||
ZipCompressionMethod.Deflate64 => CompressionType.Deflate64,
|
||||
ZipCompressionMethod.LZMA => CompressionType.LZMA,
|
||||
ZipCompressionMethod.PPMd => CompressionType.PPMd,
|
||||
ZipCompressionMethod.None => CompressionType.None,
|
||||
ZipCompressionMethod.Shrink => CompressionType.Shrink,
|
||||
ZipCompressionMethod.Reduce1 => CompressionType.Reduce1,
|
||||
ZipCompressionMethod.Reduce2 => CompressionType.Reduce2,
|
||||
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
|
||||
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
|
||||
ZipCompressionMethod.Explode => CompressionType.Explode,
|
||||
ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
|
||||
_ => CompressionType.Unknown,
|
||||
};
|
||||
|
||||
public override long Crc => _filePart?.Header.Crc ?? 0;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
"*",
|
||||
|
||||
@@ -491,56 +491,6 @@ public class ZipArchiveTests : ArchiveTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Zip_WinzipAES_CompressionType()
|
||||
{
|
||||
// Test that WinZip AES encrypted entries correctly report their compression type
|
||||
using var deflateArchive = ZipArchive.OpenArchive(
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.WinzipAES.zip"),
|
||||
new ReaderOptions { Password = "test" }
|
||||
);
|
||||
foreach (var entry in deflateArchive.Entries.Where(x => !x.IsDirectory))
|
||||
{
|
||||
Assert.True(entry.IsEncrypted);
|
||||
Assert.Equal(CompressionType.Deflate, entry.CompressionType);
|
||||
}
|
||||
|
||||
using var lzmaArchive = ZipArchive.OpenArchive(
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Zip.lzma.WinzipAES.zip"),
|
||||
new ReaderOptions { Password = "test" }
|
||||
);
|
||||
foreach (var entry in lzmaArchive.Entries.Where(x => !x.IsDirectory))
|
||||
{
|
||||
Assert.True(entry.IsEncrypted);
|
||||
Assert.Equal(CompressionType.LZMA, entry.CompressionType);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Zip_Pkware_CompressionType()
|
||||
{
|
||||
// Test that Pkware encrypted entries correctly report their compression type
|
||||
using var deflateArchive = ZipArchive.OpenArchive(
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Zip.deflate.pkware.zip"),
|
||||
new ReaderOptions { Password = "test" }
|
||||
);
|
||||
foreach (var entry in deflateArchive.Entries.Where(x => !x.IsDirectory))
|
||||
{
|
||||
Assert.True(entry.IsEncrypted);
|
||||
Assert.Equal(CompressionType.Deflate, entry.CompressionType);
|
||||
}
|
||||
|
||||
using var bzip2Archive = ZipArchive.OpenArchive(
|
||||
Path.Combine(TEST_ARCHIVES_PATH, "Zip.bzip2.pkware.zip"),
|
||||
new ReaderOptions { Password = "test" }
|
||||
);
|
||||
foreach (var entry in bzip2Archive.Entries.Where(x => !x.IsDirectory))
|
||||
{
|
||||
Assert.True(entry.IsEncrypted);
|
||||
Assert.Equal(CompressionType.BZip2, entry.CompressionType);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Zip_Read_Volume_Comment()
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -224,7 +224,7 @@ public class ZipReaderAsyncTests : ReaderTests
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
Assert.Equal(CompressionType.LZMA, reader.Entry.CompressionType);
|
||||
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
|
||||
await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH);
|
||||
}
|
||||
}
|
||||
@@ -252,7 +252,7 @@ public class ZipReaderAsyncTests : ReaderTests
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
Assert.Equal(CompressionType.Deflate, reader.Entry.CompressionType);
|
||||
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
|
||||
await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public class ZipReaderTests : ReaderTests
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
Assert.Equal(CompressionType.LZMA, reader.Entry.CompressionType);
|
||||
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
|
||||
reader.WriteEntryToDirectory(SCRATCH_FILES_PATH);
|
||||
}
|
||||
}
|
||||
@@ -223,7 +223,7 @@ public class ZipReaderTests : ReaderTests
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
Assert.Equal(CompressionType.Deflate, reader.Entry.CompressionType);
|
||||
Assert.Equal(CompressionType.Unknown, reader.Entry.CompressionType);
|
||||
reader.WriteEntryToDirectory(SCRATCH_FILES_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user