more providers

This commit is contained in:
Adam Hathcock
2026-02-09 07:34:00 +00:00
parent c48388ead2
commit 9c7d27d1e0
36 changed files with 1211 additions and 158 deletions

View File

@@ -84,7 +84,11 @@ public partial class GZipArchive
var stream = (await volumes.SingleAsync()).Stream;
yield return new GZipArchiveEntry(
this,
await GZipFilePart.CreateAsync(stream, ReaderOptions.ArchiveEncoding)
await GZipFilePart.CreateAsync(
stream,
ReaderOptions.ArchiveEncoding,
ReaderOptions.CompressionProviders
)
);
}
}

View File

@@ -88,7 +88,11 @@ public partial class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZi
var stream = volumes.Single().Stream;
yield return new GZipArchiveEntry(
this,
GZipFilePart.Create(stream, ReaderOptions.ArchiveEncoding)
GZipFilePart.Create(
stream,
ReaderOptions.ArchiveEncoding,
ReaderOptions.CompressionProviders
)
);
}

View File

@@ -55,7 +55,12 @@ public partial class ZipArchive
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
new SeekableZipFilePart(
headerFactory.NotNull(),
deh,
s,
ReaderOptions.CompressionProviders
)
);
}
break;

View File

@@ -95,7 +95,12 @@ public partial class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVo
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
new SeekableZipFilePart(
headerFactory.NotNull(),
deh,
s,
ReaderOptions.CompressionProviders
)
);
}
break;

View File

@@ -11,6 +11,12 @@ public partial class GZipEntry
ReaderOptions options
)
{
yield return new GZipEntry(await GZipFilePart.CreateAsync(stream, options.ArchiveEncoding));
yield return new GZipEntry(
await GZipFilePart.CreateAsync(
stream,
options.ArchiveEncoding,
options.CompressionProviders
)
);
}
}

View File

