1use std::cmp::Reverse;
5use std::path::{Path, PathBuf};
6
7use anyhow::Result;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13pub struct ScanSummarySnapshot {
14 pub files_analyzed: u64,
15 pub files_skipped: u64,
16 pub total_physical_lines: u64,
17 pub code_lines: u64,
18 pub comment_lines: u64,
19 pub blank_lines: u64,
20 #[serde(default)]
21 pub functions: u64,
22 #[serde(default)]
23 pub classes: u64,
24 #[serde(default)]
25 pub variables: u64,
26 #[serde(default)]
27 pub imports: u64,
28 #[serde(default)]
29 pub test_count: u64,
30 #[serde(default)]
31 pub coverage_lines_found: u64,
32 #[serde(default)]
33 pub coverage_lines_hit: u64,
34 #[serde(default)]
35 pub coverage_functions_found: u64,
36 #[serde(default)]
37 pub coverage_functions_hit: u64,
38 #[serde(default)]
39 pub coverage_branches_found: u64,
40 #[serde(default)]
41 pub coverage_branches_hit: u64,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct RegistryEntry {
47 pub run_id: String,
48 pub timestamp_utc: DateTime<Utc>,
49 pub project_label: String,
50 pub input_roots: Vec<String>,
51 pub json_path: Option<PathBuf>,
52 pub html_path: Option<PathBuf>,
53 #[serde(default)]
54 pub pdf_path: Option<PathBuf>,
55 #[serde(default)]
56 pub csv_path: Option<PathBuf>,
57 #[serde(default)]
58 pub xlsx_path: Option<PathBuf>,
59 pub summary: ScanSummarySnapshot,
60 #[serde(default)]
62 pub git_branch: Option<String>,
63 #[serde(default)]
65 pub git_commit: Option<String>,
66 #[serde(default)]
68 pub git_author: Option<String>,
69 #[serde(default)]
71 pub git_tags: Option<String>,
72 #[serde(default)]
74 pub git_nearest_tag: Option<String>,
75 #[serde(default)]
77 pub git_commit_date: Option<String>,
78}
79
80#[derive(Debug, Default, Serialize, Deserialize)]
83pub struct WatchedDirsStore {
84 pub dirs: Vec<PathBuf>,
85}
86
87impl WatchedDirsStore {
88 #[must_use]
89 pub fn load(path: &Path) -> Self {
90 std::fs::read_to_string(path)
91 .ok()
92 .and_then(|s| serde_json::from_str(&s).ok())
93 .unwrap_or_default()
94 }
95
96 pub fn save(&self, path: &Path) -> Result<()> {
100 if let Some(parent) = path.parent() {
101 std::fs::create_dir_all(parent)?;
102 }
103 std::fs::write(path, serde_json::to_string_pretty(self)?)?;
104 Ok(())
105 }
106
107 pub fn add(&mut self, dir: PathBuf) {
108 if !self.dirs.contains(&dir) {
109 self.dirs.push(dir);
110 }
111 }
112
113 pub fn remove(&mut self, dir: &Path) {
114 self.dirs.retain(|d| d != dir);
115 }
116}
117
118#[derive(Debug, Default, Serialize, Deserialize)]
121pub struct ScanRegistry {
122 pub entries: Vec<RegistryEntry>,
123}
124
125impl ScanRegistry {
126 #[must_use]
128 pub fn load(registry_path: &Path) -> Self {
129 std::fs::read_to_string(registry_path)
130 .ok()
131 .and_then(|s| serde_json::from_str(&s).ok())
132 .unwrap_or_default()
133 }
134
135 pub fn save(&self, registry_path: &Path) -> Result<()> {
139 if let Some(parent) = registry_path.parent() {
140 std::fs::create_dir_all(parent)?;
141 }
142 let json = serde_json::to_string_pretty(self)?;
143 std::fs::write(registry_path, json)?;
144 Ok(())
145 }
146
147 pub fn add_entry(&mut self, entry: RegistryEntry) {
148 self.entries.retain(|e| e.run_id != entry.run_id);
149 self.entries.push(entry);
150 self.entries.sort_by_key(|e| Reverse(e.timestamp_utc));
151 }
152
153 #[must_use]
155 pub fn entries_for_roots(&self, roots: &[String]) -> Vec<&RegistryEntry> {
156 self.entries
157 .iter()
158 .filter(|e| e.input_roots == roots)
159 .collect()
160 }
161
162 #[must_use]
163 pub fn find_by_run_id(&self, run_id: &str) -> Option<&RegistryEntry> {
164 self.entries.iter().find(|e| e.run_id == run_id)
165 }
166
167 pub fn prune_stale(&mut self) {
169 self.entries
170 .retain(|e| e.json_path.as_ref().is_none_or(|p| p.exists()));
171 }
172}
173
174const fn default_interval_hours() -> u32 {
175 24
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct CleanupPolicy {
181 pub enabled: bool,
182 #[serde(default)]
184 pub max_age_days: Option<u32>,
185 #[serde(default)]
187 pub max_run_count: Option<u32>,
188 #[serde(default = "default_interval_hours")]
190 pub interval_hours: u32,
191}
192
193#[derive(Debug, Default, Clone, Serialize, Deserialize)]
196pub struct CleanupPolicyStore {
197 pub policy: Option<CleanupPolicy>,
198 #[serde(default)]
200 pub last_run_at: Option<DateTime<Utc>>,
201 #[serde(default)]
203 pub last_run_deleted: Option<u32>,
204}
205
206impl CleanupPolicyStore {
207 #[must_use]
208 pub fn load(path: &Path) -> Self {
209 std::fs::read_to_string(path)
210 .ok()
211 .and_then(|s| serde_json::from_str(&s).ok())
212 .unwrap_or_default()
213 }
214
215 pub fn save(&self, path: &Path) -> Result<()> {
219 if let Some(parent) = path.parent() {
220 std::fs::create_dir_all(parent)?;
221 }
222 std::fs::write(path, serde_json::to_string_pretty(self)?)?;
223 Ok(())
224 }
225}