1use {
12 crate::{definition::BenchmarkDefinition, error::BenchError},
13 std::{
14 fs,
15 path::{Path, PathBuf},
16 },
17};
18
19pub fn base_cache_dir() -> Result<PathBuf, BenchError> {
27 let cache = dirs::cache_dir().ok_or(BenchError::NoCacheDir)?;
28 Ok(cache.join("kermit").join("benchmarks"))
29}
30
31pub fn cache_dir(benchmark_name: &str) -> Result<PathBuf, BenchError> {
38 Ok(base_cache_dir()?.join(benchmark_name))
39}
40
41pub fn relation_cache_path(
48 benchmark_name: &str, relation_name: &str,
49) -> Result<PathBuf, BenchError> {
50 Ok(cache_dir(benchmark_name)?.join(format!("{relation_name}.parquet")))
51}
52
53pub fn is_cached(benchmark: &BenchmarkDefinition) -> Result<bool, BenchError> {
60 for rel in &benchmark.relations {
61 let path = relation_cache_path(&benchmark.name, &rel.name)?;
62 if !path.exists() {
63 return Ok(false);
64 }
65 }
66 Ok(true)
67}
68
69pub fn ensure_cached(benchmark: &BenchmarkDefinition) -> Result<Vec<PathBuf>, BenchError> {
84 let mut paths = Vec::with_capacity(benchmark.relations.len());
85
86 for rel in &benchmark.relations {
87 let path = relation_cache_path(&benchmark.name, &rel.name)?;
88 if !path.exists() {
89 eprintln!(" downloading {} from {}...", rel.name, rel.url);
90 download_file(&rel.url, &path)?;
91 }
92 paths.push(path);
93 }
94
95 Ok(paths)
96}
97
98fn download_file(url: &str, dest: &Path) -> Result<(), BenchError> {
100 if let Some(parent) = dest.parent() {
101 fs::create_dir_all(parent)?;
102 }
103
104 let part_path = dest.with_extension("parquet.part");
105
106 let response = reqwest::blocking::get(url)
107 .and_then(|r| r.error_for_status())
108 .map_err(|source| BenchError::Download {
109 url: url.to_string(),
110 source,
111 })?;
112
113 let bytes = response.bytes().map_err(|source| BenchError::Download {
114 url: url.to_string(),
115 source,
116 })?;
117
118 fs::write(&part_path, &bytes)?;
119 fs::rename(&part_path, dest)?;
120
121 Ok(())
122}
123
124pub fn clean_benchmark(name: &str) -> Result<(), BenchError> {
135 let dir = cache_dir(name)?;
136 if dir.exists() {
137 fs::remove_dir_all(&dir)?;
138 }
139 Ok(())
140}
141
142pub fn clean_all() -> Result<(), BenchError> {
153 let dir = base_cache_dir()?;
154 if dir.exists() {
155 fs::remove_dir_all(&dir)?;
156 }
157 Ok(())
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn cache_dir_structure() {
166 let dir = cache_dir("triangle").unwrap();
167 assert!(dir.ends_with("kermit/benchmarks/triangle"));
168 }
169
170 #[test]
171 fn relation_cache_path_structure() {
172 let path = relation_cache_path("triangle", "edge").unwrap();
173 assert!(path.ends_with("kermit/benchmarks/triangle/edge.parquet"));
174 }
175
176 #[test]
177 fn is_cached_false_when_missing() {
178 let def = BenchmarkDefinition {
179 name: "nonexistent_test_benchmark".to_string(),
180 description: String::new(),
181 relations: vec![crate::definition::RelationSource {
182 name: "r".to_string(),
183 url: "http://x".to_string(),
184 }],
185 queries: vec![crate::definition::QueryDefinition {
186 name: "q".to_string(),
187 description: "test".to_string(),
188 query: "Q(X) :- r(X).".to_string(),
189 }],
190 generator: None,
191 };
192 assert!(!is_cached(&def).unwrap());
193 }
194
195 #[test]
196 fn clean_nonexistent_is_noop() {
197 assert!(clean_benchmark("this_benchmark_does_not_exist_12345").is_ok());
198 }
199}