use std::path::{Path, PathBuf};
pub struct Cache {
cache_dir: PathBuf,
}
impl Cache {
pub fn new() -> Option<Self> {
let cache_dir = dirs::cache_dir()?.join("fastc").join("deps");
Some(Self { cache_dir })
}
pub fn with_dir(cache_dir: PathBuf) -> Self {
Self { cache_dir }
}
pub fn dir(&self) -> &Path {
&self.cache_dir
}
pub fn ensure_dir(&self) -> std::io::Result<()> {
std::fs::create_dir_all(&self.cache_dir)
}
pub fn dep_path(&self, name: &str, url: &str, version: &str) -> PathBuf {
let hash = Self::hash_dep(url, version);
self.cache_dir.join(name).join(hash)
}
pub fn is_cached(&self, name: &str, url: &str, version: &str) -> bool {
let path = self.dep_path(name, url, version);
path.exists() && path.join("fastc.toml").exists()
}
fn hash_dep(url: &str, version: &str) -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
url.hash(&mut hasher);
version.hash(&mut hasher);
format!("{:016x}", hasher.finish())
}
pub fn list_cached(&self) -> std::io::Result<Vec<CachedDep>> {
let mut deps = Vec::new();
if !self.cache_dir.exists() {
return Ok(deps);
}
for entry in std::fs::read_dir(&self.cache_dir)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
let name = entry.file_name().to_string_lossy().to_string();
for version_entry in std::fs::read_dir(entry.path())? {
let version_entry = version_entry?;
if version_entry.file_type()?.is_dir() {
deps.push(CachedDep {
name: name.clone(),
path: version_entry.path(),
});
}
}
}
}
Ok(deps)
}
pub fn clean(&self) -> std::io::Result<()> {
if self.cache_dir.exists() {
std::fs::remove_dir_all(&self.cache_dir)?;
}
Ok(())
}
}
impl Default for Cache {
fn default() -> Self {
Self::new().expect("could not determine cache directory")
}
}
#[derive(Debug, Clone)]
pub struct CachedDep {
pub name: String,
pub path: PathBuf,
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_dep_path() {
let temp = TempDir::new().unwrap();
let cache = Cache::with_dir(temp.path().to_path_buf());
let path = cache.dep_path("mylib", "https://github.com/user/mylib", "v1.0.0");
assert!(path.starts_with(temp.path()));
assert!(path.to_string_lossy().contains("mylib"));
}
#[test]
fn test_hash_consistency() {
let hash1 = Cache::hash_dep("https://example.com/repo", "v1.0");
let hash2 = Cache::hash_dep("https://example.com/repo", "v1.0");
assert_eq!(hash1, hash2);
let hash3 = Cache::hash_dep("https://example.com/repo", "v2.0");
assert_ne!(hash1, hash3);
}
}