@@ -41,7 +41,9 @@ public partial class GZipEntry : Entry
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, ReaderOptions options)
{
yield return new GZipEntry(GZipFilePart.Create(stream, options.ArchiveEncoding));
yield return new GZipEntry(
GZipFilePart.Create(stream, options.ArchiveEncoding, options.CompressionProviders)
);
}
// Async methods moved to GZipEntry.Async.cs

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
namespace SharpCompress.Common.GZip;
@@ -17,7 +18,17 @@ internal sealed partial class GZipFilePart
CancellationToken cancellationToken = default
)
{
var part = new GZipFilePart(stream, archiveEncoding);
return await CreateAsync(stream, archiveEncoding, null, cancellationToken);
}
internal static async ValueTask<GZipFilePart> CreateAsync(
Stream stream,
IArchiveEncoding archiveEncoding,
CompressionProviderRegistry? compressionProviders,
CancellationToken cancellationToken = default
)
{
var part = new GZipFilePart(stream, archiveEncoding, compressionProviders);
await part.ReadAndValidateGzipHeaderAsync(cancellationToken);
if (stream.CanSeek)

View File

@@ -12,10 +12,37 @@ internal sealed partial class GZipFilePart : FilePart
{
private string? _name;
private readonly Stream _stream;
private readonly CompressionProviderRegistry? _compressionProviders;
internal static GZipFilePart Create(Stream stream, IArchiveEncoding archiveEncoding)
{
var part = new GZipFilePart(stream, archiveEncoding);
var part = new GZipFilePart(stream, archiveEncoding, null);
part.ReadAndValidateGzipHeader();
if (stream.CanSeek)
{
var position = stream.Position;
stream.Position = stream.Length - 8;
part.ReadTrailer();
stream.Position = position;
part.EntryStartPosition = position;
}
else
{
// For non-seekable streams, we can't read the trailer or track position.
// Set to 0 since the stream will be read sequentially from its current position.
part.EntryStartPosition = 0;
}
return part;
}
internal static GZipFilePart Create(
Stream stream,
IArchiveEncoding archiveEncoding,
CompressionProviderRegistry? compressionProviders
)
{
var part = new GZipFilePart(stream, archiveEncoding, compressionProviders);
part.ReadAndValidateGzipHeader();
if (stream.CanSeek)
@@ -38,6 +65,17 @@ internal sealed partial class GZipFilePart : FilePart
private GZipFilePart(Stream stream, IArchiveEncoding archiveEncoding)
: base(archiveEncoding) => _stream = stream;
private GZipFilePart(
Stream stream,
IArchiveEncoding archiveEncoding,
CompressionProviderRegistry? compressionProviders
)
: base(archiveEncoding)
{
_stream = stream;
_compressionProviders = compressionProviders;
}
internal long EntryStartPosition { get; private set; }
internal DateTime? DateModified { get; private set; }
@@ -46,13 +84,11 @@ internal sealed partial class GZipFilePart : FilePart
internal override string? FilePartName => _name;
internal override Stream GetCompressedStream() =>
new DeflateStream(
_stream,
CompressionMode.Decompress,
CompressionLevel.Default,
leaveOpen: true
);
internal override Stream GetCompressedStream()
{
var providers = _compressionProviders ?? CompressionProviderRegistry.Default;
return providers.CreateDecompressStream(CompressionType.Deflate, _stream);
}
internal override Stream GetRawStream() => _stream;

View File

@@ -1,5 +1,6 @@
using System.IO;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors;
namespace SharpCompress.Common.Zip;
@@ -15,6 +16,14 @@ internal partial class SeekableZipFilePart : ZipFilePart
)
: base(header, stream) => _headerFactory = headerFactory;
internal SeekableZipFilePart(
SeekableZipHeaderFactory headerFactory,
DirectoryEntryHeader header,
Stream stream,
CompressionProviderRegistry? compressionProviders
)
: base(header, stream, compressionProviders) => _headerFactory = headerFactory;
internal override Stream GetCompressedStream()
{
if (!_isLocalHeaderLoaded)

View File

@@ -1,6 +1,6 @@
using System.IO;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors;
using SharpCompress.IO;
namespace SharpCompress.Common.Zip;
@@ -12,6 +12,13 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart
internal StreamingZipFilePart(ZipFileEntry header, Stream stream)
: base(header, stream) { }
internal StreamingZipFilePart(
ZipFileEntry header,
Stream stream,
CompressionProviderRegistry? compressionProviders
)
: base(header, stream, compressionProviders) { }
protected override Stream CreateBaseStream() => Header.PackedStream.NotNull();
internal override Stream GetCompressedStream()
@@ -47,11 +54,6 @@ internal sealed partial class StreamingZipFilePart : ZipFilePart
// If we had TotalIn / TotalOut we could have used them
Header.CompressedSize = _decompressionStream.Position;
if (_decompressionStream is DeflateStream deflateStream)
{
stream.Position = 0;
}
Skipped = true;
}
var reader = new BinaryReader(stream, System.Text.Encoding.Default, leaveOpen: true);

View File

@@ -20,6 +20,8 @@ namespace SharpCompress.Common.Zip;
internal abstract partial class ZipFilePart : FilePart
{
private CompressionProviderRegistry? _compressionProviders;
internal ZipFilePart(ZipFileEntry header, Stream stream)
: base(header.ArchiveEncoding)
{
@@ -28,9 +30,29 @@ internal abstract partial class ZipFilePart : FilePart
BaseStream = stream;
}
internal ZipFilePart(
ZipFileEntry header,
Stream stream,
CompressionProviderRegistry? compressionProviders
)
: this(header, stream)
{
_compressionProviders = compressionProviders;
}
internal Stream BaseStream { get; }
internal ZipFileEntry Header { get; set; }
/// <summary>
/// Gets or sets the compression provider registry.
/// If null, uses the default registry.
/// </summary>
internal CompressionProviderRegistry? CompressionProviders
{
get => _compressionProviders;
set => _compressionProviders = value;
}
internal override string? FilePartName => Header.Name;
internal override Stream GetCompressedStream()
@@ -64,8 +86,38 @@ internal abstract partial class ZipFilePart : FilePart
protected bool LeaveStreamOpen =>
FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) || Header.IsZip64;
/// <summary>
/// Gets the compression provider registry, falling back to default if not set.
/// </summary>
protected CompressionProviderRegistry GetProviders() =>
_compressionProviders ?? CompressionProviderRegistry.Default;
/// <summary>
/// Converts ZipCompressionMethod to CompressionType.
/// </summary>
protected static CompressionType ToCompressionType(ZipCompressionMethod method) =>
method switch
{
ZipCompressionMethod.None => CompressionType.None,
ZipCompressionMethod.Deflate => CompressionType.Deflate,
ZipCompressionMethod.Deflate64 => CompressionType.Deflate64,
ZipCompressionMethod.BZip2 => CompressionType.BZip2,
ZipCompressionMethod.LZMA => CompressionType.LZMA,
ZipCompressionMethod.PPMd => CompressionType.PPMd,
ZipCompressionMethod.ZStandard => CompressionType.ZStandard,
ZipCompressionMethod.Xz => CompressionType.Xz,
ZipCompressionMethod.Shrink => CompressionType.Shrink,
ZipCompressionMethod.Reduce1 => CompressionType.Reduce1,
ZipCompressionMethod.Reduce2 => CompressionType.Reduce2,
ZipCompressionMethod.Reduce3 => CompressionType.Reduce3,
ZipCompressionMethod.Reduce4 => CompressionType.Reduce4,
ZipCompressionMethod.Explode => CompressionType.Explode,
_ => throw new NotSupportedException($"Unsupported compression method: {method}"),
};
protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod method)
{
// Handle special cases first
switch (method)
{
case ZipCompressionMethod.None:
@@ -74,76 +126,29 @@ internal abstract partial class ZipFilePart : FilePart
{
return new DataDescriptorStream(stream);
}
return stream;
}
case ZipCompressionMethod.Shrink:
case ZipCompressionMethod.WinzipAes:
{
return new ShrinkStream(
stream,
CompressionMode.Decompress,
Header.CompressedSize,
Header.UncompressedSize
);
}
case ZipCompressionMethod.Reduce1:
{
return ReduceStream.Create(
stream,
Header.CompressedSize,
Header.UncompressedSize,
1
);
}
case ZipCompressionMethod.Reduce2:
{
return ReduceStream.Create(
stream,
Header.CompressedSize,
Header.UncompressedSize,
2
);
}
case ZipCompressionMethod.Reduce3:
{
return ReduceStream.Create(
stream,
Header.CompressedSize,
Header.UncompressedSize,
3
);
}
case ZipCompressionMethod.Reduce4:
{
return ReduceStream.Create(
stream,
Header.CompressedSize,
Header.UncompressedSize,
4
);
}
case ZipCompressionMethod.Explode:
{
return ExplodeStream.Create(
stream,
Header.CompressedSize,
Header.UncompressedSize,
Header.Flags
);
return CreateWinzipAesDecompressionStream(stream);
}
}
case ZipCompressionMethod.Deflate:
{
return new DeflateStream(stream, CompressionMode.Decompress);
}
case ZipCompressionMethod.Deflate64:
{
return new Deflate64Stream(stream, CompressionMode.Decompress);
}
case ZipCompressionMethod.BZip2:
{
return BZip2Stream.Create(stream, CompressionMode.Decompress, false);
}
// Get the compression type and providers
var compressionType = ToCompressionType(method);
var providers = GetProviders();
// Build context with header information
var context = new CompressionContext
{
InputSize = Header.CompressedSize,
OutputSize = Header.UncompressedSize,
CanSeek = stream.CanSeek,
};
// Handle methods that need special context
switch (method)
{
case ZipCompressionMethod.LZMA:
{
if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted))
@@ -158,71 +163,71 @@ internal abstract partial class ZipFilePart : FilePart
)
)
{
reader.ReadUInt16(); //LZMA version
var props = new byte[reader.ReadUInt16()];
reader.ReadUInt16(); // LZMA version
var propsLength = reader.ReadUInt16();
var props = new byte[propsLength];
reader.Read(props, 0, props.Length);
return LzmaStream.Create(
props,
stream,
Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1,
FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1)
context = context with
{
Properties = props,
InputSize =
Header.CompressedSize > 0
? Header.CompressedSize - 4 - props.Length
: -1,
OutputSize = FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1)
? -1
: Header.UncompressedSize
);
: Header.UncompressedSize,
};
return providers.CreateDecompressStream(compressionType, stream, context);
}
}
case ZipCompressionMethod.Xz:
{
return new XZStream(stream);
}
case ZipCompressionMethod.ZStandard:
{
return new DecompressionStream(stream);
}
case ZipCompressionMethod.PPMd:
{
Span<byte> props = stackalloc byte[2];
stream.ReadFully(props);
return PpmdStream.Create(new PpmdProperties(props), stream, false);
context = context with { Properties = props.ToArray() };
return providers.CreateDecompressStream(compressionType, stream, context);
}
case ZipCompressionMethod.WinzipAes:
case ZipCompressionMethod.Explode:
{
var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
if (data is null)
{
throw new InvalidFormatException("No Winzip AES extra data found.");
}
if (data.Length != 7)
{
throw new InvalidFormatException("Winzip data length is not 7.");
}
var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes);
if (compressedMethod != 0x01 && compressedMethod != 0x02)
{
throw new InvalidFormatException(
"Unexpected vendor version number for WinZip AES metadata"
);
}
var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2));
if (vendorId != 0x4541)
{
throw new InvalidFormatException(
"Unexpected vendor ID for WinZip AES metadata"
);
}
return CreateDecompressionStream(
stream,
(ZipCompressionMethod)
BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5))
);
}
default:
{
throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod);
context = context with { FormatOptions = Header.Flags };
return providers.CreateDecompressStream(compressionType, stream, context);
}
}
// For simple methods, use the basic decompress
return providers.CreateDecompressStream(compressionType, stream, context);
}
private Stream CreateWinzipAesDecompressionStream(Stream stream)
{
var data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
if (data is null)
{
throw new InvalidFormatException("No Winzip AES extra data found.");
}
if (data.Length != 7)
{
throw new InvalidFormatException("Winzip data length is not 7.");
}
var compressedMethod = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes);
if (compressedMethod != 0x01 && compressedMethod != 0x02)
{
throw new InvalidFormatException(
"Unexpected vendor version number for WinZip AES metadata"
);
}
var vendorId = BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(2));
if (vendorId != 0x4541)
{
throw new InvalidFormatException("Unexpected vendor ID for WinZip AES metadata");
}
return CreateDecompressionStream(
stream,
(ZipCompressionMethod)BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5))
);
}
protected Stream GetCryptoStream(Stream plainStream)

