provenant/cache/
config.rs1use std::fs;
2use std::io;
3use std::path::{Path, PathBuf};
4
5use directories::ProjectDirs;
6
7use super::locking::scans_lock_path;
8
9pub const DEFAULT_CACHE_DIR_NAME: &str = ".provenant-cache";
10pub const CACHE_DIR_ENV_VAR: &str = "PROVENANT_CACHE";
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct CacheConfig {
14 root_dir: PathBuf,
15 incremental: bool,
16}
17
18impl CacheConfig {
19 #[cfg(test)]
20 pub fn new(root_dir: PathBuf) -> Self {
21 Self {
22 root_dir,
23 incremental: false,
24 }
25 }
26
27 pub fn with_options(root_dir: PathBuf, incremental: bool) -> Self {
28 Self {
29 root_dir,
30 incremental,
31 }
32 }
33
34 #[cfg(test)]
35 pub fn from_scan_root(scan_root: &Path) -> Self {
36 Self::new(scan_root.join(DEFAULT_CACHE_DIR_NAME))
37 }
38
39 fn project_cache_root() -> Option<PathBuf> {
40 ProjectDirs::from("com", "Provenant", "provenant")
41 .map(|dirs| dirs.cache_dir().to_path_buf())
42 }
43
44 pub fn default_root_dir(scan_root: &Path) -> PathBuf {
45 Self::project_cache_root().unwrap_or_else(|| scan_root.join(DEFAULT_CACHE_DIR_NAME))
46 }
47
48 pub fn resolve_root_dir(
49 scan_root: &Path,
50 cli_cache_dir: Option<&Path>,
51 env_cache_dir: Option<&Path>,
52 ) -> PathBuf {
53 if let Some(path) = cli_cache_dir {
54 return path.to_path_buf();
55 }
56
57 if let Some(path) = env_cache_dir {
58 return path.to_path_buf();
59 }
60
61 Self::default_root_dir(scan_root)
62 }
63
64 pub fn from_overrides(
65 scan_root: &Path,
66 cli_cache_dir: Option<&Path>,
67 env_cache_dir: Option<&Path>,
68 incremental: bool,
69 ) -> Self {
70 Self::with_options(
71 Self::resolve_root_dir(scan_root, cli_cache_dir, env_cache_dir),
72 incremental,
73 )
74 }
75
76 pub fn root_dir(&self) -> &Path {
77 &self.root_dir
78 }
79
80 pub fn incremental_dir(&self) -> PathBuf {
81 self.root_dir.join("incremental")
82 }
83
84 pub const fn incremental_enabled(&self) -> bool {
85 self.incremental
86 }
87
88 pub fn ensure_dirs(&self) -> io::Result<()> {
89 if self.incremental_enabled() {
90 fs::create_dir_all(self.incremental_dir())?;
91 }
92 Ok(())
93 }
94
95 #[cfg(test)]
96 pub fn clear(&self) -> io::Result<()> {
97 if self.root_dir().exists() {
98 fs::remove_dir_all(&self.root_dir)?;
99 }
100 Ok(())
101 }
102
103 pub fn clear_contents(&self) -> io::Result<()> {
104 if !self.root_dir().exists() {
105 return Ok(());
106 }
107
108 let lock_path = scans_lock_path(self.root_dir());
109 for entry in fs::read_dir(self.root_dir())? {
110 let entry = entry?;
111 let path = entry.path();
112 if path == lock_path {
113 continue;
114 }
115
116 if path.is_dir() {
117 fs::remove_dir_all(path)?;
118 } else {
119 fs::remove_file(path)?;
120 }
121 }
122
123 Ok(())
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use tempfile::TempDir;
130
131 use super::*;
132
133 #[test]
134 fn test_from_scan_root_uses_expected_directory_name() {
135 let temp_dir = TempDir::new().expect("Failed to create temp dir");
136 let config = CacheConfig::from_scan_root(temp_dir.path());
137 assert_eq!(
138 config.root_dir(),
139 temp_dir.path().join(DEFAULT_CACHE_DIR_NAME)
140 );
141 }
142
143 #[test]
144 fn test_ensure_dirs_creates_expected_tree() {
145 let temp_dir = TempDir::new().expect("Failed to create temp dir");
146 let config = CacheConfig::with_options(temp_dir.path().join(DEFAULT_CACHE_DIR_NAME), true);
147
148 config
149 .ensure_dirs()
150 .expect("Failed to create cache directories");
151
152 assert!(config.root_dir().exists());
153 assert!(config.incremental_dir().exists());
154 }
155
156 #[test]
157 fn test_resolve_root_dir_prefers_cli_then_env_then_default() {
158 let scan_root = Path::new("/scan-root");
159 let cli_dir = Path::new("/cli-cache");
160 let env_dir = Path::new("/env-cache");
161
162 assert_eq!(
163 CacheConfig::resolve_root_dir(scan_root, Some(cli_dir), Some(env_dir)),
164 cli_dir
165 );
166 assert_eq!(
167 CacheConfig::resolve_root_dir(scan_root, None, Some(env_dir)),
168 env_dir
169 );
170 assert_eq!(
171 CacheConfig::resolve_root_dir(scan_root, None, None),
172 CacheConfig::default_root_dir(scan_root)
173 );
174 }
175
176 #[test]
177 fn test_clear_removes_cache_root_directory() {
178 let temp_dir = TempDir::new().expect("Failed to create temp dir");
179 let config = CacheConfig::with_options(temp_dir.path().join("cache-root"), true);
180
181 config
182 .ensure_dirs()
183 .expect("Failed to create cache directories");
184 assert!(config.root_dir().exists());
185
186 config.clear().expect("Failed to clear cache directory");
187 assert!(!config.root_dir().exists());
188 }
189}