mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-24 16:05:27 +00:00
Merge remote-tracking branch 'origin/master' into adam/write-async
# Conflicts: # src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs # src/SharpCompress/Factories/TarFactory.cs # src/SharpCompress/Polyfills/StreamExtensions.cs # src/SharpCompress/Writers/IWriter.cs
This commit is contained in:
@@ -40,7 +40,7 @@ public abstract partial class AbstractArchive<TEntry, TVolume> : IArchive, IAsyn
|
||||
internal AbstractArchive(ArchiveType type)
|
||||
{
|
||||
Type = type;
|
||||
ReaderOptions = new();
|
||||
ReaderOptions = ReaderOptions.Default;
|
||||
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
|
||||
_lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
|
||||
_lazyVolumesAsync = new LazyAsyncReadOnlyCollection<TVolume>(
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
@@ -34,7 +31,11 @@ public static partial class ArchiveFactory
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncArchive(new FileInfo(filePath), options, cancellationToken);
|
||||
return OpenAsyncArchive(
|
||||
new FileInfo(filePath),
|
||||
options ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
@@ -53,20 +54,20 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var filesArray = fileInfos.ToArray();
|
||||
if (filesArray.Length == 0)
|
||||
var filesArray = fileInfos;
|
||||
if (filesArray.Count == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No files to open");
|
||||
}
|
||||
|
||||
var fileInfo = filesArray[0];
|
||||
if (filesArray.Length == 1)
|
||||
if (filesArray.Count == 1)
|
||||
{
|
||||
return await OpenAsyncArchive(fileInfo, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -83,21 +84,15 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? options = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
streams.NotNull(nameof(streams));
|
||||
var streamsArray = streams.ToArray();
|
||||
if (streamsArray.Length == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No streams");
|
||||
}
|
||||
|
||||
var streamsArray = streams.RequireReadable().RequireSeekable().ToList();
|
||||
var firstStream = streamsArray[0];
|
||||
if (streamsArray.Length == 1)
|
||||
if (streamsArray.Count == 1)
|
||||
{
|
||||
return await OpenAsyncArchive(firstStream, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -112,64 +107,4 @@ public static partial class ArchiveFactory
|
||||
.OpenAsyncArchive(streamsArray, options, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static ValueTask<T> FindFactoryAsync<T>(
|
||||
string path,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return FindFactoryAsync<T>(new FileInfo(path), cancellationToken);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo finfo,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
finfo.NotNull(nameof(finfo));
|
||||
using Stream stream = finfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<T> FindFactoryAsync<T>(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
}
|
||||
|
||||
var factories = Factory.Factories.OfType<T>();
|
||||
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
if (
|
||||
await factory
|
||||
.IsArchiveAsync(stream, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
var extensions = string.Join(", ", factories.Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
264
src/SharpCompress/Archives/ArchiveFactory.Detection.cs
Normal file
264
src/SharpCompress/Archives/ArchiveFactory.Detection.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
public static partial class ArchiveFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path asynchronously,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await GetArchiveInformationAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path asynchronously,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return await GetArchiveInformationAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream asynchronously,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await GetArchiveInformationAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream asynchronously,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async ValueTask<ArchiveInformation?> GetArchiveInformationAsync(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = await TryFindFactoryAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return factory is null
|
||||
? null
|
||||
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
|
||||
}
|
||||
|
||||
internal static ValueTask<T> FindFactoryAsync<T>(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return FindFactoryAsync<T>(new FileInfo(filePath), cancellationToken);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
FileInfo fileInfo,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return await FindFactoryAsync<T>(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async ValueTask<T> FindFactoryAsync<T>(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
// Use the shared async detection loop over all factories. If the matched factory
|
||||
// implements T we return it; otherwise (or if nothing matched) we fall through
|
||||
// to the same "unsupported format" exception that the original code produced,
|
||||
// listing the T-typed factories as the hint for the caller.
|
||||
var factory = await TryFindFactoryAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
if (factory is T typedFactory)
|
||||
{
|
||||
return typedFactory;
|
||||
}
|
||||
|
||||
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async counterpart of <see cref="ArchiveFactory.TryFindFactory"/>.
|
||||
/// Iterates all registered factories and returns the first one whose
|
||||
/// <see cref="IFactory.IsArchiveAsync"/> recognises the stream, or <see langword="null"/>.
|
||||
/// Stream position is restored to its value at entry on both success and failure.
|
||||
/// </summary>
|
||||
private static async ValueTask<IFactory?> TryFindFactoryAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken
|
||||
) =>
|
||||
await TryFindFactoryAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
private static async ValueTask<IFactory?> TryFindFactoryAsync(
|
||||
Stream stream,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
var isArchive = await factory
|
||||
.IsArchiveAsync(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(string filePath) =>
|
||||
GetArchiveInformation(filePath, ReaderOptions.ForFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive at the given file path,
|
||||
/// or <see langword="null"/> if the file is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the archive file.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return GetArchiveInformation(stream, readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(Stream stream) =>
|
||||
GetArchiveInformation(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the archive in the given stream,
|
||||
/// or <see langword="null"/> if the stream is not a recognized archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">A readable and seekable stream positioned at the start of the archive.</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
public static ArchiveInformation? GetArchiveInformation(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions
|
||||
)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
return factory is null
|
||||
? null
|
||||
: new ArchiveInformation(factory.KnownArchiveType, factory is IArchiveFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates all registered factories and returns the first one whose
|
||||
/// <see cref="IFactory.IsArchive"/> recognises the stream, or <see langword="null"/>.
|
||||
/// Stream position is restored to its value at entry on both success and failure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the shared, seekable-stream detection core used by
|
||||
/// <see cref="FindFactory{T}(Stream)"/>, <see cref="IsArchive(Stream, out ArchiveType?)"/>,
|
||||
/// and <see cref="GetArchiveInformation(Stream)"/>.
|
||||
/// <para>
|
||||
/// <see cref="ReaderFactory.OpenReader(Stream, ReaderOptions)"/> uses a separate code path
|
||||
/// based on <see cref="IO.SharpCompressStream"/> rewindable buffering, which supports
|
||||
/// non-seekable streams and is therefore not unified with this helper.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static IFactory? TryFindFactory(Stream stream) =>
|
||||
TryFindFactory(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
private static IFactory? TryFindFactory(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
var isArchive = factory.IsArchive(stream, readerOptions);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Factories;
|
||||
@@ -21,7 +23,7 @@ public static partial class ArchiveFactory
|
||||
where TOptions : IWriterOptions
|
||||
{
|
||||
var factory = Factory
|
||||
.Factories.OfType<IWriteableArchiveFactory<TOptions>>()
|
||||
.Factories.OfType<IWritableArchiveFactory<TOptions>>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (factory != null)
|
||||
@@ -35,7 +37,7 @@ public static partial class ArchiveFactory
|
||||
public static IArchive OpenArchive(string filePath, ReaderOptions? options = null)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenArchive(new FileInfo(filePath), options);
|
||||
return OpenArchive(new FileInfo(filePath), options ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? options = null)
|
||||
@@ -46,19 +48,19 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? options = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var filesArray = fileInfos.ToArray();
|
||||
if (filesArray.Length == 0)
|
||||
var filesArray = fileInfos;
|
||||
if (filesArray.Count == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No files to open");
|
||||
}
|
||||
|
||||
var fileInfo = filesArray[0];
|
||||
if (filesArray.Length == 1)
|
||||
if (filesArray.Count == 1)
|
||||
{
|
||||
return OpenArchive(fileInfo, options);
|
||||
}
|
||||
@@ -69,17 +71,16 @@ public static partial class ArchiveFactory
|
||||
return FindFactory<IMultiArchiveFactory>(fileInfo).OpenArchive(filesArray, options);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
public static IArchive OpenArchive(IReadOnlyList<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var streamsArray = streams.ToArray();
|
||||
if (streamsArray.Length == 0)
|
||||
var streamsArray = streams.RequireReadable().RequireSeekable().ToList();
|
||||
if (streamsArray.Count == 0)
|
||||
{
|
||||
throw new ArchiveOperationException("No streams");
|
||||
}
|
||||
|
||||
var firstStream = streamsArray[0];
|
||||
if (streamsArray.Length == 1)
|
||||
if (streamsArray.Count == 1)
|
||||
{
|
||||
return OpenArchive(firstStream, options);
|
||||
}
|
||||
@@ -100,11 +101,11 @@ public static partial class ArchiveFactory
|
||||
archive.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
|
||||
public static T FindFactory<T>(string path)
|
||||
public static T FindFactory<T>(string filePath)
|
||||
where T : IFactory
|
||||
{
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
using Stream stream = File.OpenRead(path);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return FindFactory<T>(stream);
|
||||
}
|
||||
|
||||
@@ -119,29 +120,20 @@ public static partial class ArchiveFactory
|
||||
public static T FindFactory<T>(Stream stream)
|
||||
where T : IFactory
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
// Use the shared detection loop over all factories. If the matched factory
|
||||
// implements T we return it; otherwise (or if nothing matched) we fall through
|
||||
// to the same "unsupported format" exception that the original code produced,
|
||||
// listing the T-typed factories as the hint for the caller.
|
||||
var factory = TryFindFactory(stream);
|
||||
if (factory is T typedFactory)
|
||||
{
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
return typedFactory;
|
||||
}
|
||||
|
||||
var factories = Factory.Factories.OfType<T>();
|
||||
|
||||
var startPosition = stream.Position;
|
||||
|
||||
foreach (var factory in factories)
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
if (factory.IsArchive(stream))
|
||||
{
|
||||
stream.Seek(startPosition, SeekOrigin.Begin);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
|
||||
var extensions = string.Join(", ", factories.Select(item => item.Name));
|
||||
var extensions = string.Join(", ", Factory.Factories.OfType<T>().Select(item => item.Name));
|
||||
|
||||
throw new ArchiveOperationException(
|
||||
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
|
||||
@@ -149,37 +141,82 @@ public static partial class ArchiveFactory
|
||||
}
|
||||
|
||||
public static bool IsArchive(string filePath, out ArchiveType? type)
|
||||
{
|
||||
return IsArchive(filePath, ReaderOptions.ForFilePath, out type);
|
||||
}
|
||||
|
||||
public static bool IsArchive(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
out ArchiveType? type
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream s = File.OpenRead(filePath);
|
||||
return IsArchive(s, out type);
|
||||
return IsArchive(s, readerOptions ?? ReaderOptions.ForFilePath, out type);
|
||||
}
|
||||
|
||||
public static bool IsArchive(Stream stream, out ArchiveType? type)
|
||||
{
|
||||
type = null;
|
||||
stream.NotNull(nameof(stream));
|
||||
return IsArchive(stream, ReaderOptions.ForExternalStream, out type);
|
||||
}
|
||||
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
}
|
||||
public static bool IsArchive(Stream stream, ReaderOptions? readerOptions, out ArchiveType? type)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var startPosition = stream.Position;
|
||||
var factory = TryFindFactory(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
type = factory?.KnownArchiveType;
|
||||
return factory is not null;
|
||||
}
|
||||
|
||||
foreach (var factory in Factory.Factories)
|
||||
{
|
||||
var isArchive = factory.IsArchive(stream);
|
||||
stream.Position = startPosition;
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
string filePath,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await IsArchiveAsync(filePath, ReaderOptions.ForFilePath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (isArchive)
|
||||
{
|
||||
type = factory.KnownArchiveType;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
using Stream stream = File.OpenRead(filePath);
|
||||
return await IsArchiveAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await IsArchiveAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
public static async ValueTask<(bool IsArchive, ArchiveType? Type)> IsArchiveAsync(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
var factory = await TryFindFactoryAsync(
|
||||
stream,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
return (factory is not null, factory?.KnownArchiveType);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetFileParts(string part1)
|
||||
|
||||
22
src/SharpCompress/Archives/ArchiveInformation.cs
Normal file
22
src/SharpCompress/Archives/ArchiveInformation.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about a detected archive, including its type and supported capabilities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="ArchiveFactory.GetArchiveInformation(System.IO.Stream)"/> or
|
||||
/// <see cref="ArchiveFactory.GetArchiveInformationAsync(System.IO.Stream,System.Threading.CancellationToken)"/>
|
||||
/// to obtain an instance of this record.
|
||||
/// </remarks>
|
||||
/// <param name="Type">
|
||||
/// The type of archive detected, or <see langword="null"/> when the format is not a registered well-known type.
|
||||
/// </param>
|
||||
/// <param name="SupportsRandomAccess">
|
||||
/// <see langword="true"/> when this archive format supports random access via the <see cref="IArchive"/> API,
|
||||
/// meaning the full file listing can be retrieved without decompressing the entire archive.
|
||||
/// <see langword="false"/> when only the <see cref="SharpCompress.Readers.IReader"/> API is available,
|
||||
/// which reads entries sequentially and can only report per-entry progress.
|
||||
/// </param>
|
||||
public record ArchiveInformation(ArchiveType? Type, bool SupportsRandomAccess);
|
||||
@@ -21,14 +21,14 @@ public partial class GZipArchive
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IWritableAsyncArchive<GZipWriterOptions>> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncArchive(new FileInfo(filePath), readerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
@@ -37,7 +37,7 @@ public partial class GZipArchive
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
@@ -50,39 +50,38 @@ public partial class GZipArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new GZipArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<GZipWriterOptions> OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new GZipArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -92,15 +91,11 @@ public partial class GZipArchive
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new GZipArchive(
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public interface IArchiveOpenable<TSync, TASync>
|
||||
public static abstract TSync OpenArchive(Stream stream, ReaderOptions? readerOptions = null);
|
||||
|
||||
public static abstract ValueTask<TASync> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -12,12 +12,12 @@ public interface IMultiArchiveOpenable<TSync, TASync>
|
||||
where TASync : IAsyncArchive
|
||||
{
|
||||
public static abstract TSync OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
);
|
||||
|
||||
public static abstract TSync OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
@@ -11,7 +9,7 @@ public static class IWritableArchiveExtensions
|
||||
extension(IWritableArchive writableArchive)
|
||||
{
|
||||
public void AddAllFromDirectory(
|
||||
string filePath,
|
||||
string directoryPath,
|
||||
string searchPattern = "*.*",
|
||||
SearchOption searchOption = SearchOption.AllDirectories
|
||||
)
|
||||
@@ -19,12 +17,16 @@ public static class IWritableArchiveExtensions
|
||||
using (writableArchive.PauseEntryRebuilding())
|
||||
{
|
||||
foreach (
|
||||
var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)
|
||||
var filePath in Directory.EnumerateFiles(
|
||||
directoryPath,
|
||||
searchPattern,
|
||||
searchOption
|
||||
)
|
||||
)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
writableArchive.AddEntry(
|
||||
path.Substring(filePath.Length),
|
||||
filePath.Substring(directoryPath.Length),
|
||||
fileInfo.OpenRead(),
|
||||
true,
|
||||
fileInfo.Length,
|
||||
|
||||
@@ -3,7 +3,7 @@ using SharpCompress.Common.Options;
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
/// <summary>
|
||||
/// Decorator for <see cref="Factories.Factory"/> used to declare an archive format as able to create writeable archives
|
||||
/// Decorator for <see cref="Factories.Factory"/> used to declare an archive format as able to create writable archives.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implemented by:<br/>
|
||||
@@ -12,7 +12,8 @@ namespace SharpCompress.Archives;
|
||||
/// <item><see cref="Factories.ZipFactory"/></item>
|
||||
/// <item><see cref="Factories.GZipFactory"/></item>
|
||||
/// </list>
|
||||
public interface IWriteableArchiveFactory<TOptions> : Factories.IFactory
|
||||
/// </remarks>
|
||||
public interface IWritableArchiveFactory<TOptions> : Factories.IFactory
|
||||
where TOptions : IWriterOptions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -2,9 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives;
|
||||
|
||||
@@ -13,7 +11,7 @@ public static class IWritableAsyncArchiveExtensions
|
||||
extension(IWritableAsyncArchive writableArchive)
|
||||
{
|
||||
public async ValueTask AddAllFromDirectoryAsync(
|
||||
string filePath,
|
||||
string directoryPath,
|
||||
string searchPattern = "*.*",
|
||||
SearchOption searchOption = SearchOption.AllDirectories
|
||||
)
|
||||
@@ -21,13 +19,17 @@ public static class IWritableAsyncArchiveExtensions
|
||||
using (writableArchive.PauseEntryRebuilding())
|
||||
{
|
||||
foreach (
|
||||
var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption)
|
||||
var filePath in Directory.EnumerateFiles(
|
||||
directoryPath,
|
||||
searchPattern,
|
||||
searchOption
|
||||
)
|
||||
)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
await writableArchive
|
||||
.AddEntryAsync(
|
||||
path.Substring(filePath.Length),
|
||||
filePath.Substring(directoryPath.Length),
|
||||
fileInfo.OpenRead(),
|
||||
true,
|
||||
fileInfo.Length,
|
||||
|
||||
@@ -15,18 +15,17 @@ namespace SharpCompress.Archives.Rar;
|
||||
internal class FileInfoRarArchiveVolume : RarVolume
|
||||
{
|
||||
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
|
||||
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
|
||||
: base(
|
||||
StreamingMode.Seekable,
|
||||
fileInfo.OpenRead(),
|
||||
options.WithLeaveStreamOpen(false),
|
||||
index
|
||||
)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
FileParts = GetVolumeFileParts().ToArray().ToReadOnly();
|
||||
}
|
||||
|
||||
private static ReaderOptions FixOptions(ReaderOptions options)
|
||||
{
|
||||
//make sure we're closing streams with fileinfo
|
||||
return options with { LeaveStreamOpen = false };
|
||||
}
|
||||
|
||||
internal ReadOnlyCollection<RarFilePart> FileParts { get; }
|
||||
|
||||
internal FileInfo FileInfo { get; }
|
||||
|
||||
@@ -21,14 +21,14 @@ public partial class RarArchive
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IRarAsyncArchive> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IRarAsyncArchive)OpenArchive(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IRarAsyncArchive)OpenArchive(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
|
||||
@@ -39,7 +39,7 @@ public partial class RarArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -51,53 +51,48 @@ public partial class RarArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new RarArchive(
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new RarArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IRarArchive OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new RarArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -158,7 +153,7 @@ public partial class RarArchive
|
||||
{
|
||||
try
|
||||
{
|
||||
MarkHeader.Read(stream, true, false);
|
||||
MarkHeader.Read(stream, true, options?.LookForHeader ?? false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -177,7 +172,7 @@ public partial class RarArchive
|
||||
try
|
||||
{
|
||||
await MarkHeader
|
||||
.ReadAsync(stream, true, false, cancellationToken)
|
||||
.ReadAsync(stream, true, options?.LookForHeader ?? false, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,22 +17,25 @@ public partial class SevenZipArchive
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IAsyncArchive> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty("path");
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new(
|
||||
(IAsyncArchive)OpenArchive(new FileInfo(path), readerOptions ?? new ReaderOptions())
|
||||
(IAsyncArchive)OpenArchive(
|
||||
new FileInfo(filePath),
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(string filePath, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
filePath.NotNullOrEmpty("filePath");
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenArchive(new FileInfo(filePath), readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
@@ -42,54 +45,49 @@ public partial class SevenZipArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IArchive OpenArchive(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new SevenZipArchive(
|
||||
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, _ => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,11 +143,14 @@ public partial class SevenZipArchive
|
||||
return IsSevenZipFile(stream);
|
||||
}
|
||||
|
||||
public static bool IsSevenZipFile(Stream stream)
|
||||
public static bool IsSevenZipFile(Stream stream) =>
|
||||
IsSevenZipFile(stream, ReaderOptions.ForExternalStream);
|
||||
|
||||
public static bool IsSevenZipFile(Stream stream, ReaderOptions? readerOptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SignatureMatch(stream);
|
||||
return SignatureMatch(stream, readerOptions?.LookForHeader ?? false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -160,12 +161,25 @@ public partial class SevenZipArchive
|
||||
public static async ValueTask<bool> IsSevenZipFileAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
await IsSevenZipFileAsync(stream, ReaderOptions.ForExternalStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
public static async ValueTask<bool> IsSevenZipFileAsync(
|
||||
Stream stream,
|
||||
ReaderOptions? readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
return await SignatureMatchAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
return await SignatureMatchAsync(
|
||||
stream,
|
||||
readerOptions?.LookForHeader ?? false,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -175,13 +189,29 @@ public partial class SevenZipArchive
|
||||
|
||||
private static ReadOnlySpan<byte> Signature => [(byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C];
|
||||
|
||||
private static bool SignatureMatch(Stream stream)
|
||||
private static bool SignatureMatch(Stream stream, bool lookForHeader)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(6);
|
||||
try
|
||||
{
|
||||
stream.ReadExact(buffer, 0, 6);
|
||||
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
|
||||
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
|
||||
for (var offset = 0; offset <= maxScanOffset; offset++)
|
||||
{
|
||||
stream.ReadExact(buffer, 0, 6);
|
||||
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Position -= 5;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -191,18 +221,39 @@ public partial class SevenZipArchive
|
||||
|
||||
private static async ValueTask<bool> SignatureMatchAsync(
|
||||
Stream stream,
|
||||
bool lookForHeader,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(6);
|
||||
try
|
||||
{
|
||||
if (!await stream.ReadFullyAsync(buffer, 0, 6, cancellationToken).ConfigureAwait(false))
|
||||
var maxScanOffset = lookForHeader ? 0x80000 - 20 : 0;
|
||||
for (var offset = 0; offset <= maxScanOffset; offset++)
|
||||
{
|
||||
return false;
|
||||
if (
|
||||
!await stream
|
||||
.ReadFullyAsync(buffer, 0, 6, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!lookForHeader || !stream.CanSeek || stream.Length - stream.Position < 6)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Position -= 5;
|
||||
}
|
||||
|
||||
return buffer.AsSpan().Slice(0, 6).SequenceEqual(Signature);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ public partial class TarArchive
|
||||
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var memoryStream = new PooledMemoryStream();
|
||||
await entryStream.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||
memoryStream.Position = 0;
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
@@ -38,23 +38,20 @@ public partial class TarArchive
|
||||
)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return OpenArchive(
|
||||
[fileInfo],
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
);
|
||||
return OpenArchive([fileInfo], readerOptions ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IWritableArchive<TarWriterOptions> OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
var sourceStream = new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
);
|
||||
var compressionType = TarFactory.GetCompressionType(
|
||||
sourceStream,
|
||||
@@ -65,16 +62,15 @@ public partial class TarArchive
|
||||
}
|
||||
|
||||
public static IWritableArchive<TarWriterOptions> OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
var sourceStream = new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = TarFactory.GetCompressionType(
|
||||
sourceStream,
|
||||
@@ -89,12 +85,8 @@ public partial class TarArchive
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return OpenArchive([stream], readerOptions);
|
||||
}
|
||||
@@ -105,11 +97,12 @@ public partial class TarArchive
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
var sourceStream = new SourceStream(
|
||||
stream,
|
||||
i => null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
@@ -119,14 +112,14 @@ public partial class TarArchive
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return OpenAsyncArchive(new FileInfo(path), readerOptions, cancellationToken);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncArchive(new FileInfo(filePath), readerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async ValueTask<IWritableAsyncArchive<TarWriterOptions>> OpenAsyncArchive(
|
||||
@@ -137,7 +130,7 @@ public partial class TarArchive
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
var sourceStream = new SourceStream(fileInfo, i => null, readerOptions);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
@@ -153,12 +146,11 @@ public partial class TarArchive
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
var sourceStream = new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
@@ -175,11 +167,11 @@ public partial class TarArchive
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
var sourceStream = new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
);
|
||||
var compressionType = await TarFactory
|
||||
.GetCompressionTypeAsync(sourceStream, sourceStream.ReaderOptions, cancellationToken)
|
||||
|
||||
@@ -151,7 +151,7 @@ public partial class TarArchive
|
||||
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var memoryStream = new PooledMemoryStream();
|
||||
entryStream.CopyTo(memoryStream, Constants.BufferSize);
|
||||
memoryStream.Position = 0;
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
@@ -45,7 +45,7 @@ public partial class ZipArchive
|
||||
s = new SourceStream(
|
||||
v[0].Stream,
|
||||
i => i < v.Length ? v[i].Stream : null,
|
||||
new ReaderOptions() { LeaveStreamOpen = true }
|
||||
ReaderOptions.ForExternalStream
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -41,39 +41,38 @@ public partial class ZipArchive
|
||||
new SourceStream(
|
||||
fileInfo,
|
||||
i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo),
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<ZipWriterOptions> OpenArchive(
|
||||
IEnumerable<FileInfo> fileInfos,
|
||||
IReadOnlyList<FileInfo> fileInfos,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
fileInfos.NotNull(nameof(fileInfos));
|
||||
var files = fileInfos.ToArray();
|
||||
var files = fileInfos;
|
||||
return new ZipArchive(
|
||||
new SourceStream(
|
||||
files[0],
|
||||
i => i < files.Length ? files[i] : null,
|
||||
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
|
||||
i => i < files.Count ? files[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForFilePath
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static IWritableArchive<ZipWriterOptions> OpenArchive(
|
||||
IEnumerable<Stream> streams,
|
||||
IReadOnlyList<Stream> streams,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
var strms = streams.ToArray();
|
||||
var strms = streams.RequireReadable().RequireSeekable().ToList();
|
||||
return new ZipArchive(
|
||||
new SourceStream(
|
||||
strms[0],
|
||||
i => i < strms.Length ? strms[i] : null,
|
||||
readerOptions ?? new ReaderOptions()
|
||||
i => i < strms.Count ? strms[i] : null,
|
||||
readerOptions ?? ReaderOptions.ForExternalStream
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -83,26 +82,22 @@ public partial class ZipArchive
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (stream is not { CanSeek: true })
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
stream.RequireReadable();
|
||||
stream.RequireSeekable();
|
||||
|
||||
return new ZipArchive(
|
||||
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
|
||||
new SourceStream(stream, i => null, readerOptions ?? ReaderOptions.ForExternalStream)
|
||||
);
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(path, readerOptions));
|
||||
return new((IWritableAsyncArchive<ZipWriterOptions>)OpenArchive(filePath, readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IWritableAsyncArchive<ZipWriterOptions>> OpenAsyncArchive(
|
||||
|
||||
@@ -86,7 +86,7 @@ public partial class ZipArchive
|
||||
s = new SourceStream(
|
||||
v[0].Stream,
|
||||
i => i < v.Length ? v[i].Stream : null,
|
||||
new ReaderOptions() { LeaveStreamOpen = true }
|
||||
ReaderOptions.ForExternalStream
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Breaking change:</b> Changed from field to init-only property in version 0.40.0.
|
||||
/// The default handler logs a warning message.
|
||||
/// If no handler is provided, symbolic links are silently skipped during extraction.
|
||||
/// </remarks>
|
||||
public Action<string, string>? SymbolicLinkHandler { get; init; }
|
||||
|
||||
@@ -58,10 +58,7 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// Creates a new ExtractionOptions instance with the specified overwrite behavior.
|
||||
/// </summary>
|
||||
/// <param name="overwrite">Whether to overwrite existing files.</param>
|
||||
public ExtractionOptions(bool overwrite)
|
||||
{
|
||||
Overwrite = overwrite;
|
||||
}
|
||||
public ExtractionOptions(bool overwrite) => Overwrite = overwrite;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ExtractionOptions instance with the specified extraction path and overwrite behavior.
|
||||
@@ -102,14 +99,4 @@ public sealed record ExtractionOptions : IExtractionOptions
|
||||
/// </summary>
|
||||
public static ExtractionOptions PreserveMetadata =>
|
||||
new() { PreserveFileTime = true, PreserveAttributes = true };
|
||||
|
||||
/// <summary>
|
||||
/// Default symbolic link handler that logs a warning message.
|
||||
/// </summary>
|
||||
public static void DefaultSymbolicLinkHandler(string sourcePath, string targetPath)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace SharpCompress.Common.GZip;
|
||||
|
||||
public class GZipVolume : Volume
|
||||
{
|
||||
public GZipVolume(Stream stream, ReaderOptions? options, int index)
|
||||
public GZipVolume(Stream stream, ReaderOptions options, int index)
|
||||
: base(stream, options, index) { }
|
||||
|
||||
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
|
||||
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
|
||||
: base(fileInfo.OpenRead(), options.WithLeaveStreamOpen(false)) { }
|
||||
|
||||
public override bool IsFirstVolume => true;
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common;
|
||||
|
||||
internal static class EntryExtensions
|
||||
{
|
||||
internal static void PreserveExtractionOptions(
|
||||
this IEntry entry,
|
||||
string destinationFileName,
|
||||
ExtractionOptions options
|
||||
)
|
||||
{
|
||||
if (options.PreserveFileTime || options.PreserveAttributes)
|
||||
{
|
||||
var nf = new FileInfo(destinationFileName);
|
||||
if (!nf.Exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update file time to original packed time
|
||||
if (options.PreserveFileTime)
|
||||
{
|
||||
if (entry.CreatedTime.HasValue)
|
||||
{
|
||||
nf.CreationTime = entry.CreatedTime.Value;
|
||||
}
|
||||
|
||||
if (entry.LastModifiedTime.HasValue)
|
||||
{
|
||||
nf.LastWriteTime = entry.LastModifiedTime.Value;
|
||||
}
|
||||
|
||||
if (entry.LastAccessedTime.HasValue)
|
||||
{
|
||||
nf.LastAccessTime = entry.LastAccessedTime.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.PreserveAttributes)
|
||||
{
|
||||
if (entry.Attrib.HasValue)
|
||||
{
|
||||
nf.Attributes = (FileAttributes)
|
||||
System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,18 +86,7 @@ internal static partial class IEntryExtensions
|
||||
options ??= new ExtractionOptions();
|
||||
if (entry.LinkTarget != null)
|
||||
{
|
||||
if (options.SymbolicLinkHandler is not null)
|
||||
{
|
||||
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtractionOptions.DefaultSymbolicLinkHandler(
|
||||
destinationFileName,
|
||||
entry.LinkTarget
|
||||
);
|
||||
}
|
||||
return;
|
||||
options.SymbolicLinkHandler?.Invoke(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -107,19 +107,7 @@ internal static partial class IEntryExtensions
|
||||
options ??= new ExtractionOptions();
|
||||
if (entry.LinkTarget != null)
|
||||
{
|
||||
if (options.SymbolicLinkHandler is not null)
|
||||
{
|
||||
options.SymbolicLinkHandler(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtractionOptions.DefaultSymbolicLinkHandler(
|
||||
destinationFileName,
|
||||
entry.LinkTarget
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
options.SymbolicLinkHandler?.Invoke(destinationFileName, entry.LinkTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -134,5 +122,69 @@ internal static partial class IEntryExtensions
|
||||
entry.PreserveExtractionOptions(destinationFileName, options);
|
||||
}
|
||||
}
|
||||
|
||||
internal void PreserveExtractionOptions(
|
||||
string destinationFileName,
|
||||
ExtractionOptions options
|
||||
)
|
||||
{
|
||||
if (options.PreserveFileTime || options.PreserveAttributes)
|
||||
{
|
||||
var nf = new FileInfo(destinationFileName);
|
||||
if (!nf.Exists)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update file time to original packed time
|
||||
if (options.PreserveFileTime)
|
||||
{
|
||||
if (entry.CreatedTime.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
nf.CreationTime = entry.CreatedTime.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid time or the OS rejected
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.LastModifiedTime.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
nf.LastWriteTime = entry.LastModifiedTime.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid time or the OS rejected
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.LastAccessedTime.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
nf.LastAccessTime = entry.LastAccessedTime.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid time or the OS rejected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.PreserveAttributes)
|
||||
{
|
||||
if (entry.Attrib.HasValue)
|
||||
{
|
||||
nf.Attributes = (FileAttributes)
|
||||
Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace SharpCompress.Common.Lzw;
|
||||
|
||||
public class LzwVolume : Volume
|
||||
{
|
||||
public LzwVolume(Stream stream, ReaderOptions? options, int index)
|
||||
public LzwVolume(Stream stream, ReaderOptions options, int index)
|
||||
: base(stream, options, index) { }
|
||||
|
||||
public LzwVolume(FileInfo fileInfo, ReaderOptions options)
|
||||
: base(fileInfo.OpenRead(), options with { LeaveStreamOpen = false }) { }
|
||||
: base(fileInfo.OpenRead(), options.WithLeaveStreamOpen(false)) { }
|
||||
|
||||
public override bool IsFirstVolume => true;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal class CryptKey5 : ICryptKey
|
||||
)
|
||||
{
|
||||
var passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
#if LEGACY_DOTNET || NET5_0
|
||||
#if LEGACY_DOTNET
|
||||
using var hmac = new HMACSHA256(passwordBytes);
|
||||
var block = hmac.ComputeHash(salt);
|
||||
#else
|
||||
@@ -50,7 +50,7 @@ internal class CryptKey5 : ICryptKey
|
||||
{
|
||||
for (var i = 1; i < loop[x]; i++)
|
||||
{
|
||||
#if LEGACY_DOTNET || NET5_0
|
||||
#if LEGACY_DOTNET
|
||||
block = hmac.ComputeHash(block);
|
||||
#else
|
||||
block = HMACSHA256.HashData(passwordBytes, block);
|
||||
|
||||
@@ -1182,7 +1182,7 @@ internal partial class ArchiveReader
|
||||
}
|
||||
else
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
_stream = new PooledMemoryStream();
|
||||
}
|
||||
_rem = _db._files[index].Size;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SharpCompress.Compressors.LZMA.Utilities;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.SevenZip;
|
||||
|
||||
@@ -215,7 +216,7 @@ internal sealed class SevenZipFilesInfoWriter
|
||||
Action<Stream> writeData
|
||||
)
|
||||
{
|
||||
using var dataStream = new MemoryStream();
|
||||
using var dataStream = new PooledMemoryStream();
|
||||
writeData(dataStream);
|
||||
|
||||
stream.WriteByte((byte)propertyId);
|
||||
|
||||
@@ -59,7 +59,7 @@ internal sealed partial class TarHeader
|
||||
int splitIndex = -1;
|
||||
for (int i = 0; i < dirSeps.Count; i++)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
int count = ArchiveEncoding
|
||||
.GetEncoding()
|
||||
.GetByteCount(fullName.AsSpan(0, dirSeps[i]));
|
||||
|
||||
@@ -89,7 +89,7 @@ internal sealed partial class TarHeader
|
||||
int splitIndex = -1;
|
||||
for (int i = 0; i < dirSeps.Count; i++)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
int count = ArchiveEncoding
|
||||
.GetEncoding()
|
||||
.GetByteCount(fullName.AsSpan(0, dirSeps[i]));
|
||||
|
||||
@@ -11,10 +11,10 @@ public abstract partial class Volume : IVolume, IAsyncDisposable
|
||||
private readonly Stream _baseStream;
|
||||
private readonly Stream _actualStream;
|
||||
|
||||
internal Volume(Stream stream, ReaderOptions? readerOptions, int index = 0)
|
||||
internal Volume(Stream stream, ReaderOptions readerOptions, int index = 0)
|
||||
{
|
||||
Index = index;
|
||||
ReaderOptions = readerOptions ?? new ReaderOptions();
|
||||
ReaderOptions = readerOptions;
|
||||
_baseStream = stream;
|
||||
|
||||
// Only rewind if it's a buffered SharpCompressStream (not passthrough)
|
||||
|
||||
@@ -270,7 +270,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
}
|
||||
|
||||
using var inputMs = new MemoryStream(data.ToArray(), writable: false);
|
||||
using var outputMs = new MemoryStream();
|
||||
using var outputMs = new PooledMemoryStream();
|
||||
|
||||
encoder.Code(inputMs, outputMs, data.Length, -1, null);
|
||||
|
||||
@@ -302,7 +302,7 @@ internal sealed class Lzma2EncoderStream : Stream
|
||||
decoder.SetDecoderProperties(props);
|
||||
|
||||
using var input = new MemoryStream(compressedData);
|
||||
using var output = new MemoryStream();
|
||||
using var output = new PooledMemoryStream();
|
||||
decoder.Code(input, output, compressedData.Length, uncompressedSize, null);
|
||||
|
||||
return (int)input.Position;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.RangeCoder;
|
||||
using SharpCompress.Compressors.PPMd.H;
|
||||
using SharpCompress.Compressors.PPMd.I1;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.PPMd;
|
||||
|
||||
@@ -179,7 +180,7 @@ public class PpmdStream : Stream, IAsyncDisposable
|
||||
{
|
||||
if (_compress)
|
||||
{
|
||||
_model.EncodeBlock(_stream, new MemoryStream(), true);
|
||||
_model.EncodeBlock(_stream, Stream.Null, true);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
@@ -32,12 +31,12 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
.ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
|
||||
Update(_blake2sp!, new ReadOnlySpan<byte>(buffer, offset, result));
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
|
||||
EnsureHash();
|
||||
if (!disableCRCCheck && !GetCrc().SequenceEqual(readStream.CurrentCrc) && count != 0)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
@@ -56,14 +55,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, buffer.Span.Slice(0, result), result);
|
||||
Update(_blake2sp!, buffer.Span.Slice(0, result));
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
EnsureHash();
|
||||
if (
|
||||
!disableCRCCheck
|
||||
&& !(GetCrc().SequenceEqual(readStream.CurrentCrc))
|
||||
&& !GetCrc().SequenceEqual(readStream.CurrentCrc)
|
||||
&& buffer.Length != 0
|
||||
)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
@@ -13,14 +10,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
private readonly MultiVolumeReadOnlyStreamBase readStream;
|
||||
private readonly bool disableCRCCheck;
|
||||
|
||||
const uint BLAKE2S_NUM_ROUNDS = 10;
|
||||
const uint BLAKE2S_FINAL_FLAG = (~(uint)0);
|
||||
const int BLAKE2S_BLOCK_SIZE = 64;
|
||||
const int BLAKE2S_DIGEST_SIZE = 32;
|
||||
const int BLAKE2SP_PARALLEL_DEGREE = 8;
|
||||
const uint BLAKE2S_INIT_IV_SIZE = 8;
|
||||
private const int BLAKE2S_NUM_ROUNDS = 10;
|
||||
private const uint BLAKE2S_FINAL_FLAG = ~(uint)0;
|
||||
private const int BLAKE2S_BLOCK_SIZE = 64;
|
||||
private const int BLAKE2S_DIGEST_SIZE = 32;
|
||||
private const int BLAKE2SP_PARALLEL_DEGREE = 8;
|
||||
private const int BLAKE2S_INIT_IV_SIZE = 8;
|
||||
|
||||
static readonly UInt32[] k_BLAKE2S_IV =
|
||||
private static readonly uint[] k_BLAKE2S_IV =
|
||||
{
|
||||
0x6A09E667U,
|
||||
0xBB67AE85U,
|
||||
@@ -32,7 +29,7 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
0x5BE0CD19U,
|
||||
};
|
||||
|
||||
static readonly byte[][] k_BLAKE2S_Sigma =
|
||||
private static readonly byte[][] k_BLAKE2S_Sigma =
|
||||
{
|
||||
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
|
||||
new byte[] { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
|
||||
@@ -46,14 +43,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
new byte[] { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
|
||||
};
|
||||
|
||||
internal class BLAKE2S
|
||||
private sealed class BLAKE2S
|
||||
{
|
||||
internal UInt32[] h;
|
||||
internal UInt32[] t;
|
||||
internal UInt32[] f;
|
||||
internal byte[] b;
|
||||
internal readonly uint[] h;
|
||||
internal readonly uint[] t;
|
||||
internal readonly uint[] f;
|
||||
internal readonly byte[] b;
|
||||
internal int bufferPosition;
|
||||
internal UInt32 lastNodeFlag;
|
||||
internal uint lastNodeFlag;
|
||||
|
||||
public BLAKE2S()
|
||||
{
|
||||
@@ -64,9 +61,9 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
}
|
||||
};
|
||||
|
||||
internal class BLAKE2SP
|
||||
private sealed class BLAKE2SP
|
||||
{
|
||||
internal BLAKE2S[] S;
|
||||
internal readonly BLAKE2S[] S;
|
||||
internal int bufferPosition;
|
||||
|
||||
public BLAKE2SP()
|
||||
@@ -79,9 +76,8 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
}
|
||||
};
|
||||
|
||||
BLAKE2SP _blake2sp;
|
||||
|
||||
byte[] _hash = [];
|
||||
private BLAKE2SP? _blake2sp;
|
||||
private byte[]? _hash;
|
||||
|
||||
private RarBLAKE2spStream(
|
||||
IRarUnpack unpack,
|
||||
@@ -92,10 +88,9 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
{
|
||||
this.readStream = readStream;
|
||||
|
||||
// TODO: rar uses a modified hash xor'ed with encryption key?
|
||||
disableCRCCheck = fileHeader.IsEncrypted;
|
||||
_hash = fileHeader.FileCrc.NotNull();
|
||||
_blake2sp = new BLAKE2SP();
|
||||
ResetCrc();
|
||||
this._blake2sp = CreateBlake2sp();
|
||||
}
|
||||
|
||||
public static RarBLAKE2spStream Create(
|
||||
@@ -111,19 +106,15 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
|
||||
// Async methods moved to RarBLAKE2spStream.Async.cs
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
public byte[] GetCrc() =>
|
||||
this._hash
|
||||
?? throw new InvalidOperationException(
|
||||
"hash not computed, has the stream been fully drained?"
|
||||
);
|
||||
|
||||
public byte[] GetCrc() => _hash;
|
||||
|
||||
internal void ResetCrc(BLAKE2S hash)
|
||||
private static void ResetCrc(BLAKE2S hash)
|
||||
{
|
||||
for (UInt32 j = 0; j < BLAKE2S_INIT_IV_SIZE; j++)
|
||||
{
|
||||
hash.h[j] = k_BLAKE2S_IV[j];
|
||||
}
|
||||
k_BLAKE2S_IV.AsSpan().CopyTo(hash.h);
|
||||
hash.t[0] = 0;
|
||||
hash.t[1] = 0;
|
||||
hash.f[0] = 0;
|
||||
@@ -132,14 +123,14 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
hash.lastNodeFlag = 0;
|
||||
}
|
||||
|
||||
internal void G(
|
||||
ref UInt32[] m,
|
||||
ref byte[] sigma,
|
||||
private static void G(
|
||||
Span<uint> m,
|
||||
byte[] sigma,
|
||||
int i,
|
||||
ref UInt32 a,
|
||||
ref UInt32 b,
|
||||
ref UInt32 c,
|
||||
ref UInt32 d
|
||||
ref uint a,
|
||||
ref uint b,
|
||||
ref uint c,
|
||||
ref uint d
|
||||
)
|
||||
{
|
||||
a += b + m[sigma[2 * i]];
|
||||
@@ -157,16 +148,22 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
b = (b >> 7) | (b << 25);
|
||||
}
|
||||
|
||||
internal void Compress(BLAKE2S hash)
|
||||
private static void Compress(BLAKE2S hash)
|
||||
{
|
||||
var m = new UInt32[16];
|
||||
var v = new UInt32[16];
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
Span<uint> m = stackalloc uint[16];
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
|
||||
MemoryMarshal.Cast<byte, uint>(hash.b).CopyTo(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
|
||||
}
|
||||
}
|
||||
|
||||
Span<uint> v = stackalloc uint[16];
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
v[i] = hash.h[i];
|
||||
@@ -184,16 +181,15 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
|
||||
for (var r = 0; r < BLAKE2S_NUM_ROUNDS; r++)
|
||||
{
|
||||
ref byte[] sigma = ref k_BLAKE2S_Sigma[r];
|
||||
|
||||
G(ref m, ref sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
|
||||
G(ref m, ref sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
|
||||
G(ref m, ref sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
|
||||
G(ref m, ref sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
|
||||
G(ref m, ref sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
|
||||
G(ref m, ref sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
|
||||
G(ref m, ref sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
|
||||
G(ref m, ref sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
|
||||
var sigma = k_BLAKE2S_Sigma[r];
|
||||
G(m, sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
|
||||
G(m, sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
|
||||
G(m, sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
|
||||
G(m, sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
|
||||
G(m, sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
|
||||
G(m, sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
|
||||
G(m, sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
|
||||
G(m, sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
@@ -202,103 +198,124 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(BLAKE2S hash, ReadOnlySpan<byte> data, int size)
|
||||
private static void Update(BLAKE2S hash, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var i = 0;
|
||||
while (size != 0)
|
||||
while (data.Length != 0)
|
||||
{
|
||||
var pos = hash.bufferPosition;
|
||||
var reminder = BLAKE2S_BLOCK_SIZE - pos;
|
||||
|
||||
if (size <= reminder)
|
||||
var chunkSize = BLAKE2S_BLOCK_SIZE - pos;
|
||||
if (data.Length <= chunkSize)
|
||||
{
|
||||
data.Slice(i, size).CopyTo(new Span<byte>(hash.b, pos, size));
|
||||
hash.bufferPosition += size;
|
||||
data.CopyTo(hash.b.AsSpan(pos));
|
||||
hash.bufferPosition += data.Length;
|
||||
return;
|
||||
}
|
||||
data.Slice(i, reminder).CopyTo(new Span<byte>(hash.b, pos, reminder));
|
||||
data.Slice(0, chunkSize).CopyTo(hash.b.AsSpan(pos));
|
||||
hash.t[0] += BLAKE2S_BLOCK_SIZE;
|
||||
hash.t[1] += hash.t[0] < BLAKE2S_BLOCK_SIZE ? 1U : 0U;
|
||||
Compress(hash);
|
||||
hash.bufferPosition = 0;
|
||||
i += reminder;
|
||||
size -= reminder;
|
||||
data = data.Slice(chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
internal byte[] Final(BLAKE2S hash)
|
||||
private static void Final(BLAKE2S hash, Span<byte> output)
|
||||
{
|
||||
hash.t[0] += (uint)hash.bufferPosition;
|
||||
hash.t[1] += hash.t[0] < hash.bufferPosition ? 1U : 0U;
|
||||
hash.f[0] = BLAKE2S_FINAL_FLAG;
|
||||
hash.f[1] = hash.lastNodeFlag;
|
||||
Array.Clear(hash.b, hash.bufferPosition, BLAKE2S_BLOCK_SIZE - hash.bufferPosition);
|
||||
hash.b.AsSpan(hash.bufferPosition).Clear();
|
||||
Compress(hash);
|
||||
|
||||
var mem = new MemoryStream();
|
||||
|
||||
for (var i = 0; i < 8; i++)
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
mem.Write(BitConverter.GetBytes(hash.h[i]), 0, 4);
|
||||
MemoryMarshal.Cast<uint, byte>(hash.h).CopyTo(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
var v = hash.h[i];
|
||||
output[i * 4] = (byte)v;
|
||||
output[i * 4 + 1] = (byte)(v >> 8);
|
||||
output[i * 4 + 2] = (byte)(v >> 16);
|
||||
output[i * 4 + 3] = (byte)(v >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
|
||||
public void ResetCrc()
|
||||
private static BLAKE2SP CreateBlake2sp()
|
||||
{
|
||||
_blake2sp.bufferPosition = 0;
|
||||
var blake2sp = new BLAKE2SP();
|
||||
|
||||
for (UInt32 i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
for (var i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
{
|
||||
_blake2sp.S[i].bufferPosition = 0;
|
||||
ResetCrc(_blake2sp.S[i]);
|
||||
_blake2sp.S[i].h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
|
||||
_blake2sp.S[i].h[2] ^= i;
|
||||
_blake2sp.S[i].h[3] ^= (BLAKE2S_DIGEST_SIZE << 24);
|
||||
var blake2S = blake2sp.S[i];
|
||||
ResetCrc(blake2S);
|
||||
|
||||
var h = blake2S.h;
|
||||
// word[0]: digest_length | (fanout<<16) | (depth<<24)
|
||||
h[0] ^= BLAKE2S_DIGEST_SIZE | (BLAKE2SP_PARALLEL_DEGREE << 16) | (2 << 24);
|
||||
// word[2]: node_offset = leaf index
|
||||
h[2] ^= (uint)i;
|
||||
// word[3]: inner_length in bits 24-31
|
||||
h[3] ^= BLAKE2S_DIGEST_SIZE << 24;
|
||||
}
|
||||
|
||||
_blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
return blake2sp;
|
||||
}
|
||||
|
||||
internal void Update(BLAKE2SP hash, ReadOnlySpan<byte> data, int size)
|
||||
private static void Update(BLAKE2SP hash, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var i = 0;
|
||||
var pos = hash.bufferPosition;
|
||||
while (size != 0)
|
||||
while (data.Length != 0)
|
||||
{
|
||||
var index = pos / BLAKE2S_BLOCK_SIZE;
|
||||
var reminder = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
|
||||
if (reminder > size)
|
||||
var chunkSize = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
|
||||
if (chunkSize > data.Length)
|
||||
{
|
||||
reminder = size;
|
||||
chunkSize = data.Length;
|
||||
}
|
||||
// Update(hash.S[index], data, size);
|
||||
Update(hash.S[index], data.Slice(i, reminder), reminder);
|
||||
size -= reminder;
|
||||
i += reminder;
|
||||
pos += reminder;
|
||||
pos &= (BLAKE2S_BLOCK_SIZE * (BLAKE2SP_PARALLEL_DEGREE - 1));
|
||||
Update(hash.S[index], data.Slice(0, chunkSize));
|
||||
data = data.Slice(chunkSize);
|
||||
pos = (pos + chunkSize) & (BLAKE2S_BLOCK_SIZE * BLAKE2SP_PARALLEL_DEGREE - 1);
|
||||
}
|
||||
hash.bufferPosition = pos;
|
||||
}
|
||||
|
||||
internal byte[] Final(BLAKE2SP hash)
|
||||
private static byte[] Final(BLAKE2SP blake2sp)
|
||||
{
|
||||
var h = new BLAKE2S();
|
||||
var blake2s = new BLAKE2S();
|
||||
ResetCrc(blake2s);
|
||||
|
||||
ResetCrc(h);
|
||||
h.h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
|
||||
h.h[3] ^= (1 << 16 | BLAKE2S_DIGEST_SIZE << 24);
|
||||
h.lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
var h = blake2s.h;
|
||||
// word[0]: digest_length | (fanout<<16) | (depth<<24) — same as leaves
|
||||
h[0] ^= BLAKE2S_DIGEST_SIZE | (BLAKE2SP_PARALLEL_DEGREE << 16) | (2 << 24);
|
||||
// word[3]: node_depth=1 (bits 16-23), inner_length=32 (bits 24-31)
|
||||
h[3] ^= (1 << 16) | (BLAKE2S_DIGEST_SIZE << 24);
|
||||
blake2s.lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
|
||||
Span<byte> digest = stackalloc byte[BLAKE2S_DIGEST_SIZE];
|
||||
for (var i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
{
|
||||
var digest = Final(_blake2sp.S[i]);
|
||||
Update(h, digest, BLAKE2S_DIGEST_SIZE);
|
||||
Final(blake2sp.S[i], digest);
|
||||
Update(blake2s, digest);
|
||||
}
|
||||
|
||||
return Final(h);
|
||||
Final(blake2s, digest);
|
||||
return digest.ToArray();
|
||||
}
|
||||
|
||||
private void EnsureHash()
|
||||
{
|
||||
if (this._hash == null)
|
||||
{
|
||||
this._hash = Final(this._blake2sp!);
|
||||
// prevent incorrect usage past hash finality by failing fast
|
||||
this._blake2sp = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
@@ -306,12 +323,12 @@ internal partial class RarBLAKE2spStream : RarStream
|
||||
var result = base.Read(buffer, offset, count);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
|
||||
Update(this._blake2sp!, new ReadOnlySpan<byte>(buffer, offset, result));
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
|
||||
EnsureHash();
|
||||
if (!disableCRCCheck && !GetCrc().SequenceEqual(readStream.CurrentCrc) && count != 0)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Squeezed;
|
||||
|
||||
@@ -54,14 +55,14 @@ public partial class SqueezeStream
|
||||
|
||||
if (bytesRead != 2)
|
||||
{
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
return new PooledMemoryStream();
|
||||
}
|
||||
|
||||
int numnodes = numNodesBytes[0] | (numNodesBytes[1] << 8);
|
||||
|
||||
if (numnodes >= NUMVALS || numnodes == 0)
|
||||
{
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
return new PooledMemoryStream();
|
||||
}
|
||||
|
||||
var dnode = new int[numnodes, 2];
|
||||
@@ -82,7 +83,7 @@ public partial class SqueezeStream
|
||||
}
|
||||
|
||||
var bitReader = new BitReader(_stream);
|
||||
var huffmanDecoded = new MemoryStream();
|
||||
var huffmanDecoded = new PooledMemoryStream();
|
||||
int i = 0;
|
||||
|
||||
while (true)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Text;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Squeezed;
|
||||
|
||||
@@ -67,7 +68,7 @@ public partial class SqueezeStream : Stream
|
||||
|
||||
if (numnodes >= NUMVALS || numnodes == 0)
|
||||
{
|
||||
return new MemoryStream(Array.Empty<byte>());
|
||||
return new PooledMemoryStream();
|
||||
}
|
||||
|
||||
var dnode = new int[numnodes, 2];
|
||||
@@ -78,7 +79,7 @@ public partial class SqueezeStream : Stream
|
||||
}
|
||||
|
||||
var bitReader = new BitReader(_stream);
|
||||
var huffmanDecoded = new MemoryStream();
|
||||
var huffmanDecoded = new PooledMemoryStream();
|
||||
int i = 0;
|
||||
|
||||
while (true)
|
||||
|
||||
@@ -5,7 +5,7 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
#endif
|
||||
|
||||
@@ -554,7 +554,7 @@ public static unsafe partial class Methods
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ZSTD_copy16(void* dst, void* src)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
if (AdvSimd.IsSupported)
|
||||
{
|
||||
AdvSimd.Store((byte*)dst, AdvSimd.LoadVector128((byte*)src));
|
||||
|
||||
@@ -6,7 +6,7 @@ using static SharpCompress.Compressors.ZStandard.UnsafeHelper;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
#endif
|
||||
|
||||
@@ -1172,7 +1172,7 @@ public static unsafe partial class Methods
|
||||
{
|
||||
assert(rowEntries == 16 || rowEntries == 32 || rowEntries == 64);
|
||||
assert(rowEntries <= 64);
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
if (AdvSimd.IsSupported && BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (rowEntries == 16)
|
||||
@@ -1272,7 +1272,7 @@ public static unsafe partial class Methods
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
if (AdvSimd.IsSupported && BitConverter.IsLittleEndian)
|
||||
{
|
||||
if (rowEntries == 16)
|
||||
|
||||
@@ -23,12 +23,12 @@ public class AceFactory : Factory, IReaderFactory
|
||||
yield return "ace";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
AceHeader.IsArchive(stream);
|
||||
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => AceHeader.IsArchiveAsync(stream, cancellationToken);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class ArcFactory : Factory, IReaderFactory
|
||||
yield return "arc";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null)
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
//You may have to use some(paranoid) checks to ensure that you actually are
|
||||
//processing an ARC file, since other archivers also adopted the idea of putting
|
||||
@@ -63,7 +63,7 @@ public class ArcFactory : Factory, IReaderFactory
|
||||
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
|
||||
@@ -23,12 +23,12 @@ public class ArjFactory : Factory, IReaderFactory
|
||||
yield return "arj";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
ArjHeader.IsArchive(stream);
|
||||
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => ArjHeader.IsArchiveAsync(stream, cancellationToken);
|
||||
|
||||
|
||||
@@ -54,11 +54,10 @@ public abstract class Factory : IFactory
|
||||
public abstract IEnumerable<string> GetSupportedExtensions();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract bool IsArchive(Stream stream, string? password = null);
|
||||
|
||||
public abstract bool IsArchive(Stream stream, ReaderOptions readerOptions);
|
||||
public abstract ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -86,7 +85,7 @@ public abstract class Factory : IFactory
|
||||
if (this is IReaderFactory readerFactory)
|
||||
{
|
||||
stream.Rewind();
|
||||
if (IsArchive(stream, options.Password))
|
||||
if (IsArchive(stream, options))
|
||||
{
|
||||
stream.Rewind(true);
|
||||
reader = readerFactory.OpenReader(stream, options);
|
||||
@@ -106,10 +105,7 @@ public abstract class Factory : IFactory
|
||||
if (this is IReaderFactory readerFactory)
|
||||
{
|
||||
stream.Rewind();
|
||||
if (
|
||||
await IsArchiveAsync(stream, options.Password, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
if (await IsArchiveAsync(stream, options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
stream.Rewind(true);
|
||||
return await readerFactory
|
||||
|
||||
@@ -27,7 +27,7 @@ public class GZipFactory
|
||||
IMultiArchiveFactory,
|
||||
IReaderFactory,
|
||||
IWriterFactory,
|
||||
IWriteableArchiveFactory<GZipWriterOptions>
|
||||
IWritableArchiveFactory<GZipWriterOptions>
|
||||
{
|
||||
#region IFactory
|
||||
|
||||
@@ -44,13 +44,13 @@ public class GZipFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
GZipArchive.IsGZipFile(stream);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => GZipArchive.IsGZipFileAsync(stream, cancellationToken);
|
||||
|
||||
@@ -218,7 +218,7 @@ public class GZipFactory
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWriteableArchiveFactory
|
||||
#region IWritableArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IWritableArchive<GZipWriterOptions> CreateArchive() => GZipArchive.CreateArchive();
|
||||
|
||||
@@ -37,18 +37,18 @@ public interface IFactory
|
||||
/// Returns true if the stream represents an archive of the format defined by this type.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
|
||||
/// <param name="password">optional password</param>
|
||||
bool IsArchive(Stream stream, string? password = null);
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
bool IsArchive(Stream stream, ReaderOptions readerOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the stream represents an archive of the format defined by this type asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream, pointing to the beginning of the archive.</param>
|
||||
/// <param name="password">optional password</param>
|
||||
/// <param name="readerOptions">Options controlling archive detection.</param>
|
||||
/// <param name="cancellationToken">cancellation token</param>
|
||||
ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
|
||||
@@ -32,13 +32,13 @@ public class LzwFactory : Factory, IReaderFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
LzwStream.IsLzwStream(stream);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => LzwStream.IsLzwStreamAsync(stream, cancellationToken);
|
||||
|
||||
|
||||
@@ -31,15 +31,15 @@ public class RarFactory : Factory, IArchiveFactory, IMultiArchiveFactory, IReade
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
RarArchive.IsRarFile(stream);
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
RarArchive.IsRarFile(stream, readerOptions);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => RarArchive.IsRarFileAsync(stream, cancellationToken: cancellationToken);
|
||||
) => RarArchive.IsRarFileAsync(stream, readerOptions, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override FileInfo? GetFilePart(int index, FileInfo part1) =>
|
||||
|
||||
@@ -34,15 +34,15 @@ public class SevenZipFactory : Factory, IArchiveFactory, IMultiArchiveFactory, I
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
SevenZipArchive.IsSevenZipFile(stream);
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
SevenZipArchive.IsSevenZipFile(stream, readerOptions);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => SevenZipArchive.IsSevenZipFileAsync(stream, cancellationToken);
|
||||
) => SevenZipArchive.IsSevenZipFileAsync(stream, readerOptions, cancellationToken);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class TarFactory
|
||||
IMultiArchiveFactory,
|
||||
IReaderFactory,
|
||||
IWriterFactory,
|
||||
IWriteableArchiveFactory<TarWriterOptions>
|
||||
IWritableArchiveFactory<TarWriterOptions>
|
||||
{
|
||||
#region IFactory
|
||||
|
||||
@@ -48,8 +48,9 @@ public class TarFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null)
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var providers = readerOptions.Providers;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -76,10 +77,11 @@ public class TarFactory
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var providers = readerOptions.Providers;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -311,7 +313,7 @@ public class TarFactory
|
||||
/// <inheritdoc/>
|
||||
public IReader OpenReader(Stream stream, ReaderOptions? options)
|
||||
{
|
||||
options ??= new ReaderOptions();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -343,7 +345,7 @@ public class TarFactory
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
options ??= new ReaderOptions();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = new SharpCompressStream(stream);
|
||||
sharpCompressStream.StartRecording(TarWrapper.MaximumRewindBufferSize);
|
||||
foreach (var wrapper in TarWrapper.Wrappers)
|
||||
@@ -469,7 +471,7 @@ public class TarFactory
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWriteableArchiveFactory
|
||||
#region IWritableArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IWritableArchive<TarWriterOptions> CreateArchive() => TarArchive.CreateArchive();
|
||||
|
||||
@@ -21,12 +21,12 @@ internal class ZStandardFactory : Factory
|
||||
yield return "zstd";
|
||||
}
|
||||
|
||||
public override bool IsArchive(Stream stream, string? password = null) =>
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions) =>
|
||||
ZStandardStream.IsZStandard(stream);
|
||||
|
||||
public override ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
) => ZStandardStream.IsZStandardAsync(stream, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ZipFactory
|
||||
IMultiArchiveFactory,
|
||||
IReaderFactory,
|
||||
IWriterFactory,
|
||||
IWriteableArchiveFactory<ZipWriterOptions>
|
||||
IWritableArchiveFactory<ZipWriterOptions>
|
||||
{
|
||||
#region IFactory
|
||||
|
||||
@@ -43,10 +43,10 @@ public class ZipFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsArchive(Stream stream, string? password = null)
|
||||
public override bool IsArchive(Stream stream, ReaderOptions readerOptions)
|
||||
{
|
||||
var startPosition = stream.CanSeek ? stream.Position : -1;
|
||||
if (ZipArchive.IsZipFile(stream, password))
|
||||
if (ZipArchive.IsZipFile(stream, readerOptions.Password))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class ZipFactory
|
||||
stream.Position = startPosition;
|
||||
|
||||
//test the zip (last) file of a multipart zip
|
||||
if (ZipArchive.IsZipMulti(stream, password))
|
||||
if (ZipArchive.IsZipMulti(stream, readerOptions.Password))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class ZipFactory
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask<bool> IsArchiveAsync(
|
||||
Stream stream,
|
||||
string? password = null,
|
||||
ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
@@ -84,7 +84,7 @@ public class ZipFactory
|
||||
// probe for single volume zip
|
||||
if (
|
||||
await ZipArchive
|
||||
.IsZipFileAsync(stream, password, cancellationToken)
|
||||
.IsZipFileAsync(stream, readerOptions.Password, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
@@ -102,7 +102,7 @@ public class ZipFactory
|
||||
//test the zip (last) file of a multipart zip
|
||||
if (
|
||||
await ZipArchive
|
||||
.IsZipMultiAsync(stream, password, cancellationToken)
|
||||
.IsZipMultiAsync(stream, readerOptions.Password, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
)
|
||||
{
|
||||
@@ -246,7 +246,7 @@ public class ZipFactory
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWriteableArchiveFactory
|
||||
#region IWritableArchiveFactory
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IWritableArchive<ZipWriterOptions> CreateArchive() => ZipArchive.CreateArchive();
|
||||
|
||||
701
src/SharpCompress/IO/PooledMemoryStream.cs
Normal file
701
src/SharpCompress/IO/PooledMemoryStream.cs
Normal file
@@ -0,0 +1,701 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
|
||||
/// <summary>
|
||||
/// MemoryStream implementation backed by pooled byte arrays.
|
||||
/// Uses <see cref="ArrayPool{T}"/> to reduce GC pressure for temporary buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation is not thread-safe. Use appropriate synchronization for concurrent access.
|
||||
/// Buffers exposed via <see cref="GetBuffer"/> or <see cref="TryGetBuffer"/> are allocated as
|
||||
/// fresh non-pooled arrays to avoid exposing pooled memory.
|
||||
/// </remarks>
|
||||
public sealed class PooledMemoryStream : MemoryStream
|
||||
{
|
||||
private const int MaxStreamLength = int.MaxValue;
|
||||
|
||||
private readonly ArrayPool<byte> _arrayPool;
|
||||
private readonly int _blockSize;
|
||||
|
||||
private List<byte[]>? _blocks;
|
||||
private bool _isOpen;
|
||||
private int _position;
|
||||
private int _length;
|
||||
private int _capacity;
|
||||
|
||||
public PooledMemoryStream()
|
||||
: this(0) { }
|
||||
|
||||
public PooledMemoryStream(int capacity)
|
||||
: this(capacity, Constants.BufferSize, ArrayPool<byte>.Shared) { }
|
||||
|
||||
public PooledMemoryStream(int capacity, int blockSize)
|
||||
: this(capacity, blockSize, ArrayPool<byte>.Shared) { }
|
||||
|
||||
public PooledMemoryStream(int capacity, int blockSize, ArrayPool<byte> arrayPool)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(arrayPool, nameof(arrayPool));
|
||||
ThrowHelper.ThrowIfNegative(capacity, nameof(capacity));
|
||||
ThrowHelper.ThrowIfNegativeOrZero(blockSize, nameof(blockSize));
|
||||
|
||||
_arrayPool = arrayPool;
|
||||
_blockSize = blockSize;
|
||||
|
||||
_blocks = new List<byte[]>();
|
||||
_isOpen = true;
|
||||
_position = 0;
|
||||
_length = 0;
|
||||
_capacity = capacity;
|
||||
|
||||
EnsureSegmentedAllocated(capacity);
|
||||
}
|
||||
|
||||
public override bool CanRead => _isOpen;
|
||||
|
||||
public override bool CanSeek => _isOpen;
|
||||
|
||||
public override bool CanWrite => _isOpen;
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return _length;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return _position;
|
||||
}
|
||||
set
|
||||
{
|
||||
EnsureNotClosed();
|
||||
ThrowHelper.ThrowIfNegative(value, nameof(value));
|
||||
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
|
||||
|
||||
_position = (int)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return _capacity;
|
||||
}
|
||||
set
|
||||
{
|
||||
ThrowHelper.ThrowIfLessThan(value, _length, nameof(value));
|
||||
|
||||
EnsureNotClosed();
|
||||
|
||||
var target = value;
|
||||
if (target == _capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetCapacityAbsolute(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
EnsureNotClosed();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin loc)
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var anchor = loc switch
|
||||
{
|
||||
SeekOrigin.Begin => 0,
|
||||
SeekOrigin.Current => _position,
|
||||
SeekOrigin.End => _length,
|
||||
_ => throw new ArgumentException("Invalid seek origin.", nameof(loc)),
|
||||
};
|
||||
|
||||
var target = anchor + offset;
|
||||
if (target < 0)
|
||||
{
|
||||
throw new IOException("Attempted to seek before the beginning of the stream.");
|
||||
}
|
||||
|
||||
if (target > MaxStreamLength)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
_position = (int)target;
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
EnsureWritable();
|
||||
|
||||
ThrowHelper.ThrowIfNegative(value, nameof(value));
|
||||
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
|
||||
var newLength = (int)value;
|
||||
if (newLength > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(newLength);
|
||||
}
|
||||
|
||||
if (newLength > _length)
|
||||
{
|
||||
ClearRange(_length, newLength - _length);
|
||||
}
|
||||
|
||||
_length = newLength;
|
||||
if (_position > newLength)
|
||||
{
|
||||
_position = newLength;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ValidateReadWriteBufferArguments(buffer, offset, count);
|
||||
EnsureNotClosed();
|
||||
|
||||
var available = _length - _position;
|
||||
if (available <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count > available)
|
||||
{
|
||||
count = available;
|
||||
}
|
||||
|
||||
CopyFromSegmented(_position, buffer, offset, count);
|
||||
|
||||
_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
if (_position >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var blockIndex = _position / _blockSize;
|
||||
var blockOffset = _position % _blockSize;
|
||||
var value = _blocks![blockIndex][blockOffset];
|
||||
|
||||
_position++;
|
||||
return value;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ValidateReadWriteBufferArguments(buffer, offset, count);
|
||||
EnsureWritable();
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPosition = _position + count;
|
||||
if (endPosition < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (endPosition > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(endPosition);
|
||||
}
|
||||
|
||||
if (_position > _length)
|
||||
{
|
||||
ClearRange(_length, _position - _length);
|
||||
}
|
||||
|
||||
CopyToSegmented(_position, buffer, offset, count);
|
||||
|
||||
_position = endPosition;
|
||||
if (_position > _length)
|
||||
{
|
||||
_length = _position;
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
EnsureWritable();
|
||||
|
||||
var endPosition = _position + 1;
|
||||
if (endPosition < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (endPosition > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(endPosition);
|
||||
}
|
||||
|
||||
if (_position > _length)
|
||||
{
|
||||
ClearRange(_length, _position - _length);
|
||||
}
|
||||
|
||||
var blockIndex = _position / _blockSize;
|
||||
var blockOffset = _position % _blockSize;
|
||||
_blocks![blockIndex][blockOffset] = value;
|
||||
|
||||
_position = endPosition;
|
||||
if (_position > _length)
|
||||
{
|
||||
_length = _position;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] CreateExposableBuffer()
|
||||
{
|
||||
var exposable = new byte[_capacity];
|
||||
if (_length == 0)
|
||||
{
|
||||
return exposable;
|
||||
}
|
||||
|
||||
CopyFromSegmented(0, exposable, 0, _length);
|
||||
|
||||
return exposable;
|
||||
}
|
||||
|
||||
public override byte[] GetBuffer()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
return CreateExposableBuffer();
|
||||
}
|
||||
|
||||
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var exposableBuffer = CreateExposableBuffer();
|
||||
buffer = new ArraySegment<byte>(exposableBuffer, 0, _length);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override byte[] ToArray()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var count = _length;
|
||||
if (count == 0)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var copy = new byte[count];
|
||||
CopyFromSegmented(0, copy, 0, count);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public override void WriteTo(Stream stream)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(stream, nameof(stream));
|
||||
EnsureNotClosed();
|
||||
|
||||
var count = _length;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = 0;
|
||||
var remaining = count;
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = position / _blockSize;
|
||||
var blockOffset = position % _blockSize;
|
||||
var toWrite = Math.Min(remaining, _blockSize - blockOffset);
|
||||
stream.Write(_blocks![blockIndex], blockOffset, toWrite);
|
||||
position += toWrite;
|
||||
remaining -= toWrite;
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled<int>(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult(Read(buffer, offset, count));
|
||||
}
|
||||
|
||||
public override Task WriteAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return Task.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
Write(buffer, offset, count);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
EnsureNotClosed();
|
||||
|
||||
var available = _length - _position;
|
||||
if (available <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = Math.Min(available, buffer.Length);
|
||||
var sourcePosition = _position;
|
||||
var destinationOffset = 0;
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = sourcePosition / _blockSize;
|
||||
var blockOffset = sourcePosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
_blocks!
|
||||
[blockIndex]
|
||||
.AsSpan(blockOffset, toCopy)
|
||||
.CopyTo(buffer.Slice(destinationOffset, toCopy));
|
||||
|
||||
sourcePosition += toCopy;
|
||||
destinationOffset += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
|
||||
_position += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
EnsureWritable();
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPosition = _position + buffer.Length;
|
||||
if (endPosition < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (endPosition > _capacity)
|
||||
{
|
||||
EnsureCapacityForAppend(endPosition);
|
||||
}
|
||||
|
||||
if (_position > _length)
|
||||
{
|
||||
ClearRange(_length, _position - _length);
|
||||
}
|
||||
|
||||
var sourceOffset = 0;
|
||||
var destinationPosition = _position;
|
||||
var remaining = buffer.Length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = destinationPosition / _blockSize;
|
||||
var blockOffset = destinationPosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
|
||||
buffer
|
||||
.Slice(sourceOffset, toCopy)
|
||||
.CopyTo(_blocks![blockIndex].AsSpan(blockOffset, toCopy));
|
||||
|
||||
sourceOffset += toCopy;
|
||||
destinationPosition += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
|
||||
_position = endPosition;
|
||||
if (_position > _length)
|
||||
{
|
||||
_length = _position;
|
||||
}
|
||||
}
|
||||
|
||||
public override ValueTask<int> ReadAsync(
|
||||
Memory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ValueTask.FromCanceled<int>(cancellationToken);
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(Read(buffer.Span));
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(
|
||||
ReadOnlyMemory<byte> buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ValueTask.FromCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
Write(buffer.Span);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isOpen)
|
||||
{
|
||||
_isOpen = false;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ReturnPooledBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void EnsureNotClosed()
|
||||
{
|
||||
if (!_isOpen)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(PooledMemoryStream));
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureWritable()
|
||||
{
|
||||
EnsureNotClosed();
|
||||
}
|
||||
|
||||
private void EnsureCapacityForAppend(int requiredLength)
|
||||
{
|
||||
if (requiredLength < 0)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
if (requiredLength <= _capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nextCapacity = RoundUpToBlockBoundary(requiredLength);
|
||||
SetCapacityAbsolute(nextCapacity);
|
||||
}
|
||||
|
||||
private void SetCapacityAbsolute(int newCapacity)
|
||||
{
|
||||
ThrowHelper.ThrowIfLessThan(newCapacity, _length, nameof(newCapacity));
|
||||
|
||||
EnsureSegmentedAllocated(newCapacity);
|
||||
|
||||
_capacity = newCapacity;
|
||||
if (_length > _capacity)
|
||||
{
|
||||
_length = _capacity;
|
||||
}
|
||||
if (_position > _capacity)
|
||||
{
|
||||
_position = _capacity;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSegmentedAllocated(int capacity)
|
||||
{
|
||||
var requiredAllocated = RoundUpToBlockBoundary(capacity);
|
||||
var requiredBlocks = requiredAllocated == 0 ? 0 : requiredAllocated / _blockSize;
|
||||
|
||||
_blocks ??= new List<byte[]>();
|
||||
|
||||
while (_blocks.Count < requiredBlocks)
|
||||
{
|
||||
_blocks.Add(_arrayPool.Rent(_blockSize));
|
||||
}
|
||||
|
||||
while (_blocks.Count > requiredBlocks)
|
||||
{
|
||||
var index = _blocks.Count - 1;
|
||||
var block = _blocks[index];
|
||||
_blocks.RemoveAt(index);
|
||||
_arrayPool.Return(block);
|
||||
}
|
||||
}
|
||||
|
||||
private int RoundUpToBlockBoundary(int value)
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var rounded = ((long)value + _blockSize - 1) / _blockSize * _blockSize;
|
||||
if (rounded > MaxStreamLength)
|
||||
{
|
||||
throw new IOException("Stream is too long.");
|
||||
}
|
||||
|
||||
return (int)rounded;
|
||||
}
|
||||
|
||||
private void ClearRange(int absoluteStart, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var position = absoluteStart;
|
||||
var remaining = count;
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = position / _blockSize;
|
||||
var blockOffset = position % _blockSize;
|
||||
var toClear = Math.Min(remaining, _blockSize - blockOffset);
|
||||
Array.Clear(_blocks![blockIndex], blockOffset, toClear);
|
||||
position += toClear;
|
||||
remaining -= toClear;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyFromSegmented(
|
||||
int absoluteSourcePosition,
|
||||
byte[] destination,
|
||||
int offset,
|
||||
int count
|
||||
)
|
||||
{
|
||||
var sourcePosition = absoluteSourcePosition;
|
||||
var destinationOffset = offset;
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = sourcePosition / _blockSize;
|
||||
var blockOffset = sourcePosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
Buffer.BlockCopy(
|
||||
_blocks![blockIndex],
|
||||
blockOffset,
|
||||
destination,
|
||||
destinationOffset,
|
||||
toCopy
|
||||
);
|
||||
|
||||
sourcePosition += toCopy;
|
||||
destinationOffset += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToSegmented(
|
||||
int absoluteDestinationPosition,
|
||||
byte[] source,
|
||||
int offset,
|
||||
int count
|
||||
)
|
||||
{
|
||||
var sourceOffset = offset;
|
||||
var destinationPosition = absoluteDestinationPosition;
|
||||
var remaining = count;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
var blockIndex = destinationPosition / _blockSize;
|
||||
var blockOffset = destinationPosition % _blockSize;
|
||||
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
|
||||
Buffer.BlockCopy(source, sourceOffset, _blocks![blockIndex], blockOffset, toCopy);
|
||||
|
||||
sourceOffset += toCopy;
|
||||
destinationPosition += toCopy;
|
||||
remaining -= toCopy;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnSegmentedBlocks()
|
||||
{
|
||||
if (_blocks is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
_arrayPool.Return(_blocks[i]);
|
||||
}
|
||||
|
||||
_blocks.Clear();
|
||||
}
|
||||
|
||||
private void ReturnPooledBuffers()
|
||||
{
|
||||
ReturnSegmentedBlocks();
|
||||
_blocks = null;
|
||||
}
|
||||
|
||||
private static void ValidateReadWriteBufferArguments(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(buffer, nameof(buffer));
|
||||
ThrowHelper.ThrowIfNegative(offset, nameof(offset));
|
||||
ThrowHelper.ThrowIfNegative(count, nameof(count));
|
||||
if (buffer.Length - offset < count)
|
||||
{
|
||||
throw new ArgumentException("Offset and length are out of bounds.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,11 +203,17 @@ public partial class SharpCompressStream : Stream, IStreamStack
|
||||
// Allocate ring buffer with the requested minimum size (at least the global default).
|
||||
if (_ringBuffer is null)
|
||||
{
|
||||
var size =
|
||||
var requiredSize =
|
||||
minBufferSize.GetValueOrDefault() > Constants.RewindableBufferSize
|
||||
? minBufferSize.GetValueOrDefault()
|
||||
: Constants.RewindableBufferSize;
|
||||
_ringBuffer = new RingBuffer(size);
|
||||
_ringBuffer = new RingBuffer(requiredSize);
|
||||
}
|
||||
else if (minBufferSize.HasValue && minBufferSize.Value > _ringBuffer.Capacity)
|
||||
{
|
||||
throw new ArchiveOperationException(
|
||||
$"StartRecording requires a ring buffer of at least {minBufferSize.Value} bytes, but the stream was created with capacity {_ringBuffer.Capacity}."
|
||||
);
|
||||
}
|
||||
|
||||
// Mark current position as recording anchor
|
||||
|
||||
@@ -28,7 +28,7 @@ public static class StreamExtensions
|
||||
public async ValueTask SkipAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
#if NET5_0_OR_GREATER
|
||||
#if NET6_0_OR_GREATER
|
||||
await stream.CopyToAsync(Stream.Null, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
await stream.CopyToAsync(Stream.Null).ConfigureAwait(false);
|
||||
|
||||
@@ -22,20 +22,16 @@ public abstract partial class AbstractReader<TEntry, TVolume> : IReader, IAsyncR
|
||||
private bool _wroteCurrentEntry;
|
||||
private readonly bool _disposeVolume;
|
||||
|
||||
internal AbstractReader(
|
||||
ReaderOptions options,
|
||||
ArchiveType archiveType,
|
||||
bool disposeVolume = true
|
||||
)
|
||||
internal AbstractReader(ReaderOptions options, ArchiveType type, bool disposeVolume = true)
|
||||
{
|
||||
ArchiveType = archiveType;
|
||||
Type = type;
|
||||
_disposeVolume = disposeVolume;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
internal ReaderOptions Options { get; }
|
||||
|
||||
public ArchiveType ArchiveType { get; }
|
||||
public ArchiveType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current volume that the current entry resides in
|
||||
|
||||
@@ -19,8 +19,8 @@ public partial class AceReader
|
||||
/// <returns>An AceReader instance.</returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeAceReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new SingleVolumeAceReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -31,19 +31,19 @@ public partial class AceReader
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeAceReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -61,8 +61,8 @@ public partial class AceReader
|
||||
ReaderOptions? options = null
|
||||
)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeAceReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeAceReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -84,6 +84,7 @@ public partial class AceReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ internal class SingleVolumeAceReader : AceReader
|
||||
internal SingleVolumeAceReader(Stream stream, ReaderOptions options)
|
||||
: base(options)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Arc;
|
||||
public partial class ArcReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -48,6 +48,7 @@ public partial class ArcReader : IReaderOpenable
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ public partial class ArcReader : AbstractReader<ArcEntry, ArcVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ArcReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new ArcReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
protected override IEnumerable<ArcEntry> GetEntries(Stream stream)
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Arj;
|
||||
public partial class ArjReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -48,6 +48,7 @@ public partial class ArjReader : IReaderOpenable
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ public abstract partial class ArjReader : AbstractReader<ArjEntry, ArjVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeArjReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new SingleVolumeArjReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,8 +43,8 @@ public abstract partial class ArjReader : AbstractReader<ArjEntry, ArjVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeArjReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeArjReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
protected abstract void ValidateArchive(ArjVolume archive);
|
||||
|
||||
@@ -12,7 +12,7 @@ internal class SingleVolumeArjReader : ArjReader
|
||||
internal SingleVolumeArjReader(Stream stream, ReaderOptions options)
|
||||
: base(options)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
_stream = stream;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ public partial class GZipReader
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -49,12 +49,13 @@ public partial class GZipReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new GZipReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new GZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace SharpCompress.Readers;
|
||||
|
||||
public interface IAsyncReader : IAsyncDisposable
|
||||
{
|
||||
ArchiveType ArchiveType { get; }
|
||||
ArchiveType Type { get; }
|
||||
|
||||
IEntry Entry { get; }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace SharpCompress.Readers;
|
||||
|
||||
public interface IReader : IDisposable
|
||||
{
|
||||
ArchiveType ArchiveType { get; }
|
||||
ArchiveType Type { get; }
|
||||
|
||||
IEntry Entry { get; }
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@ public interface IReaderFactory : Factories.IFactory
|
||||
ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
Stream stream,
|
||||
ReaderOptions? options,
|
||||
CancellationToken cancellationToken
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public interface IReaderOpenable
|
||||
public static abstract IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null);
|
||||
|
||||
public static abstract ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
@@ -10,14 +10,14 @@ public partial class LzwReader
|
||||
#endif
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -49,12 +49,13 @@ public partial class LzwReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new LzwReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new LzwReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Rar;
|
||||
public partial class RarReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
|
||||
@@ -49,7 +49,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
readerOptions ??= new ReaderOptions { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
|
||||
public static IReader OpenReader(IEnumerable<FileInfo> fileInfos, ReaderOptions? options = null)
|
||||
{
|
||||
options ??= new ReaderOptions { LeaveStreamOpen = false };
|
||||
options ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfos.Select(x => x.OpenRead()), options);
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new SingleVolumeRarReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new SingleVolumeRarReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,8 +84,8 @@ public abstract partial class RarReader : AbstractReader<RarReaderEntry, RarVolu
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(IEnumerable<Stream> streams, ReaderOptions? options = null)
|
||||
{
|
||||
streams.NotNull(nameof(streams));
|
||||
return new MultiVolumeRarReader(streams, options ?? new ReaderOptions());
|
||||
var streamArray = streams.RequireReadable();
|
||||
return new MultiVolumeRarReader(streamArray, options ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
protected override IEnumerable<RarReaderEntry> GetEntries(Stream stream)
|
||||
|
||||
@@ -25,7 +25,11 @@ public static partial class ReaderFactory
|
||||
)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncReader(new FileInfo(filePath), options, cancellationToken);
|
||||
return OpenAsyncReader(
|
||||
new FileInfo(filePath),
|
||||
options ?? ReaderOptions.ForFilePath,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,7 +56,7 @@ public static partial class ReaderFactory
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
|
||||
@@ -12,7 +12,7 @@ public static partial class ReaderFactory
|
||||
public static IReader OpenReader(string filePath, ReaderOptions? options = null)
|
||||
{
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenReader(new FileInfo(filePath), options);
|
||||
return OpenReader(new FileInfo(filePath), options ?? ReaderOptions.ForFilePath);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? options = null)
|
||||
@@ -29,7 +29,7 @@ public static partial class ReaderFactory
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? options = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireReadable();
|
||||
options ??= ReaderOptions.ForExternalStream;
|
||||
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
|
||||
@@ -24,9 +24,36 @@ namespace SharpCompress.Readers;
|
||||
public sealed record ReaderOptions : IReaderOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// SharpCompress will keep the supplied streams open. Default is true.
|
||||
/// Whether SharpCompress leaves the supplied streams open when the reader/archive is disposed.
|
||||
/// As of v0.21, the library is documented to close streams by default; this option now defaults to false.
|
||||
/// Set to true when passing caller-owned streams that should not be disposed.
|
||||
/// </summary>
|
||||
public bool LeaveStreamOpen { get; init; } = true;
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Default behavior (LeaveStreamOpen = false):</b>
|
||||
/// When you open an archive from a file path (e.g., <c>GZipArchive.OpenArchive(filePath)</c>),
|
||||
/// SharpCompress manages the stream lifetime and closes it on Dispose.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Caller-provided streams (LeaveStreamOpen = true):</b>
|
||||
/// When you pass a stream you created (FileStream, MemoryStream, NetworkStream, etc.),
|
||||
/// set LeaveStreamOpen = true to prevent SharpCompress from disposing it.
|
||||
/// Use <see cref="ForExternalStream"/> preset for convenience.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Example:</b>
|
||||
/// <code>
|
||||
/// // File-based: stream managed by library
|
||||
/// using var archive = GZipArchive.OpenArchive(filePath); // LeaveStreamOpen = false
|
||||
///
|
||||
/// // Caller-provided stream: caller manages lifetime
|
||||
/// using var stream = File.OpenRead(filePath);
|
||||
/// var options = new ReaderOptions { LeaveStreamOpen = true };
|
||||
/// using var archive = GZipArchive.OpenArchive(stream, options);
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool LeaveStreamOpen { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Encoding to use for archive entry names.
|
||||
@@ -124,35 +151,34 @@ public sealed record ReaderOptions : IReaderOptions
|
||||
/// <summary>
|
||||
/// Gets ReaderOptions configured for caller-provided streams.
|
||||
/// </summary>
|
||||
public static ReaderOptions ForExternalStream => new() { LeaveStreamOpen = true };
|
||||
internal static ReaderOptions Default => new();
|
||||
|
||||
public static ReaderOptions ForExternalStream => Default.WithLeaveStreamOpen(true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets ReaderOptions configured for file-based overloads that open their own stream.
|
||||
/// </summary>
|
||||
public static ReaderOptions ForFilePath => new() { LeaveStreamOpen = false };
|
||||
public static ReaderOptions ForFilePath => Default;
|
||||
|
||||
/// <summary>
|
||||
/// Creates ReaderOptions for reading encrypted archives.
|
||||
/// </summary>
|
||||
/// <param name="password">The password for encrypted archives.</param>
|
||||
public static ReaderOptions ForEncryptedArchive(string? password = null) =>
|
||||
new ReaderOptions().WithPassword(password);
|
||||
Default.WithPassword(password);
|
||||
|
||||
/// <summary>
|
||||
/// Creates ReaderOptions for archives with custom character encoding.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding for archive entry names.</param>
|
||||
public static ReaderOptions ForEncoding(IArchiveEncoding encoding) =>
|
||||
new ReaderOptions().WithArchiveEncoding(encoding);
|
||||
Default.WithArchiveEncoding(encoding);
|
||||
|
||||
/// <summary>
|
||||
/// Creates ReaderOptions for self-extracting archives that require header search.
|
||||
/// </summary>
|
||||
public static ReaderOptions ForSelfExtractingArchive(string? password = null) =>
|
||||
new ReaderOptions()
|
||||
.WithLookForHeader(true)
|
||||
.WithPassword(password)
|
||||
.WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
|
||||
Default.WithLookForHeader(true).WithPassword(password).WithRewindableBufferSize(1_048_576); // 1MB for SFX archives
|
||||
|
||||
// Note: Parameterized constructors have been removed.
|
||||
// Use fluent With*() helpers or object initializers instead:
|
||||
|
||||
@@ -72,13 +72,13 @@ public partial class TarReader
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return OpenAsyncReader(new FileInfo(path), readerOptions, cancellationToken);
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsyncReader(new FileInfo(filePath), readerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -89,7 +89,7 @@ public partial class TarReader
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
stream.NotNull(nameof(stream));
|
||||
readerOptions ??= new ReaderOptions();
|
||||
readerOptions ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
stream,
|
||||
bufferSize: Math.Max(
|
||||
@@ -143,7 +143,7 @@ public partial class TarReader
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
var stream = fileInfo.OpenAsyncReadStream(cancellationToken);
|
||||
return await OpenAsyncReader(stream, readerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -158,7 +158,7 @@ public partial class TarReader
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= new ReaderOptions() { LeaveStreamOpen = false };
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
|
||||
@@ -170,8 +170,8 @@ public partial class TarReader
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
readerOptions ??= new ReaderOptions();
|
||||
stream.RequireReadable();
|
||||
readerOptions ??= ReaderOptions.ForExternalStream;
|
||||
var sharpCompressStream = SharpCompressStream.Create(
|
||||
stream,
|
||||
bufferSize: Math.Max(
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace SharpCompress.Readers.Zip;
|
||||
public partial class ZipReader : IReaderOpenable
|
||||
{
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
string path,
|
||||
string filePath,
|
||||
ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
path.NotNullOrEmpty(nameof(path));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(path), readerOptions));
|
||||
filePath.NotNullOrEmpty(nameof(filePath));
|
||||
return new((IAsyncReader)OpenReader(new FileInfo(filePath), readerOptions));
|
||||
}
|
||||
|
||||
public static ValueTask<IAsyncReader> OpenAsyncReader(
|
||||
@@ -48,6 +48,7 @@ public partial class ZipReader : IReaderOpenable
|
||||
public static IReader OpenReader(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
readerOptions ??= ReaderOptions.ForFilePath;
|
||||
return OpenReader(fileInfo.OpenRead(), readerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
|
||||
/// <returns></returns>
|
||||
public static IReader OpenReader(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ZipReader(stream, readerOptions ?? new ReaderOptions());
|
||||
stream.RequireReadable();
|
||||
return new ZipReader(stream, readerOptions ?? ReaderOptions.ForExternalStream);
|
||||
}
|
||||
|
||||
public static IReader OpenReader(
|
||||
@@ -57,8 +57,8 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
|
||||
IEnumerable<ZipEntry> entries
|
||||
)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
return new ZipReader(stream, options ?? new ReaderOptions(), entries);
|
||||
stream.RequireReadable();
|
||||
return new ZipReader(stream, options ?? ReaderOptions.ForExternalStream, entries);
|
||||
}
|
||||
|
||||
#endregion Open
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<AssemblyVersion>0.0.0.0</AssemblyVersion>
|
||||
<FileVersion>0.0.0.0</FileVersion>
|
||||
<Authors>Adam Hathcock</Authors>
|
||||
<TargetFrameworks>net48;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<TargetFrameworks>net48;netstandard2.0;netstandard2.1;net6.0;net8.0;net10.0</TargetFrameworks>
|
||||
<AssemblyName>SharpCompress</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>../../SharpCompress.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
56
src/SharpCompress/StreamValidationExtensions.cs
Normal file
56
src/SharpCompress/StreamValidationExtensions.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress;
|
||||
|
||||
internal static class StreamValidationExtensions
|
||||
{
|
||||
internal static void RequireReadable(this Stream stream)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException("Stream must be readable", nameof(stream));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RequireSeekable(this Stream stream)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Stream must be seekable", nameof(stream));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RequireWritable(this Stream stream)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
|
||||
if (!stream.CanWrite)
|
||||
{
|
||||
throw new ArgumentException("Stream must be writable", nameof(stream));
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Stream> RequireSeekable(this IEnumerable<Stream> streams)
|
||||
{
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.RequireSeekable();
|
||||
yield return stream;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<Stream> RequireReadable(this IEnumerable<Stream> streams)
|
||||
{
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.RequireReadable();
|
||||
yield return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,15 @@ internal static class ThrowHelper
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowIfGreaterThan(long value, long other, string? paramName = null)
|
||||
{
|
||||
if (value > other)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowIfGreaterThan(uint value, uint other, string? paramName = null)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract partial class AbstractWriter(ArchiveType type, IWriterOptions wr
|
||||
|
||||
protected Stream? OutputStream { get; private set; }
|
||||
|
||||
public ArchiveType WriterType { get; } = type;
|
||||
public ArchiveType Type { get; } = type;
|
||||
|
||||
protected IWriterOptions WriterOptions { get; } = writerOptions;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers.GZip;
|
||||
@@ -15,28 +17,43 @@ public partial class GZipWriter : IWriterOpenable<GZipWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, GZipWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new GZipWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new GZipWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
|
||||
}
|
||||
|
||||
public static IWriter OpenWriter(Stream stream, GZipWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new GZipWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(string stream, GZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
GZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, GZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
GZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, GZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
GZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace SharpCompress.Writers;
|
||||
|
||||
public interface IWriter : IDisposable
|
||||
{
|
||||
ArchiveType WriterType { get; }
|
||||
ArchiveType Type { get; }
|
||||
void Write(string filename, Stream source, DateTime? modificationTime);
|
||||
void WriteDirectory(string directoryName, DateTime? modificationTime);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Options;
|
||||
|
||||
namespace SharpCompress.Writers;
|
||||
@@ -17,22 +18,25 @@ public interface IWriterOpenable<TWriterOptions>
|
||||
/// Opens a Writer asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write to.</param>
|
||||
/// <param name="archiveType">The archive type.</param>
|
||||
/// <param name="writerOptions">Writer options.</param>
|
||||
/// <returns>A task that returns an IWriter.</returns>
|
||||
public static abstract IAsyncWriter OpenAsyncWriter(
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that returns an async writer.</returns>
|
||||
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
TWriterOptions writerOptions
|
||||
TWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public static abstract IAsyncWriter OpenAsyncWriter(
|
||||
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
TWriterOptions writerOptions
|
||||
TWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public static abstract IAsyncWriter OpenAsyncWriter(
|
||||
public static abstract ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
TWriterOptions writerOptions
|
||||
TWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Writers.SevenZip;
|
||||
|
||||
@@ -20,7 +22,13 @@ public partial class SevenZipWriter : IWriterOpenable<SevenZipWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, SevenZipWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new SevenZipWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new SevenZipWriter(
|
||||
fileInfo.OpenWrite(),
|
||||
writerOptions with
|
||||
{
|
||||
LeaveStreamOpen = false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -28,35 +36,47 @@ public partial class SevenZipWriter : IWriterOpenable<SevenZipWriterOptions>
|
||||
/// </summary>
|
||||
public static IWriter OpenWriter(Stream stream, SevenZipWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new SevenZipWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new async SevenZipWriter for the specified file path.
|
||||
/// </summary>
|
||||
public static IAsyncWriter OpenAsyncWriter(string filePath, SevenZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
SevenZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(filePath, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new async SevenZipWriter for the specified stream.
|
||||
/// </summary>
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, SevenZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
SevenZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new async SevenZipWriter for the specified file.
|
||||
/// </summary>
|
||||
public static IAsyncWriter OpenAsyncWriter(
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
SevenZipWriterOptions writerOptions
|
||||
SevenZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -203,7 +203,7 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
var filesInfo = new SevenZipFilesInfoWriter { Entries = entries.ToArray() };
|
||||
|
||||
// Write header to a temporary stream first
|
||||
using var headerStream = new MemoryStream();
|
||||
using var headerStream = new PooledMemoryStream();
|
||||
ArchiveHeaderWriter.WriteRawHeader(headerStream, mainStreamsInfo, filesInfo);
|
||||
|
||||
// Optionally compress the header
|
||||
@@ -244,7 +244,7 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
};
|
||||
|
||||
// Write encoded header to a second temporary stream
|
||||
using var encodedHeaderStream = new MemoryStream();
|
||||
using var encodedHeaderStream = new PooledMemoryStream();
|
||||
ArchiveHeaderWriter.WriteEncodedHeader(encodedHeaderStream, headerStreamsInfo);
|
||||
|
||||
// Write the encoded header to the output
|
||||
@@ -252,12 +252,10 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
encodedHeaderStream.Position = 0;
|
||||
encodedHeaderStream.CopyTo(output);
|
||||
|
||||
// Compute CRC of the encoded header
|
||||
var headerCrc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
encodedHeaderStream.GetBuffer().AsSpan(0, (int)encodedHeaderStream.Length)
|
||||
);
|
||||
// Compute CRC of the encoded header without allocating a contiguous buffer
|
||||
var encodedHeaderCrcSink = new Crc32Stream(Stream.Null);
|
||||
encodedHeaderStream.WriteTo(encodedHeaderCrcSink);
|
||||
var headerCrc = encodedHeaderCrcSink.Crc;
|
||||
|
||||
// Back-patch signature header
|
||||
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
|
||||
@@ -283,12 +281,10 @@ public partial class SevenZipWriter : AbstractWriter
|
||||
rawHeaderStream.Position = 0;
|
||||
rawHeaderStream.CopyTo(output);
|
||||
|
||||
// Compute CRC of the raw header
|
||||
var headerCrc = Crc32Stream.Compute(
|
||||
Crc32Stream.DEFAULT_POLYNOMIAL,
|
||||
Crc32Stream.DEFAULT_SEED,
|
||||
rawHeaderStream.GetBuffer().AsSpan(0, (int)rawHeaderStream.Length)
|
||||
);
|
||||
// Compute CRC of the raw header without allocating a contiguous buffer
|
||||
var rawHeaderCrcSink = new Crc32Stream(Stream.Null);
|
||||
rawHeaderStream.WriteTo(rawHeaderCrcSink);
|
||||
var headerCrc = rawHeaderCrcSink.Crc;
|
||||
|
||||
// Back-patch signature header
|
||||
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers.Tar;
|
||||
@@ -15,28 +17,43 @@ public partial class TarWriter : IWriterOpenable<TarWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, TarWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new TarWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new TarWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
|
||||
}
|
||||
|
||||
public static IWriter OpenWriter(Stream stream, TarWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new TarWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(string stream, TarWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
TarWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, TarWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
TarWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, TarWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
TarWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -75,6 +75,8 @@ public static class WriterFactory
|
||||
IWriterOptions writerOptions
|
||||
)
|
||||
{
|
||||
stream.RequireWritable();
|
||||
|
||||
var factory = Factories
|
||||
.Factory.Factories.OfType<IWriterFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == archiveType);
|
||||
@@ -102,6 +104,8 @@ public static class WriterFactory
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
stream.RequireWritable();
|
||||
|
||||
var factory = Factories
|
||||
.Factory.Factories.OfType<IWriterFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == archiveType);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Options;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Providers;
|
||||
using D = SharpCompress.Compressors.Deflate;
|
||||
|
||||
@@ -18,17 +17,10 @@ namespace SharpCompress.Writers;
|
||||
/// </remarks>
|
||||
public sealed record WriterOptions : IWriterOptions
|
||||
{
|
||||
private CompressionType _compressionType;
|
||||
private int _compressionLevel;
|
||||
|
||||
/// <summary>
|
||||
/// The compression type to use for the archive.
|
||||
/// </summary>
|
||||
public CompressionType CompressionType
|
||||
{
|
||||
get => _compressionType;
|
||||
init => _compressionType = value;
|
||||
}
|
||||
public CompressionType CompressionType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The compression level to be used when the compression type supports variable levels.
|
||||
@@ -40,11 +32,11 @@ public sealed record WriterOptions : IWriterOptions
|
||||
/// </summary>
|
||||
public int CompressionLevel
|
||||
{
|
||||
get => _compressionLevel;
|
||||
get;
|
||||
init
|
||||
{
|
||||
CompressionLevelValidation.Validate(CompressionType, value);
|
||||
_compressionLevel = value;
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Writers.Zip;
|
||||
@@ -15,28 +17,43 @@ public partial class ZipWriter : IWriterOpenable<ZipWriterOptions>
|
||||
public static IWriter OpenWriter(FileInfo fileInfo, ZipWriterOptions writerOptions)
|
||||
{
|
||||
fileInfo.NotNull(nameof(fileInfo));
|
||||
return new ZipWriter(fileInfo.OpenWrite(), writerOptions);
|
||||
return new ZipWriter(fileInfo.OpenWrite(), writerOptions with { LeaveStreamOpen = false });
|
||||
}
|
||||
|
||||
public static IWriter OpenWriter(Stream stream, ZipWriterOptions writerOptions)
|
||||
{
|
||||
stream.NotNull(nameof(stream));
|
||||
stream.RequireWritable();
|
||||
return new ZipWriter(stream, writerOptions);
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(string stream, ZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
string filePath,
|
||||
ZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(filePath, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(Stream stream, ZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
Stream stream,
|
||||
ZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(stream, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(stream, writerOptions));
|
||||
}
|
||||
|
||||
public static IAsyncWriter OpenAsyncWriter(FileInfo fileInfo, ZipWriterOptions writerOptions)
|
||||
public static ValueTask<IAsyncWriter> OpenAsyncWriter(
|
||||
FileInfo fileInfo,
|
||||
ZipWriterOptions writerOptions,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
return (IAsyncWriter)OpenWriter(fileInfo, writerOptions);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new((IAsyncWriter)OpenWriter(fileInfo, writerOptions));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -313,48 +313,6 @@
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
".NETCoreApp,Version=v5.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.3, )",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.102, )",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "10.0.102",
|
||||
"Microsoft.SourceLink.Common": "10.0.102"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.14.15, )",
|
||||
"resolved": "17.14.15",
|
||||
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net6.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
@@ -397,48 +355,6 @@
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net7.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.3, )",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.102, )",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "10.0.102",
|
||||
"Microsoft.SourceLink.Common": "10.0.102"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.14.15, )",
|
||||
"resolved": "17.14.15",
|
||||
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net8.0": {
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
@@ -486,48 +402,6 @@
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
},
|
||||
"net9.0": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.0.3, )",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
|
||||
}
|
||||
},
|
||||
"Microsoft.SourceLink.GitHub": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.102, )",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Oxq3RCIJSdtpIU4hLqO7XaDe/Ra3HS9Wi8rJl838SAg6Zu1iQjerA0+xXWBgUFYbgknUGCLOU0T+lzMLkvY9Qg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Build.Tasks.Git": "10.0.102",
|
||||
"Microsoft.SourceLink.Common": "10.0.102"
|
||||
}
|
||||
},
|
||||
"Microsoft.VisualStudio.Threading.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[17.14.15, )",
|
||||
"resolved": "17.14.15",
|
||||
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
|
||||
},
|
||||
"Microsoft.Build.Tasks.Git": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "0i81LYX31U6UiXz4NOLbvc++u+/mVDmOt+PskrM/MygpDxkv9THKQyRUmavBpLK6iBV0abNWnn+CQgSRz//Pwg=="
|
||||
},
|
||||
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.3",
|
||||
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
|
||||
},
|
||||
"Microsoft.SourceLink.Common": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.102",
|
||||
"contentHash": "Mk1IMb9q5tahC2NltxYXFkLBtuBvfBoCQ3pIxYQWfzbCE9o1OB9SsHe0hnNGo7lWgTA/ePbFAJLWu6nLL9K17A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user