View File

@@ -0,0 +1,44 @@
using System;
using System.IO;
namespace SharpCompress.Compressors;
/// <summary>
/// Provides context information for compression operations.
/// Carries format-specific parameters that some compression types require.
/// </summary>
public sealed record CompressionContext
{
/// <summary>
/// The size of the input data, or -1 if unknown.
/// </summary>
public long InputSize { get; init; } = -1;
/// <summary>
/// The expected output size, or -1 if unknown.
/// </summary>
public long OutputSize { get; init; } = -1;
/// <summary>
/// Properties bytes for the compression format (e.g., LZMA properties).
/// </summary>
public byte[]? Properties { get; init; }
/// <summary>
/// Whether the underlying stream supports seeking.
/// </summary>
public bool CanSeek { get; init; }
/// <summary>
/// Additional format-specific options.
/// </summary>
public object? FormatOptions { get; init; }
/// <summary>
/// Creates a CompressionContext from a stream.
/// </summary>
/// <param name="stream">The stream to extract context from.</param>
/// <returns>A CompressionContext populated from the stream.</returns>
public static CompressionContext FromStream(Stream stream) =>
new() { CanSeek = stream.CanSeek, InputSize = stream.CanSeek ? stream.Length : -1 };
}

View File

@@ -90,6 +90,69 @@ public sealed class CompressionProviderRegistry
return provider.CreateDecompressStream(source);
}
/// <summary>
/// Creates a compression stream for the specified type with context.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="destination">The destination stream.</param>
/// <param name="level">The compression level.</param>
/// <param name="context">Context information for the compression.</param>
/// <returns>A compression stream.</returns>
/// <exception cref="InvalidOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support compression.</exception>
public Stream CreateCompressStream(
CompressionType type,
Stream destination,
int level,
CompressionContext context
)
{
var provider = GetProvider(type);
if (provider is null)
{
throw new InvalidOperationException(
$"No compression provider registered for type: {type}"
);
}
return provider.CreateCompressStream(destination, level, context);
}
/// <summary>
/// Creates a decompression stream for the specified type with context.
/// </summary>
/// <param name="type">The compression type.</param>
/// <param name="source">The source stream.</param>
/// <param name="context">Context information for the decompression.</param>
/// <returns>A decompression stream.</returns>
/// <exception cref="InvalidOperationException">If no provider is registered for the type.</exception>
/// <exception cref="NotSupportedException">If the provider does not support decompression.</exception>
public Stream CreateDecompressStream(
CompressionType type,
Stream source,
CompressionContext context
)
{
var provider = GetProvider(type);
if (provider is null)
{
throw new InvalidOperationException(
$"No compression provider registered for type: {type}"
);
}
return provider.CreateDecompressStream(source, context);
}
/// <summary>
/// Gets the provider as an ICompressingProvider if it supports complex initialization.
/// </summary>
/// <param name="type">The compression type.</param>
/// <returns>The compressing provider, or null if the provider doesn't support complex initialization.</returns>
public ICompressingProvider? GetCompressingProvider(CompressionType type)
{
var provider = GetProvider(type);
return provider as ICompressingProvider;
}
/// <summary>
/// Creates a new registry with the specified provider added or replaced.
/// </summary>
@@ -122,6 +185,15 @@ public sealed class CompressionProviderRegistry
[CompressionType.LZip] = new LZipCompressionProvider(),
[CompressionType.Xz] = new XzCompressionProvider(),
[CompressionType.Lzw] = new LzwCompressionProvider(),
[CompressionType.Deflate64] = new Deflate64CompressionProvider(),
[CompressionType.Shrink] = new ShrinkCompressionProvider(),
[CompressionType.Reduce1] = new Reduce1CompressionProvider(),
[CompressionType.Reduce2] = new Reduce2CompressionProvider(),
[CompressionType.Reduce3] = new Reduce3CompressionProvider(),
[CompressionType.Reduce4] = new Reduce4CompressionProvider(),
[CompressionType.Explode] = new ExplodeCompressionProvider(),
[CompressionType.LZMA] = new LzmaCompressingProvider(),
[CompressionType.PPMd] = new PpmdCompressingProvider(),
};
return new CompressionProviderRegistry(providers);

