use std::path::{Path, PathBuf};
use std::sync::Arc;
use async_trait::async_trait;
use crate::error::Result;
use crate::installer::InstallInfo;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ComponentType {
Msvc,
Sdk,
}
impl ComponentType {
pub fn as_str(&self) -> &'static str {
match self {
ComponentType::Msvc => "msvc",
ComponentType::Sdk => "sdk",
}
}
}
impl std::fmt::Display for ComponentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[async_trait]
pub trait ComponentDownloader: Send + Sync {
async fn download(&self) -> Result<InstallInfo>;
fn component_type(&self) -> ComponentType;
fn component_name(&self) -> &'static str {
match self.component_type() {
ComponentType::Msvc => "MSVC",
ComponentType::Sdk => "Windows SDK",
}
}
}
pub trait CacheManager: Send + Sync {
fn get(&self, key: &str) -> Option<Vec<u8>>;
fn set(&self, key: &str, value: &[u8]) -> Result<()>;
fn invalidate(&self, key: &str) -> Result<()>;
fn clear(&self) -> Result<()>;
fn cache_dir(&self) -> &Path;
fn contains(&self, key: &str) -> bool {
self.get(key).is_some()
}
fn entry_path(&self, key: &str) -> PathBuf {
self.cache_dir().join(key)
}
}
#[derive(Debug, Clone)]
pub struct FileSystemCacheManager {
cache_dir: PathBuf,
}
impl FileSystemCacheManager {
pub fn new(cache_dir: impl Into<PathBuf>) -> Self {
Self {
cache_dir: cache_dir.into(),
}
}
pub fn default_cache_dir() -> Self {
let cache_dir =
if let Some(proj) = directories::ProjectDirs::from("com", "loonghao", "msvc-kit") {
proj.cache_dir().to_path_buf()
} else {
std::env::temp_dir().join("msvc-kit").join("cache")
};
Self::new(cache_dir)
}
}
impl CacheManager for FileSystemCacheManager {
fn get(&self, key: &str) -> Option<Vec<u8>> {
let path = self.cache_dir.join(key);
std::fs::read(&path).ok()
}
fn set(&self, key: &str, value: &[u8]) -> Result<()> {
let path = self.cache_dir.join(key);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, value)?;
Ok(())
}
fn invalidate(&self, key: &str) -> Result<()> {
let path = self.cache_dir.join(key);
if path.exists() {
std::fs::remove_file(&path)?;
}
Ok(())
}
fn clear(&self) -> Result<()> {
if self.cache_dir.exists() {
std::fs::remove_dir_all(&self.cache_dir)?;
std::fs::create_dir_all(&self.cache_dir)?;
}
Ok(())
}
fn cache_dir(&self) -> &Path {
&self.cache_dir
}
}
pub type BoxedCacheManager = Arc<dyn CacheManager>;
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_filesystem_cache_manager() {
let temp_dir = TempDir::new().unwrap();
let cache = FileSystemCacheManager::new(temp_dir.path());
cache.set("test_key", b"test_value").unwrap();
assert_eq!(cache.get("test_key"), Some(b"test_value".to_vec()));
assert!(cache.contains("test_key"));
assert!(!cache.contains("nonexistent"));
cache.invalidate("test_key").unwrap();
assert!(!cache.contains("test_key"));
cache.set("nested/path/key", b"nested_value").unwrap();
assert_eq!(cache.get("nested/path/key"), Some(b"nested_value".to_vec()));
cache.set("key1", b"value1").unwrap();
cache.set("key2", b"value2").unwrap();
cache.clear().unwrap();
assert!(!cache.contains("key1"));
assert!(!cache.contains("key2"));
}
#[test]
fn test_entry_path() {
let temp_dir = TempDir::new().unwrap();
let cache = FileSystemCacheManager::new(temp_dir.path());
let path = cache.entry_path("some/key");
assert_eq!(path, temp_dir.path().join("some/key"));
}
}