mod fingerprint;
mod hasher;
mod identity;
use std::path::{Path, PathBuf};
use super::RunnerKind;
use fingerprint::fingerprint;
use identity::{disabled, identity};
const KEEP_PER_KIND: usize = 6;
pub struct Cache {
dir: PathBuf,
key: String,
}
impl Cache {
pub fn open(kind: &RunnerKind, root: &Path, entry: &Path) -> Option<Self> {
if disabled() {
return None;
}
let salt = format!(
"{}|{}|{}",
env!("CARGO_PKG_VERSION"),
identity(kind),
entry.display()
);
let key = fingerprint(root, &salt)?;
Some(Self {
dir: root.join(".fusible").join("cache"),
key,
})
}
pub fn short_key(&self) -> &str {
&self.key[..8]
}
fn path(&self, kind: &str) -> PathBuf {
self.dir.join(format!("{kind}-{}", self.key))
}
pub fn get(&self, kind: &str) -> Option<PathBuf> {
Some(self.path(kind)).filter(|path| path.is_file())
}
pub fn restore(&self, kind: &str, dest: &Path) -> bool {
let Some(hit) = self.get(kind) else {
return false;
};
eprintln!(
"⚡ {kind} unchanged ({}), reusing the cache",
self.short_key()
);
std::fs::copy(hit, dest).is_ok()
}
pub fn put(&self, kind: &str, source: &Path) {
if std::fs::create_dir_all(&self.dir).is_err() {
return;
}
let target = self.path(kind);
let partial = target.with_extension(format!("{}.part", std::process::id()));
if std::fs::copy(source, &partial).is_err() || std::fs::rename(&partial, &target).is_err() {
let _ = std::fs::remove_file(&partial);
return;
}
self.prune(kind);
}
fn prune(&self, kind: &str) {
let prefix = format!("{kind}-");
let mut entries: Vec<_> = std::fs::read_dir(&self.dir)
.into_iter()
.flatten()
.flatten()
.filter(|e| e.file_name().to_string_lossy().starts_with(&prefix))
.filter_map(|e| Some((e.metadata().ok()?.modified().ok()?, e.path())))
.collect();
entries.sort_by_key(|entry| std::cmp::Reverse(entry.0));
for (_, path) in entries.into_iter().skip(KEEP_PER_KIND) {
let _ = std::fs::remove_file(path);
}
}
}