use crate::error::{Error, Result};
use crate::templates::manifest::TemplateManifest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub mod github;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CachedTemplate {
pub name: String,
pub source: String,
pub version: String,
pub fetched_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub cache_path: PathBuf,
pub manifest: TemplateManifest,
}
pub struct TemplateRepository {
cache_dir: PathBuf,
index: TemplateIndex,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TemplateIndex {
pub templates: HashMap<String, CachedTemplate>,
pub version: u32,
}
impl TemplateRepository {
pub fn new() -> Result<Self> {
let cache_dir = Self::cache_dir()?;
std::fs::create_dir_all(&cache_dir).map_err(|e| {
Error::template(format!("Failed to create template cache directory: {e}"))
})?;
let index = Self::load_index(&cache_dir)?;
Ok(Self { cache_dir, index })
}
fn cache_dir() -> Result<PathBuf> {
let config_dir =
dirs::config_dir().ok_or_else(|| Error::config("Could not find config directory"))?;
Ok(config_dir.join("ferrous-forge").join("templates"))
}
fn load_index(cache_dir: &Path) -> Result<TemplateIndex> {
let index_path = cache_dir.join("index.json");
if index_path.exists() {
let content = std::fs::read_to_string(&index_path)
.map_err(|e| Error::template(format!("Failed to read template index: {e}")))?;
let index: TemplateIndex = serde_json::from_str(&content)
.map_err(|e| Error::template(format!("Failed to parse template index: {e}")))?;
Ok(index)
} else {
Ok(TemplateIndex {
version: 1,
templates: HashMap::new(),
})
}
}
fn save_index(&self) -> Result<()> {
let index_path = self.cache_dir.join("index.json");
let content = serde_json::to_string_pretty(&self.index)
.map_err(|e| Error::template(format!("Failed to serialize template index: {e}")))?;
std::fs::write(&index_path, content)
.map_err(|e| Error::template(format!("Failed to write template index: {e}")))?;
Ok(())
}
pub fn list_cached(&self) -> Vec<&CachedTemplate> {
self.index.templates.values().collect()
}
pub fn get_cached(&self, name: &str) -> Option<&CachedTemplate> {
self.index.templates.get(name)
}
pub fn is_cached(&self, name: &str) -> bool {
self.index.templates.contains_key(name)
}
pub fn add_to_cache(&mut self, template: CachedTemplate) -> Result<()> {
self.index.templates.insert(template.name.clone(), template);
self.save_index()
}
pub fn remove_from_cache(&mut self, name: &str) -> Result<()> {
if let Some(template) = self.index.templates.remove(name) {
if template.cache_path.exists() {
std::fs::remove_dir_all(&template.cache_path).map_err(|e| {
Error::template(format!("Failed to remove cached template: {e}"))
})?;
}
}
self.save_index()
}
pub fn cache_directory(&self) -> &Path {
&self.cache_dir
}
pub fn template_cache_path(&self, name: &str) -> PathBuf {
self.cache_dir.join(name)
}
}
impl CachedTemplate {
pub fn needs_update(&self) -> bool {
let age = chrono::Utc::now() - self.updated_at;
age.num_hours() >= 24
}
}