lean_ctx/core/shadow/
persistence.rs1use super::{ShadowReport, runtime::load_shadow_config};
3use chrono::Utc;
4use std::path::{Path, PathBuf};
5
6pub fn persist_report(report: &ShadowReport) -> std::io::Result<PathBuf> {
7 let dir = report_dir();
8 std::fs::create_dir_all(&dir)?;
9 let path = dir.join(format!(
10 "shadow_report_{}.json",
11 Utc::now().format("%Y%m%d_%H%M%S")
12 ));
13 std::fs::write(
14 &path,
15 serde_json::to_vec_pretty(report).map_err(std::io::Error::other)?,
16 )?;
17 for old in list_reports_in(&dir).into_iter().rev().skip(50) {
18 std::fs::remove_file(old)?;
19 }
20 Ok(path)
21}
22pub fn list_reports() -> Vec<PathBuf> {
23 list_reports_in(&report_dir())
24}
25pub fn load_report(path: &Path) -> Option<ShadowReport> {
26 std::fs::read(path)
27 .ok()
28 .and_then(|body| serde_json::from_slice(&body).ok())
29}
30fn report_dir() -> PathBuf {
31 let configured = load_shadow_config().report_dir;
32 if configured.trim().is_empty() {
33 crate::core::paths::state_dir()
34 .map(|d| d.join("shadow_reports"))
35 .unwrap_or_else(|_| PathBuf::from("shadow_reports"))
36 } else {
37 PathBuf::from(configured)
38 }
39}
40fn list_reports_in(dir: &Path) -> Vec<PathBuf> {
41 let mut paths = std::fs::read_dir(dir)
42 .into_iter()
43 .flatten()
44 .flatten()
45 .map(|e| e.path())
46 .filter(|p| p.extension().is_some_and(|e| e == "json"))
47 .collect::<Vec<_>>();
48 paths.sort();
49 paths
50}