View File

@@ -0,0 +1,43 @@
using System.IO;
using SharpCompress.Common;
namespace SharpCompress.Compressors;
/// <summary>
/// Extended compression provider interface for formats that require initialization/finalization data.
/// </summary>
/// <remarks>
/// Some compression formats (like LZMA and PPMd in Zip) require special handling:
/// - Data written before compression starts (magic bytes, properties headers)
/// - Data written after compression completes (properties, footers)
/// This interface extends ICompressionProvider to support these complex initialization patterns
/// while keeping the simple ICompressionProvider interface for formats that don't need it.
/// </remarks>
public interface ICompressingProvider : ICompressionProvider
{
/// <summary>
/// Gets initialization data to write before compression starts.
/// Returns null if no pre-compression data is needed.
/// </summary>
/// <param name="context">Context information.</param>
/// <returns>Bytes to write before compression, or null.</returns>
byte[]? GetPreCompressionData(CompressionContext context);
/// <summary>
/// Gets properties/data to write after creating the compression stream but before writing data.
/// Returns null if no properties are needed.
/// </summary>
/// <param name="stream">The compression stream that was created.</param>
/// <param name="context">Context information.</param>
/// <returns>Bytes to write after stream creation, or null.</returns>
byte[]? GetCompressionProperties(Stream stream, CompressionContext context);
/// <summary>
/// Gets data to write after compression is complete.
/// Returns null if no post-compression data is needed.
/// </summary>
/// <param name="stream">The compression stream.</param>
/// <param name="context">Context information.</param>
/// <returns>Bytes to write after compression, or null.</returns>
byte[]? GetPostCompressionData(Stream stream, CompressionContext context);
}

