132 lines
4.2 KiB
C#
132 lines
4.2 KiB
C#
using ZstdSharp;
|
|
|
|
namespace StellaOps.Concelier.Federation.Compression;
|
|
|
|
/// <summary>
|
|
/// ZStandard compression utilities for federation bundles.
|
|
/// </summary>
|
|
public static class ZstdCompression
|
|
{
|
|
/// <summary>Default compression level (balanced speed/ratio).</summary>
|
|
public const int DefaultLevel = 3;
|
|
|
|
/// <summary>Minimum compression level.</summary>
|
|
public const int MinLevel = 1;
|
|
|
|
/// <summary>Maximum compression level.</summary>
|
|
public const int MaxLevel = 19;
|
|
|
|
/// <summary>
|
|
/// Create a compression stream wrapping the output.
|
|
/// </summary>
|
|
/// <param name="output">Target stream for compressed data.</param>
|
|
/// <param name="level">Compression level (1-19, default 3).</param>
|
|
/// <returns>Stream that compresses data written to it.</returns>
|
|
public static CompressionStream CreateCompressionStream(Stream output, int level = DefaultLevel)
|
|
{
|
|
ValidateLevel(level);
|
|
return new CompressionStream(output, level);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a decompression stream wrapping the input.
|
|
/// </summary>
|
|
/// <param name="input">Source stream of compressed data.</param>
|
|
/// <returns>Stream that decompresses data read from it.</returns>
|
|
public static DecompressionStream CreateDecompressionStream(Stream input)
|
|
{
|
|
return new DecompressionStream(input);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compress data in memory.
|
|
/// </summary>
|
|
/// <param name="data">Uncompressed data.</param>
|
|
/// <param name="level">Compression level (1-19, default 3).</param>
|
|
/// <returns>Compressed data.</returns>
|
|
public static byte[] Compress(ReadOnlySpan<byte> data, int level = DefaultLevel)
|
|
{
|
|
ValidateLevel(level);
|
|
using var compressor = new Compressor(level);
|
|
return compressor.Wrap(data).ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decompress data in memory.
|
|
/// </summary>
|
|
/// <param name="compressed">Compressed data.</param>
|
|
/// <returns>Decompressed data.</returns>
|
|
public static byte[] Decompress(ReadOnlySpan<byte> compressed)
|
|
{
|
|
using var decompressor = new Decompressor();
|
|
return decompressor.Unwrap(compressed).ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compress a file to another file.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decompress a file to another file.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decompress from one stream to another.
|
|
/// </summary>
|
|
public static async Task DecompressAsync(
|
|
Stream input,
|
|
Stream output,
|
|
CancellationToken ct = default)
|
|
{
|
|
await using var decompressor = CreateDecompressionStream(input);
|
|
await decompressor.CopyToAsync(output, ct);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compress from one stream to another.
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
}
|