ferrous_forge/templates/repository/
mod.rs1use crate::error::{Error, Result};
13use crate::templates::manifest::TemplateManifest;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::path::{Path, PathBuf};
17
18pub mod github;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CachedTemplate {
24 pub name: String,
26 pub source: String,
28 pub version: String,
30 pub fetched_at: chrono::DateTime<chrono::Utc>,
32 pub updated_at: chrono::DateTime<chrono::Utc>,
34 pub cache_path: PathBuf,
36 pub manifest: TemplateManifest,
38}
39
40pub struct TemplateRepository {
42 cache_dir: PathBuf,
44 index: TemplateIndex,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Default)]
50pub struct TemplateIndex {
51 pub templates: HashMap<String, CachedTemplate>,
53 pub version: u32,
55}
56
57impl TemplateRepository {
58 pub fn new() -> Result<Self> {
64 let cache_dir = Self::cache_dir()?;
65 std::fs::create_dir_all(&cache_dir).map_err(|e| {
66 Error::template(format!("Failed to create template cache directory: {e}"))
67 })?;
68
69 let index = Self::load_index(&cache_dir)?;
70
71 Ok(Self { cache_dir, index })
72 }
73
74 fn cache_dir() -> Result<PathBuf> {
76 let config_dir =
77 dirs::config_dir().ok_or_else(|| Error::config("Could not find config directory"))?;
78 Ok(config_dir.join("ferrous-forge").join("templates"))
79 }
80
81 fn load_index(cache_dir: &Path) -> Result<TemplateIndex> {
83 let index_path = cache_dir.join("index.json");
84 if index_path.exists() {
85 let content = std::fs::read_to_string(&index_path)
86 .map_err(|e| Error::template(format!("Failed to read template index: {e}")))?;
87 let index: TemplateIndex = serde_json::from_str(&content)
88 .map_err(|e| Error::template(format!("Failed to parse template index: {e}")))?;
89 Ok(index)
90 } else {
91 Ok(TemplateIndex {
92 version: 1,
93 templates: HashMap::new(),
94 })
95 }
96 }
97
98 fn save_index(&self) -> Result<()> {
100 let index_path = self.cache_dir.join("index.json");
101 let content = serde_json::to_string_pretty(&self.index)
102 .map_err(|e| Error::template(format!("Failed to serialize template index: {e}")))?;
103 std::fs::write(&index_path, content)
104 .map_err(|e| Error::template(format!("Failed to write template index: {e}")))?;
105 Ok(())
106 }
107
108 pub fn list_cached(&self) -> Vec<&CachedTemplate> {
110 self.index.templates.values().collect()
111 }
112
113 pub fn get_cached(&self, name: &str) -> Option<&CachedTemplate> {
115 self.index.templates.get(name)
116 }
117
118 pub fn is_cached(&self, name: &str) -> bool {
120 self.index.templates.contains_key(name)
121 }
122
123 pub fn add_to_cache(&mut self, template: CachedTemplate) -> Result<()> {
129 self.index.templates.insert(template.name.clone(), template);
130 self.save_index()
131 }
132
133 pub fn remove_from_cache(&mut self, name: &str) -> Result<()> {
139 if let Some(template) = self.index.templates.remove(name) {
140 if template.cache_path.exists() {
142 std::fs::remove_dir_all(&template.cache_path).map_err(|e| {
143 Error::template(format!("Failed to remove cached template: {e}"))
144 })?;
145 }
146 }
147 self.save_index()
148 }
149
150 pub fn cache_directory(&self) -> &Path {
152 &self.cache_dir
153 }
154
155 pub fn template_cache_path(&self, name: &str) -> PathBuf {
157 self.cache_dir.join(name)
158 }
159}
160
161impl CachedTemplate {
162 pub fn needs_update(&self) -> bool {
164 let age = chrono::Utc::now() - self.updated_at;
165 age.num_hours() >= 24
166 }
167}