use super::envelope::{BinaryWriteOptions, CompressionCodec};
#[derive(Clone, Debug)]
pub struct CompressionProfile {
pub name: &'static str,
pub codec: CompressionCodec,
pub level: Option<i32>,
pub expected_ratio: f32,
pub description: &'static str,
}
impl CompressionProfile {
pub const fn new(
name: &'static str,
codec: CompressionCodec,
level: Option<i32>,
expected_ratio: f32,
description: &'static str,
) -> Self {
Self {
name,
codec,
level,
expected_ratio,
description,
}
}
pub fn to_write_options(&self) -> BinaryWriteOptions {
BinaryWriteOptions {
codec: self.codec,
level: self.level,
}
}
}
pub const PROFILE_KERNEL: CompressionProfile = CompressionProfile::new(
"Kernel",
CompressionCodec::Zstd,
Some(19),
0.25, "Maximum compression for kernel/boot files",
);
pub const PROFILE_LIBRARIES: CompressionProfile = CompressionProfile::new(
"Libraries",
CompressionCodec::Zstd,
Some(9),
0.40, "Balanced compression for shared libraries",
);
pub const PROFILE_BINARIES: CompressionProfile = CompressionProfile::new(
"Binaries",
CompressionCodec::Zstd,
Some(6),
0.45, "Moderate compression for executables",
);
pub const PROFILE_CONFIG: CompressionProfile = CompressionProfile::new(
"Config",
CompressionCodec::Lz4,
None,
0.50, "Fast LZ4 compression for config files",
);
pub const PROFILE_RUNTIME: CompressionProfile = CompressionProfile::new(
"Runtime",
CompressionCodec::None,
None,
1.0, "No compression for runtime/temp data",
);
pub const PROFILE_ARCHIVE: CompressionProfile = CompressionProfile::new(
"Archive",
CompressionCodec::Zstd,
Some(22), 0.20, "Maximum compression for archives/backups",
);
pub const PROFILE_BALANCED: CompressionProfile = CompressionProfile::new(
"Balanced",
CompressionCodec::Zstd,
Some(3),
0.55, "General-purpose balanced compression",
);
pub const PROFILE_DATABASE: CompressionProfile = CompressionProfile::new(
"Database",
CompressionCodec::Zstd,
Some(5),
0.35, "Compression for databases and logs",
);
pub const PROFILE_MEDIA: CompressionProfile = CompressionProfile::new(
"Media",
CompressionCodec::None,
None,
0.98, "Skip compression for pre-compressed media",
);
pub const ALL_PROFILES: &[&CompressionProfile] = &[
&PROFILE_KERNEL,
&PROFILE_LIBRARIES,
&PROFILE_BINARIES,
&PROFILE_CONFIG,
&PROFILE_RUNTIME,
&PROFILE_ARCHIVE,
&PROFILE_BALANCED,
&PROFILE_DATABASE,
&PROFILE_MEDIA,
];
#[derive(Clone, Debug)]
pub struct CompressionProfiler {
pub default_profile: CompressionProfile,
}
impl Default for CompressionProfiler {
fn default() -> Self {
Self {
default_profile: PROFILE_BALANCED,
}
}
}
impl CompressionProfiler {
pub fn with_default(default: CompressionProfile) -> Self {
Self {
default_profile: default,
}
}
pub fn for_path(&self, path: &str) -> CompressionProfile {
let path_lower = path.to_lowercase();
if path_lower.starts_with("/boot")
|| path_lower.contains("vmlinuz")
|| path_lower.contains("initr")
|| path_lower.ends_with(".ko")
|| path_lower.ends_with(".ko.zst")
|| path_lower.ends_with(".ko.xz")
{
return PROFILE_KERNEL;
}
if path_lower.ends_with(".so")
|| path_lower.contains(".so.")
|| path_lower.ends_with(".dll")
|| path_lower.starts_with("/lib")
|| path_lower.starts_with("/usr/lib")
{
return PROFILE_LIBRARIES;
}
if path_lower.starts_with("/bin")
|| path_lower.starts_with("/sbin")
|| path_lower.starts_with("/usr/bin")
|| path_lower.starts_with("/usr/sbin")
|| path_lower.starts_with("/usr/local/bin")
{
return PROFILE_BINARIES;
}
if path_lower.starts_with("/etc")
|| path_lower.ends_with(".conf")
|| path_lower.ends_with(".cfg")
|| path_lower.ends_with(".ini")
|| path_lower.ends_with(".yaml")
|| path_lower.ends_with(".yml")
|| path_lower.ends_with(".toml")
|| path_lower.ends_with(".json")
|| path_lower.ends_with(".xml")
{
return PROFILE_CONFIG;
}
if path_lower.starts_with("/tmp")
|| path_lower.starts_with("/var/tmp")
|| path_lower.starts_with("/run")
|| path_lower.starts_with("/dev/shm")
|| path_lower.contains("/cache/")
{
return PROFILE_RUNTIME;
}
if path_lower.ends_with(".db")
|| path_lower.ends_with(".sqlite")
|| path_lower.ends_with(".sqlite3")
|| path_lower.ends_with(".log")
|| path_lower.starts_with("/var/log")
|| path_lower.ends_with(".journal")
{
return PROFILE_DATABASE;
}
if path_lower.ends_with(".jpg")
|| path_lower.ends_with(".jpeg")
|| path_lower.ends_with(".png")
|| path_lower.ends_with(".gif")
|| path_lower.ends_with(".webp")
|| path_lower.ends_with(".mp3")
|| path_lower.ends_with(".mp4")
|| path_lower.ends_with(".mkv")
|| path_lower.ends_with(".webm")
|| path_lower.ends_with(".ogg")
|| path_lower.ends_with(".flac")
|| path_lower.ends_with(".zip")
|| path_lower.ends_with(".gz")
|| path_lower.ends_with(".xz")
|| path_lower.ends_with(".zst")
|| path_lower.ends_with(".bz2")
|| path_lower.ends_with(".7z")
|| path_lower.ends_with(".rar")
{
return PROFILE_MEDIA;
}
if path_lower.starts_with("/var/backups")
|| path_lower.starts_with("/backup")
|| path_lower.contains("/archive/")
{
return PROFILE_ARCHIVE;
}
self.default_profile.clone()
}
pub fn by_name(&self, name: &str) -> Option<CompressionProfile> {
ALL_PROFILES
.iter()
.find(|p| p.name.eq_ignore_ascii_case(name))
.map(|p| (*p).clone())
}
pub fn estimate_compressed_size(&self, path: &str, original_size: usize) -> usize {
let profile = self.for_path(path);
(original_size as f32 * profile.expected_ratio) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profile_selection_kernel() {
let profiler = CompressionProfiler::default();
assert_eq!(profiler.for_path("/boot/vmlinuz").name, "Kernel");
assert_eq!(profiler.for_path("/boot/initrd.img").name, "Kernel");
assert_eq!(
profiler.for_path("/lib/modules/5.4.0/ext4.ko").name,
"Kernel"
);
}
#[test]
fn test_profile_selection_libraries() {
let profiler = CompressionProfiler::default();
assert_eq!(
profiler.for_path("/lib/x86_64-linux-gnu/libc.so.6").name,
"Libraries"
);
assert_eq!(profiler.for_path("/usr/lib/libssl.so.3").name, "Libraries");
}
#[test]
fn test_profile_selection_binaries() {
let profiler = CompressionProfiler::default();
assert_eq!(profiler.for_path("/bin/bash").name, "Binaries");
assert_eq!(profiler.for_path("/usr/bin/python3").name, "Binaries");
assert_eq!(profiler.for_path("/sbin/init").name, "Binaries");
}
#[test]
fn test_profile_selection_config() {
let profiler = CompressionProfiler::default();
assert_eq!(profiler.for_path("/etc/passwd").name, "Config");
assert_eq!(profiler.for_path("/etc/nginx/nginx.conf").name, "Config");
assert_eq!(profiler.for_path("/app/config.yaml").name, "Config");
}
#[test]
fn test_profile_selection_runtime() {
let profiler = CompressionProfiler::default();
assert_eq!(profiler.for_path("/tmp/session.sock").name, "Runtime");
assert_eq!(profiler.for_path("/run/systemd/notify").name, "Runtime");
}
#[test]
fn test_profile_selection_media() {
let profiler = CompressionProfiler::default();
assert_eq!(profiler.for_path("/home/user/photo.jpg").name, "Media");
assert_eq!(profiler.for_path("/var/data/video.mp4").name, "Media");
assert_eq!(profiler.for_path("/archive/backup.tar.gz").name, "Media");
}
#[test]
fn test_profile_to_write_options() {
let profile = PROFILE_KERNEL;
let opts = profile.to_write_options();
assert_eq!(opts.codec, CompressionCodec::Zstd);
assert_eq!(opts.level, Some(19));
}
#[test]
fn test_estimate_compressed_size() {
let profiler = CompressionProfiler::default();
let est = profiler.estimate_compressed_size("/boot/vmlinuz", 10_000_000);
assert_eq!(est, 2_500_000);
let est = profiler.estimate_compressed_size("/tmp/data", 10_000_000);
assert_eq!(est, 10_000_000);
}
#[test]
fn test_by_name() {
let profiler = CompressionProfiler::default();
assert!(profiler.by_name("Kernel").is_some());
assert!(profiler.by_name("kernel").is_some()); assert!(profiler.by_name("NonExistent").is_none());
}
}