View File

@@ -46,6 +46,20 @@ public interface ICompressionProvider
/// <exception cref="NotSupportedException">Thrown if SupportsCompression is false.</exception>
Stream CreateCompressStream(Stream destination, int compressionLevel);
/// <summary>
/// Creates a compression stream with context information.
/// </summary>
/// <param name="destination">The destination stream.</param>
/// <param name="compressionLevel">The compression level.</param>
/// <param name="context">Context information about the compression.</param>
/// <returns>A compression stream.</returns>
/// <exception cref="NotSupportedException">Thrown if SupportsCompression is false.</exception>
Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
);
/// <summary>
/// Creates a decompression stream that decompresses data read from it.
/// </summary>
@@ -53,4 +67,13 @@ public interface ICompressionProvider
/// <returns>A stream that decompresses data read from it.</returns>
/// <exception cref="NotSupportedException">Thrown if SupportsDecompression is false.</exception>
Stream CreateDecompressStream(Stream source);
/// <summary>
/// Creates a decompression stream with context information.
/// </summary>
/// <param name="source">The source stream.</param>
/// <param name="context">Context information about the decompression.</param>
/// <returns>A decompression stream.</returns>
/// <exception cref="NotSupportedException">Thrown if SupportsDecompression is false.</exception>
Stream CreateDecompressStream(Stream source, CompressionContext context);
}

View File

@@ -19,8 +19,24 @@ public sealed class BZip2CompressionProvider : ICompressionProvider
return BZip2Stream.Create(destination, CompressionMode.Compress, false);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for BZip2 compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return BZip2Stream.Create(source, CompressionMode.Decompress, false);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for BZip2 decompression
return CreateDecompressStream(source);
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Deflate64;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Deflate64 decompression using SharpCompress's internal implementation.
/// Note: Deflate64 compression is not supported; this provider is decompression-only.
/// </summary>
public sealed class Deflate64CompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Deflate64;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Deflate64 compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Deflate64 compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
return new Deflate64Stream(source, CompressionMode.Decompress);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for Deflate64 decompression
return CreateDecompressStream(source);
}
}

View File

@@ -19,8 +19,24 @@ public sealed class DeflateCompressionProvider : ICompressionProvider
return new DeflateStream(destination, CompressionMode.Compress, level);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for simple Deflate compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return new DeflateStream(source, CompressionMode.Decompress);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for simple Deflate decompression
return CreateDecompressStream(source);
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors.Explode;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Explode decompression using SharpCompress's internal implementation.
/// Note: Explode compression is not supported; this provider is decompression-only.
/// </summary>
/// <remarks>
/// Explode requires compressed size, uncompressed size, and flags which must be provided via CompressionContext.
/// </remarks>
public sealed class ExplodeCompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Explode;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Explode compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Explode compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"Explode decompression requires compressed size, uncompressed size, and flags. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload with FormatOptions."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.InputSize < 0 || context.OutputSize < 0)
{
throw new ArgumentException(
"Explode decompression requires InputSize and OutputSize in CompressionContext.",
nameof(context)
);
}
if (context.FormatOptions is not HeaderFlags flags)
{
throw new ArgumentException(
"Explode decompression requires HeaderFlags in CompressionContext.FormatOptions.",
nameof(context)
);
}
return ExplodeStream.Create(source, context.InputSize, context.OutputSize, flags);
}
}

View File

@@ -19,8 +19,24 @@ public sealed class GZipCompressionProvider : ICompressionProvider
return new GZipStream(destination, CompressionMode.Compress, level);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for simple GZip compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return new GZipStream(source, CompressionMode.Decompress);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for simple GZip decompression
return CreateDecompressStream(source);
}
}

