keyhog_core/
cache_layout.rs1use serde::{Deserialize, Serialize};
13use std::path::Path;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "kebab-case")]
18pub enum CacheKind {
19 HyperscanShards,
21 DetectorPlans,
23 GpuPrograms,
25 MatcherArtifacts,
27 LockFiles,
29}
30
31impl CacheKind {
32 pub const ALL: &'static [CacheKind] = &[
34 CacheKind::HyperscanShards,
35 CacheKind::DetectorPlans,
36 CacheKind::GpuPrograms,
37 CacheKind::MatcherArtifacts,
38 CacheKind::LockFiles,
39 ];
40
41 #[must_use]
43 pub const fn label(self) -> &'static str {
44 match self {
45 Self::HyperscanShards => "hyperscan-shards",
46 Self::DetectorPlans => "detector-plans",
47 Self::GpuPrograms => "gpu-programs",
48 Self::MatcherArtifacts => "matcher-artifacts",
49 Self::LockFiles => "lock-files",
50 }
51 }
52
53 #[must_use]
55 pub fn matches_path(self, path: &Path) -> bool {
56 Self::classify_path(path) == Some(self)
57 }
58
59 #[must_use]
61 pub fn classify_path(path: &Path) -> Option<Self> {
62 let file_name = path.file_name()?.to_str()?;
63 if file_name.starts_with('.') {
64 return None;
65 }
66 if file_name.ends_with(".lock") {
67 let is_package_lock = file_name.eq_ignore_ascii_case("Cargo.lock")
68 || file_name.eq_ignore_ascii_case("flake.lock")
69 || file_name.eq_ignore_ascii_case("yarn.lock")
70 || file_name.eq_ignore_ascii_case("pnpm-lock.yaml")
71 || file_name.eq_ignore_ascii_case("composer.lock")
72 || file_name.eq_ignore_ascii_case("Gemfile.lock")
73 || file_name.eq_ignore_ascii_case("poetry.lock")
74 || file_name.eq_ignore_ascii_case("Pipfile.lock")
75 || file_name.eq_ignore_ascii_case("package.lock")
76 || file_name.eq_ignore_ascii_case("package-lock.json");
77 if !is_package_lock {
78 return Some(Self::LockFiles);
79 }
80 return None;
81 }
82 if file_name.starts_with(crate::hyperscan_cache::HYPERSCAN_CACHE_PREFIX)
83 && file_name.ends_with(crate::hyperscan_cache::HYPERSCAN_CACHE_SUFFIX)
84 {
85 return Some(Self::HyperscanShards);
86 }
87 if file_name.starts_with("detectors-") && file_name.ends_with(".json") {
88 return Some(Self::DetectorPlans);
89 }
90 if file_name.ends_with(crate::MATCHER_ARTIFACT_SUFFIX) {
91 return Some(Self::MatcherArtifacts);
92 }
93 let in_programs_dir = path
94 .parent()
95 .and_then(|p| p.file_name())
96 .and_then(|n| n.to_str())
97 .is_some_and(|n| n == "programs");
98 let is_gpu_matcher_file = file_name.starts_with("gpu-") && file_name.ends_with(".bin");
99 let is_program_binary = in_programs_dir
100 && (file_name.ends_with(".bin")
101 || (file_name.len() == 64 && file_name.chars().all(|c| c.is_ascii_hexdigit()))
102 || file_name.starts_with("gpu-"));
103 if is_gpu_matcher_file || is_program_binary {
104 return Some(Self::GpuPrograms);
105 }
106 None
107 }
108
109 #[must_use]
111 pub const fn default_policy(self) -> CacheEvictionPolicy {
112 match self {
113 Self::HyperscanShards => CacheEvictionPolicy {
114 max_entries: 128,
115 max_bytes: 512 * 1024 * 1024, max_lock_age_secs: 600,
117 },
118 Self::DetectorPlans => CacheEvictionPolicy {
119 max_entries: 16,
120 max_bytes: 64 * 1024 * 1024, max_lock_age_secs: 600,
122 },
123 Self::GpuPrograms => CacheEvictionPolicy {
124 max_entries: 64,
125 max_bytes: 128 * 1024 * 1024, max_lock_age_secs: 600,
127 },
128 Self::MatcherArtifacts => CacheEvictionPolicy {
129 max_entries: 8,
130 max_bytes: 2 * 1024 * 1024 * 1024, max_lock_age_secs: 600,
132 },
133 Self::LockFiles => CacheEvictionPolicy {
134 max_entries: 1024,
135 max_bytes: 10 * 1024 * 1024, max_lock_age_secs: 600, },
138 }
139 }
140}
141
142impl std::fmt::Display for CacheKind {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 write!(f, "{}", self.label())
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150pub struct CacheEvictionPolicy {
151 pub max_entries: usize,
153 pub max_bytes: u64,
155 pub max_lock_age_secs: u64,
157}
158
159impl CacheEvictionPolicy {
160 #[must_use]
162 pub const fn new(max_entries: usize, max_bytes: u64, max_lock_age_secs: u64) -> Self {
163 Self {
164 max_entries,
165 max_bytes,
166 max_lock_age_secs,
167 }
168 }
169}