pub mod paths;
use dashmap::DashMap;
use rayon::prelude::*;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
pub use paths::{get_cache_dir, get_findings_cache_path, get_git_cache_path, get_graph_db_path, get_graph_stats_path, ensure_cache_dir};
static GLOBAL_CACHE: OnceLock<FileCache> = OnceLock::new();
pub fn global_cache() -> &'static FileCache {
GLOBAL_CACHE.get_or_init(FileCache::new)
}
pub fn warm_global_cache(root: &Path, extensions: &[&str]) {
global_cache().warm(root, extensions);
}
#[derive(Clone)]
pub struct FileCache {
contents: Arc<DashMap<PathBuf, Arc<String>>>,
lines: Arc<DashMap<PathBuf, Arc<Vec<String>>>>,
}
impl FileCache {
pub fn new() -> Self {
Self {
contents: Arc::new(DashMap::new()),
lines: Arc::new(DashMap::new()),
}
}
pub fn warm(&self, root: &Path, extensions: &[&str]) {
let walker = ignore::WalkBuilder::new(root)
.hidden(false)
.git_ignore(true)
.build();
let paths: Vec<PathBuf> = walker
.filter_map(|e| e.ok())
.filter(|e| e.path().is_file())
.filter(|e| {
e.path()
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| extensions.contains(&ext))
.unwrap_or(false)
})
.map(|e| e.path().to_path_buf())
.collect();
paths.par_iter().for_each(|path| {
if let Ok(content) = std::fs::read_to_string(path) {
self.contents.insert(path.clone(), Arc::new(content));
}
});
}
pub fn get_content(&self, path: &Path) -> Option<Arc<String>> {
if let Some(content) = self.contents.get(path) {
return Some(Arc::clone(&content));
}
if let Ok(content) = std::fs::read_to_string(path) {
let arc = Arc::new(content);
self.contents.insert(path.to_path_buf(), Arc::clone(&arc));
Some(arc)
} else {
None
}
}
pub fn get_lines(&self, path: &Path) -> Option<Arc<Vec<String>>> {
if let Some(lines) = self.lines.get(path) {
return Some(Arc::clone(&lines));
}
let content = self.get_content(path)?;
let lines: Vec<String> = content.lines().map(String::from).collect();
let arc = Arc::new(lines);
self.lines.insert(path.to_path_buf(), Arc::clone(&arc));
Some(arc)
}
pub fn cached_paths(&self) -> Vec<PathBuf> {
self.contents.iter().map(|r| r.key().clone()).collect()
}
pub fn paths_with_ext(&self, extensions: &[&str]) -> Vec<PathBuf> {
self.contents
.iter()
.filter(|r| {
r.key()
.extension()
.and_then(|e| e.to_str())
.map(|e| extensions.contains(&e))
.unwrap_or(false)
})
.map(|r| r.key().clone())
.collect()
}
pub fn stats(&self) -> (usize, usize) {
(self.contents.len(), self.lines.len())
}
}
impl Default for FileCache {
fn default() -> Self {
Self::new()
}
}