View File

@@ -18,8 +18,24 @@ public sealed class LZipCompressionProvider : ICompressionProvider
return new LZipStream(destination, CompressionMode.Compress);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for LZip compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return new LZipStream(source, CompressionMode.Decompress);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for LZip decompression
return CreateDecompressStream(source);
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.LZMA;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides LZMA compression and decompression using SharpCompress's internal implementation.
/// This is a complex provider that requires initialization data for compression.
/// </summary>
public sealed class LzmaCompressingProvider : ICompressingProvider
{
public CompressionType CompressionType => CompressionType.LZMA;
public bool SupportsCompression => true;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new InvalidOperationException(
"LZMA compression requires context with CanSeek information. "
+ "Use CreateCompressStream(Stream, int, CompressionContext) overload."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// LZMA stream creation returns the encoder stream
// Note: Pre-compression data and properties are handled via ICompressingProvider methods
var props = new LzmaEncoderProperties(!context.CanSeek);
return LzmaStream.Create(props, false, destination);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"LZMA decompression requires properties. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload with Properties."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.Properties is null || context.Properties.Length < 5)
{
throw new ArgumentException(
"LZMA decompression requires Properties (at least 5 bytes) in CompressionContext.",
nameof(context)
);
}
return LzmaStream.Create(context.Properties, source, context.InputSize, context.OutputSize);
}
public byte[]? GetPreCompressionData(CompressionContext context)
{
// Zip format writes these magic bytes before the LZMA stream
return new byte[] { 9, 20, 5, 0 };
}
public byte[]? GetCompressionProperties(Stream stream, CompressionContext context)
{
// The LZMA stream exposes its properties after creation
if (stream is LzmaStream lzmaStream)
{
return lzmaStream.Properties;
}
return null;
}
public byte[]? GetPostCompressionData(Stream stream, CompressionContext context)
{
// No post-compression data needed for LZMA in Zip
return null;
}
}

View File

