lean_ctx/core/
storage_maintenance.rs1use std::path::PathBuf;
10
11#[derive(Debug, Default, Clone, Copy)]
13pub struct MaintenanceResult {
14 pub quarantined_removed: u32,
15 pub bytes_freed: u64,
16 pub archive_entries_pruned: u32,
17 pub archive_db_bytes_after: u64,
18}
19
20const QUARANTINED_FILES: &[&str] = &[
21 "bm25_index.json.quarantined",
22 "bm25_index.bin.quarantined",
23 "bm25_index.bin.zst.quarantined",
24];
25
26fn prune_quarantined_bm25() -> (u32, u64) {
29 let mut removed = 0u32;
30 let mut freed = 0u64;
31 let Ok(data_dir) = crate::core::data_dir::lean_ctx_data_dir() else {
32 return (removed, freed);
33 };
34 let vectors_dir = data_dir.join("vectors");
35 let Ok(entries) = std::fs::read_dir(&vectors_dir) else {
36 return (removed, freed);
37 };
38 for entry in entries.flatten() {
39 let dir = entry.path();
40 if !dir.is_dir() {
41 continue;
42 }
43 for q_name in QUARANTINED_FILES {
44 let q: PathBuf = dir.join(q_name);
45 if q.exists() {
46 if let Ok(meta) = std::fs::metadata(&q) {
47 freed = freed.saturating_add(meta.len());
48 }
49 if std::fs::remove_file(&q).is_ok() {
50 removed += 1;
51 }
52 }
53 }
54 }
55 (removed, freed)
56}
57
58pub fn run_quiet() -> MaintenanceResult {
61 let (quarantined_removed, bytes_freed) = prune_quarantined_bm25();
62 let archive_entries_pruned = crate::core::archive::cleanup();
66 let archive_db_bytes_after = crate::core::archive_fts::enforce_cap();
67 if quarantined_removed > 0 || archive_entries_pruned > 0 {
68 tracing::info!(
69 "storage maintenance: pruned {quarantined_removed} quarantined BM25 index file(s) \
70 (freed {bytes_freed} bytes) + {archive_entries_pruned} archive entry/entries; \
71 archive DB now {archive_db_bytes_after} bytes"
72 );
73 }
74 MaintenanceResult {
75 quarantined_removed,
76 bytes_freed,
77 archive_entries_pruned,
78 archive_db_bytes_after,
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn prune_removes_quarantined_files() {
88 let _lock = crate::core::data_dir::test_env_lock();
89 let tmp = tempfile::tempdir().unwrap();
90 crate::test_env::set_var("LEAN_CTX_DATA_DIR", tmp.path());
91
92 let idx_dir = tmp.path().join("vectors").join("proj_abc");
93 std::fs::create_dir_all(&idx_dir).unwrap();
94 std::fs::write(idx_dir.join("bm25_index.json.quarantined"), b"dead").unwrap();
95 std::fs::write(idx_dir.join("bm25_index.bin"), b"live").unwrap();
96
97 let (removed, freed) = prune_quarantined_bm25();
98 assert_eq!(removed, 1);
99 assert!(freed >= 4);
100 assert!(!idx_dir.join("bm25_index.json.quarantined").exists());
101 assert!(
102 idx_dir.join("bm25_index.bin").exists(),
103 "live index must be preserved"
104 );
105
106 crate::test_env::remove_var("LEAN_CTX_DATA_DIR");
107 }
108}