use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CacheKind {
HyperscanShards,
DetectorPlans,
GpuPrograms,
MatcherArtifacts,
LockFiles,
}
impl CacheKind {
pub const ALL: &'static [CacheKind] = &[
CacheKind::HyperscanShards,
CacheKind::DetectorPlans,
CacheKind::GpuPrograms,
CacheKind::MatcherArtifacts,
CacheKind::LockFiles,
];
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::HyperscanShards => "hyperscan-shards",
Self::DetectorPlans => "detector-plans",
Self::GpuPrograms => "gpu-programs",
Self::MatcherArtifacts => "matcher-artifacts",
Self::LockFiles => "lock-files",
}
}
#[must_use]
pub fn matches_path(self, path: &Path) -> bool {
Self::classify_path(path) == Some(self)
}
#[must_use]
pub fn classify_path(path: &Path) -> Option<Self> {
let file_name = path.file_name()?.to_str()?;
if file_name.starts_with('.') {
return None;
}
if file_name.ends_with(".lock") {
let is_package_lock = file_name.eq_ignore_ascii_case("Cargo.lock")
|| file_name.eq_ignore_ascii_case("flake.lock")
|| file_name.eq_ignore_ascii_case("yarn.lock")
|| file_name.eq_ignore_ascii_case("pnpm-lock.yaml")
|| file_name.eq_ignore_ascii_case("composer.lock")
|| file_name.eq_ignore_ascii_case("Gemfile.lock")
|| file_name.eq_ignore_ascii_case("poetry.lock")
|| file_name.eq_ignore_ascii_case("Pipfile.lock")
|| file_name.eq_ignore_ascii_case("package.lock")
|| file_name.eq_ignore_ascii_case("package-lock.json");
if !is_package_lock {
return Some(Self::LockFiles);
}
return None;
}
if file_name.starts_with(crate::hyperscan_cache::HYPERSCAN_CACHE_PREFIX)
&& file_name.ends_with(crate::hyperscan_cache::HYPERSCAN_CACHE_SUFFIX)
{
return Some(Self::HyperscanShards);
}
if file_name.starts_with("detectors-") && file_name.ends_with(".json") {
return Some(Self::DetectorPlans);
}
if file_name.ends_with(crate::MATCHER_ARTIFACT_SUFFIX) {
return Some(Self::MatcherArtifacts);
}
let in_programs_dir = path
.parent()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.is_some_and(|n| n == "programs");
let is_gpu_matcher_file = file_name.starts_with("gpu-") && file_name.ends_with(".bin");
let is_program_binary = in_programs_dir
&& (file_name.ends_with(".bin")
|| (file_name.len() == 64 && file_name.chars().all(|c| c.is_ascii_hexdigit()))
|| file_name.starts_with("gpu-"));
if is_gpu_matcher_file || is_program_binary {
return Some(Self::GpuPrograms);
}
None
}
#[must_use]
pub const fn default_policy(self) -> CacheEvictionPolicy {
match self {
Self::HyperscanShards => CacheEvictionPolicy {
max_entries: 128,
max_bytes: 512 * 1024 * 1024, max_lock_age_secs: 600,
},
Self::DetectorPlans => CacheEvictionPolicy {
max_entries: 16,
max_bytes: 64 * 1024 * 1024, max_lock_age_secs: 600,
},
Self::GpuPrograms => CacheEvictionPolicy {
max_entries: 64,
max_bytes: 128 * 1024 * 1024, max_lock_age_secs: 600,
},
Self::MatcherArtifacts => CacheEvictionPolicy {
max_entries: 8,
max_bytes: 2 * 1024 * 1024 * 1024, max_lock_age_secs: 600,
},
Self::LockFiles => CacheEvictionPolicy {
max_entries: 1024,
max_bytes: 10 * 1024 * 1024, max_lock_age_secs: 600, },
}
}
}
impl std::fmt::Display for CacheKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.label())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacheEvictionPolicy {
pub max_entries: usize,
pub max_bytes: u64,
pub max_lock_age_secs: u64,
}
impl CacheEvictionPolicy {
#[must_use]
pub const fn new(max_entries: usize, max_bytes: u64, max_lock_age_secs: u64) -> Self {
Self {
max_entries,
max_bytes,
max_lock_age_secs,
}
}
}