@@ -22,8 +22,25 @@ public sealed class LzwCompressionProvider : ICompressionProvider
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"LZW compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
return new LzwStream(source);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for LZW decompression
return CreateDecompressStream(source);
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.PPMd;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides PPMd compression and decompression using SharpCompress's internal implementation.
/// This is a complex provider that requires initialization data for compression.
/// </summary>
public sealed class PpmdCompressingProvider : ICompressingProvider
{
public CompressionType CompressionType => CompressionType.PPMd;
public bool SupportsCompression => true;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
// Ppmd doesn't use compressionLevel, uses PpmdProperties instead
var props = new PpmdProperties();
return PpmdStream.Create(props, destination, true);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for Ppmd compression, but we could use FormatOptions for custom properties
if (context.FormatOptions is PpmdProperties customProps)
{
return PpmdStream.Create(customProps, destination, true);
}
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"PPMd decompression requires properties. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload with Properties."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.Properties is null || context.Properties.Length < 2)
{
throw new ArgumentException(
"PPMd decompression requires Properties (at least 2 bytes) in CompressionContext.",
nameof(context)
);
}
var props = new PpmdProperties(context.Properties);
return PpmdStream.Create(props, source, false);
}
public byte[]? GetPreCompressionData(CompressionContext context)
{
// Ppmd writes its properties before the compressed data
if (context.FormatOptions is PpmdProperties customProps)
{
return customProps.Properties;
}
var defaultProps = new PpmdProperties();
return defaultProps.Properties;
}
public byte[]? GetCompressionProperties(Stream stream, CompressionContext context)
{
// Properties are already written in GetPreCompressionData
return null;
}
public byte[]? GetPostCompressionData(Stream stream, CompressionContext context)
{
// No post-compression data needed for Ppmd
return null;
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Reduce;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Reduce1 decompression using SharpCompress's internal implementation.
/// Note: Reduce compression is not supported; this provider is decompression-only.
/// </summary>
public sealed class Reduce1CompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Reduce1;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"Reduce decompression requires compressed and uncompressed sizes. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.InputSize < 0 || context.OutputSize < 0)
{
throw new ArgumentException(
"Reduce decompression requires InputSize and OutputSize in CompressionContext.",
nameof(context)
);
}
return ReduceStream.Create(source, context.InputSize, context.OutputSize, 1);
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Reduce;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Reduce2 decompression using SharpCompress's internal implementation.
/// Note: Reduce compression is not supported; this provider is decompression-only.
/// </summary>
public sealed class Reduce2CompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Reduce2;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"Reduce decompression requires compressed and uncompressed sizes. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.InputSize < 0 || context.OutputSize < 0)
{
throw new ArgumentException(
"Reduce decompression requires InputSize and OutputSize in CompressionContext.",
nameof(context)
);
}
return ReduceStream.Create(source, context.InputSize, context.OutputSize, 2);
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Reduce;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Reduce3 decompression using SharpCompress's internal implementation.
/// Note: Reduce compression is not supported; this provider is decompression-only.
/// </summary>
public sealed class Reduce3CompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Reduce3;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"Reduce decompression requires compressed and uncompressed sizes. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.InputSize < 0 || context.OutputSize < 0)
{
throw new ArgumentException(
"Reduce decompression requires InputSize and OutputSize in CompressionContext.",
nameof(context)
);
}
return ReduceStream.Create(source, context.InputSize, context.OutputSize, 3);
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Reduce;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Reduce4 decompression using SharpCompress's internal implementation.
/// Note: Reduce compression is not supported; this provider is decompression-only.
/// </summary>
public sealed class Reduce4CompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Reduce4;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Reduce compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"Reduce decompression requires compressed and uncompressed sizes. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.InputSize < 0 || context.OutputSize < 0)
{
throw new ArgumentException(
"Reduce decompression requires InputSize and OutputSize in CompressionContext.",
nameof(context)
);
}
return ReduceStream.Create(source, context.InputSize, context.OutputSize, 4);
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.IO;
using SharpCompress.Common;
using SharpCompress.Compressors.Shrink;
namespace SharpCompress.Compressors.Providers;
/// <summary>
/// Provides Shrink decompression using SharpCompress's internal implementation.
/// Note: Shrink compression is not supported; this provider is decompression-only.
/// </summary>
/// <remarks>
/// Shrink requires compressed and uncompressed sizes which must be provided via CompressionContext.
/// </remarks>
public sealed class ShrinkCompressionProvider : ICompressionProvider
{
public CompressionType CompressionType => CompressionType.Shrink;
public bool SupportsCompression => false;
public bool SupportsDecompression => true;
public Stream CreateCompressStream(Stream destination, int compressionLevel)
{
throw new NotSupportedException(
"Shrink compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"Shrink compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
throw new InvalidOperationException(
"Shrink decompression requires compressed and uncompressed sizes. "
+ "Use CreateDecompressStream(Stream, CompressionContext) overload."
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
if (context.InputSize < 0 || context.OutputSize < 0)
{
throw new ArgumentException(
"Shrink decompression requires InputSize and OutputSize in CompressionContext.",
nameof(context)
);
}
return new ShrinkStream(
source,
CompressionMode.Decompress,
context.InputSize,
context.OutputSize
);
}
}

View File

@@ -24,6 +24,16 @@ public sealed class SystemDeflateCompressionProvider : ICompressionProvider
return new DeflateStream(destination, bclLevel, leaveOpen: true);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for simple Deflate compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return new DeflateStream(
@@ -33,6 +43,12 @@ public sealed class SystemDeflateCompressionProvider : ICompressionProvider
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for simple Deflate decompression
return CreateDecompressStream(source);
}
/// <summary>
/// Maps SharpCompress compression level (0-9) to BCL CompressionLevel.
/// </summary>

View File

@@ -24,6 +24,16 @@ public sealed class SystemGZipCompressionProvider : ICompressionProvider
return new GZipStream(destination, bclLevel, leaveOpen: true);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for simple GZip compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return new GZipStream(
@@ -33,6 +43,12 @@ public sealed class SystemGZipCompressionProvider : ICompressionProvider
);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for simple GZip decompression
return CreateDecompressStream(source);
}
/// <summary>
/// Maps SharpCompress compression level (0-9) to BCL CompressionLevel.
/// </summary>

View File

@@ -22,8 +22,25 @@ public sealed class XzCompressionProvider : ICompressionProvider
);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
throw new NotSupportedException(
"XZ compression is not supported by SharpCompress's internal implementation."
);
}
public Stream CreateDecompressStream(Stream source)
{
return new XZStream(source);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for XZ decompression
return CreateDecompressStream(source);
}
}

View File

