using ZstdSharp;
namespace StellaOps.Concelier.Federation.Compression;
///
/// ZStandard compression utilities for federation bundles.
///
public static class ZstdCompression
{
/// Default compression level (balanced speed/ratio).
public const int DefaultLevel = 3;
/// Minimum compression level.
public const int MinLevel = 1;
/// Maximum compression level.
public const int MaxLevel = 19;
///
/// Create a compression stream wrapping the output.
///
/// Target stream for compressed data.
/// Compression level (1-19, default 3).
/// Stream that compresses data written to it.
public static CompressionStream CreateCompressionStream(Stream output, int level = DefaultLevel)
{
ValidateLevel(level);
return new CompressionStream(output, level);
}
///
/// Create a decompression stream wrapping the input.
///
/// Source stream of compressed data.
/// Stream that decompresses data read from it.
public static DecompressionStream CreateDecompressionStream(Stream input)
{
return new DecompressionStream(input);
}
///
/// Compress data in memory.
///
/// Uncompressed data.
/// Compression level (1-19, default 3).
/// Compressed data.
public static byte[] Compress(ReadOnlySpan data, int level = DefaultLevel)
{
ValidateLevel(level);
using var compressor = new Compressor(level);
return compressor.Wrap(data).ToArray();
}
///
/// Decompress data in memory.
///
/// Compressed data.
/// Decompressed data.
public static byte[] Decompress(ReadOnlySpan compressed)
{
using var decompressor = new Decompressor();
return decompressor.Unwrap(compressed).ToArray();
}
///
/// Compress a file to another file.
///
public static async Task CompressFileAsync(
string inputPath,
string outputPath,
int level = DefaultLevel,
CancellationToken ct = default)
{
ValidateLevel(level);
await using var input = File.OpenRead(inputPath);
await using var output = File.Create(outputPath);
await using var compressor = CreateCompressionStream(output, level);
await input.CopyToAsync(compressor, ct);
}
///
/// Decompress a file to another file.
///
public static async Task DecompressFileAsync(
string inputPath,
string outputPath,
CancellationToken ct = default)
{
await using var input = File.OpenRead(inputPath);
await using var decompressor = CreateDecompressionStream(input);
await using var output = File.Create(outputPath);
await decompressor.CopyToAsync(output, ct);
}
///
/// Decompress from one stream to another.
///
public static async Task DecompressAsync(
Stream input,
Stream output,
CancellationToken ct = default)
{
await using var decompressor = CreateDecompressionStream(input);
await decompressor.CopyToAsync(output, ct);
}
///
/// Compress from one stream to another.
///
public static async Task CompressAsync(
Stream input,
Stream output,
int level = DefaultLevel,
CancellationToken ct = default)
{
ValidateLevel(level);
await using var compressor = CreateCompressionStream(output, level);
await input.CopyToAsync(compressor, ct);
}
private static void ValidateLevel(int level)
{
if (level < MinLevel || level > MaxLevel)
{
throw new ArgumentOutOfRangeException(
nameof(level),
level,
$"Compression level must be between {MinLevel} and {MaxLevel}");
}
}
}