@@ -18,8 +18,24 @@ public sealed class ZStandardCompressionProvider : ICompressionProvider
return new ZStd.CompressionStream(destination, compressionLevel);
}
public Stream CreateCompressStream(
Stream destination,
int compressionLevel,
CompressionContext context
)
{
// Context not used for ZStandard compression
return CreateCompressStream(destination, compressionLevel);
}
public Stream CreateDecompressStream(Stream source)
{
return new ZStd.DecompressionStream(source);
}
public Stream CreateDecompressStream(Stream source, CompressionContext context)
{
// Context not used for ZStandard decompression
return CreateDecompressStream(source);
}
}

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Compressors;
namespace SharpCompress.Readers.Zip;
@@ -18,16 +19,28 @@ public partial class ZipReader
{
private readonly StreamingZipHeaderFactory _headerFactory;
private readonly Stream _stream;
private readonly CompressionProviderRegistry? _compressionProviders;
public ZipEntryAsyncEnumerable(StreamingZipHeaderFactory headerFactory, Stream stream)
public ZipEntryAsyncEnumerable(
StreamingZipHeaderFactory headerFactory,
Stream stream,
CompressionProviderRegistry? compressionProviders
)
{
_headerFactory = headerFactory;
_stream = stream;
_compressionProviders = compressionProviders;
}
public IAsyncEnumerator<ZipEntry> GetAsyncEnumerator(
CancellationToken cancellationToken = default
) => new ZipEntryAsyncEnumerator(_headerFactory, _stream, cancellationToken);
) =>
new ZipEntryAsyncEnumerator(
_headerFactory,
_stream,
_compressionProviders,
cancellationToken
);
}
/// <summary>
@@ -37,15 +50,18 @@ public partial class ZipReader
{
private readonly Stream _stream;
private readonly IAsyncEnumerator<ZipHeader> _headerEnumerator;
private readonly CompressionProviderRegistry? _compressionProviders;
private ZipEntry? _current;
public ZipEntryAsyncEnumerator(
StreamingZipHeaderFactory headerFactory,
Stream stream,
CompressionProviderRegistry? compressionProviders,
CancellationToken cancellationToken
)
{
_stream = stream;
_compressionProviders = compressionProviders;
_headerEnumerator = headerFactory
.ReadStreamHeaderAsync(stream)
.GetAsyncEnumerator(cancellationToken);
@@ -67,7 +83,11 @@ public partial class ZipReader
{
case ZipHeaderType.LocalEntry:
_current = new ZipEntry(
new StreamingZipFilePart((LocalEntryHeader)header, _stream)
new StreamingZipFilePart(
(LocalEntryHeader)header,
_stream,
_compressionProviders
)
);
return true;
case ZipHeaderType.DirectoryEntry:

View File

@@ -74,7 +74,11 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
case ZipHeaderType.LocalEntry:
{
yield return new ZipEntry(
new StreamingZipFilePart((LocalEntryHeader)h, stream)
new StreamingZipFilePart(
(LocalEntryHeader)h,
stream,
Options.CompressionProviders
)
);
}
break;
@@ -99,7 +103,7 @@ public partial class ZipReader : AbstractReader<ZipEntry, ZipVolume>
/// Returns entries asynchronously for streams that only support async reads.
/// </summary>
protected override IAsyncEnumerable<ZipEntry> GetEntriesAsync(Stream stream) =>
new ZipEntryAsyncEnumerable(_headerFactory, stream);
new ZipEntryAsyncEnumerable(_headerFactory, stream, Options.CompressionProviders);
// Async nested classes moved to ZipReader.Async.cs
}

View File

@@ -448,25 +448,70 @@ public partial class ZipWriter : AbstractWriter
}
case ZipCompressionMethod.LZMA:
{
// LZMA has complex initialization that doesn't fit the provider pattern
counting.WriteByte(9);
counting.WriteByte(20);
counting.WriteByte(5);
counting.WriteByte(0);
var lzmaStream = LzmaStream.Create(
new LzmaEncoderProperties(!originalStream.CanSeek),
false,
counting
// Use ICompressingProvider for complex initialization
var compressingProvider = providers.GetCompressingProvider(
CompressionType.LZMA
);
counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length);
if (compressingProvider is null)
{
throw new InvalidOperationException("LZMA compression provider not found.");
}
var context = new CompressionContext { CanSeek = originalStream.CanSeek };
// Write pre-compression data (magic bytes)
var preData = compressingProvider.GetPreCompressionData(context);
if (preData != null)
{
counting.Write(preData, 0, preData.Length);
}
// Create compression stream
var lzmaStream = compressingProvider.CreateCompressStream(
counting,
compressionLevel,
context
);
// Write compression properties
var props = compressingProvider.GetCompressionProperties(lzmaStream, context);
if (props != null)
{
counting.Write(props, 0, props.Length);
}
return lzmaStream;
}
case ZipCompressionMethod.PPMd:
{
// PPMd has complex initialization that doesn't fit the provider pattern
counting.Write(writer.PpmdProperties.Properties, 0, 2);
return PpmdStream.Create(writer.PpmdProperties, counting, true);
// Use ICompressingProvider for complex initialization
var compressingProvider = providers.GetCompressingProvider(
CompressionType.PPMd
);
if (compressingProvider is null)
{
throw new InvalidOperationException("PPMd compression provider not found.");
}
var context = new CompressionContext
{
CanSeek = originalStream.CanSeek,
FormatOptions = writer.PpmdProperties,
};
// Write pre-compression data (properties)
var preData = compressingProvider.GetPreCompressionData(context);
if (preData != null)
{
counting.Write(preData, 0, preData.Length);
}
// Create compression stream
return compressingProvider.CreateCompressStream(
counting,
compressionLevel,
context
);
}
case ZipCompressionMethod.ZStandard:
{