Skip to main content

oxide_cli/
cache.rs

1use std::{fs, path::Path};
2
3use anyhow::Result;
4use chrono::Utc;
5use comfy_table::{Attribute, Cell, Color, Table};
6use serde::{Deserialize, Serialize};
7
8use crate::{AppContext, templates::OxideTemplate};
9
10#[derive(Serialize, Deserialize)]
11pub struct TemplatesCache {
12  #[serde(rename = "lastUpdated")]
13  pub last_updated: String,
14  pub templates: Vec<CachedTemplate>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18pub struct CachedTemplate {
19  pub name: String,
20  pub version: String,
21  pub source: String,
22  pub path: String,
23  pub official: bool,
24  pub commit_sha: String,
25}
26
27pub fn update_templates_cache(
28  template_path: &Path,
29  path: &Path,
30  commit_sha: &str,
31) -> Result<CachedTemplate> {
32  let oxide_json = template_path.join(path).join("oxide.template.json");
33  let content = fs::read_to_string(&oxide_json)?;
34  let template_info: OxideTemplate = serde_json::from_str(&content)?;
35
36  let templates_json = template_path.join("oxide-templates.json");
37  let mut templates_info: TemplatesCache = if templates_json.exists() {
38    let content = fs::read_to_string(&templates_json)?;
39    serde_json::from_str(&content)?
40  } else {
41    TemplatesCache {
42      last_updated: Utc::now().to_rfc3339(),
43      templates: Vec::new(),
44    }
45  };
46
47  templates_info.last_updated = Utc::now().to_rfc3339();
48
49  // Replace existing entry to avoid duplicates on re-download
50  templates_info
51    .templates
52    .retain(|t| t.name != template_info.name);
53  let cached_template = CachedTemplate {
54    name: template_info.name,
55    version: template_info.version,
56    source: template_info.repository.url,
57    path: path.to_string_lossy().to_string(),
58    official: template_info.official,
59    commit_sha: commit_sha.to_string(),
60  };
61  templates_info.templates.push(cached_template.clone());
62
63  fs::write(
64    &templates_json,
65    serde_json::to_string_pretty(&templates_info)?,
66  )?;
67
68  Ok(cached_template)
69}
70
71pub fn get_cached_template(ctx: &AppContext, name: &str) -> Result<Option<CachedTemplate>> {
72  let templates_json = ctx.paths.templates.join("oxide-templates.json");
73
74  if !templates_json.exists() {
75    return Ok(None);
76  }
77
78  let content = fs::read_to_string(&templates_json)?;
79  let templates_info: TemplatesCache = serde_json::from_str(&content)?;
80
81  Ok(
82    templates_info
83      .templates
84      .into_iter()
85      .find(|t| t.name == name),
86  )
87}
88
89pub fn remove_template_from_cache(template_path: &Path, template_name: &str) -> Result<()> {
90  let templates_json = template_path.join("oxide-templates.json");
91
92  if !templates_json.exists() {
93    return Err(anyhow::anyhow!(
94      "Template '{}' is not installed",
95      template_name
96    ));
97  }
98
99  let content = fs::read_to_string(&templates_json)?;
100  let mut templates_info: TemplatesCache = serde_json::from_str(&content)?;
101
102  let exists = templates_info
103    .templates
104    .iter()
105    .any(|t| t.name == template_name);
106  if !exists {
107    return Err(anyhow::anyhow!(
108      "Template '{}' is not installed",
109      template_name
110    ));
111  }
112
113  templates_info.last_updated = Utc::now().to_rfc3339();
114
115  if let Some(t) = templates_info
116    .templates
117    .iter()
118    .find(|t| t.name == template_name)
119  {
120    let cleanup_path = template_path.join(&t.path);
121
122    if cleanup_path.exists() {
123      if let Err(e) = fs::remove_dir_all(&cleanup_path) {
124        println!("Failed to remove: {}", e);
125      }
126
127      let mut current = cleanup_path.parent();
128      while let Some(parent) = current {
129        if parent == template_path {
130          break;
131        }
132        if fs::remove_dir(parent).is_err() {
133          break;
134        }
135        current = parent.parent();
136      }
137    }
138  }
139
140  templates_info
141    .templates
142    .retain(|template| template.name != template_name);
143
144  fs::write(
145    &templates_json,
146    serde_json::to_string_pretty(&templates_info)?,
147  )?;
148
149  println!("✓ Removed template '{}'", template_name);
150  Ok(())
151}
152
153pub fn get_installed_templates(template_path: &Path) -> Result<()> {
154  let templates_json = template_path.join("oxide-templates.json");
155
156  let templates_info: TemplatesCache = if templates_json.exists() {
157    let content = fs::read_to_string(&templates_json)?;
158    serde_json::from_str(&content)?
159  } else {
160    TemplatesCache {
161      last_updated: Utc::now().to_rfc3339(),
162      templates: Vec::new(),
163    }
164  };
165
166  if templates_info.templates.is_empty() {
167    println!("No templates installed yet.");
168    return Ok(());
169  }
170
171  let mut table = Table::new();
172
173  table.set_header(vec![
174    Cell::new("Name").add_attribute(Attribute::Bold),
175    Cell::new("Version").add_attribute(Attribute::Bold),
176    Cell::new("Official").add_attribute(Attribute::Bold),
177  ]);
178
179  for template in templates_info.templates {
180    table.add_row(vec![
181      Cell::new(&template.name),
182      Cell::new(&template.version),
183      Cell::new(if template.official { "✓" } else { "✗" }).fg(if template.official {
184        Color::Green
185      } else {
186        Color::Red
187      }),
188    ]);
189  }
190
191  println!(
192    "\nInstalled templates (last updated: {}):",
193    templates_info.last_updated
194  );
195  println!("{table}");
196
197  Ok(())
198}
199
200pub fn is_template_installed(ctx: &AppContext, template_name: &str) -> Result<bool> {
201  let templates_json = ctx.paths.templates.join("oxide-templates.json");
202
203  let templates_info: TemplatesCache = if templates_json.exists() {
204    let content = fs::read_to_string(&templates_json)?;
205    serde_json::from_str(&content)?
206  } else {
207    TemplatesCache {
208      last_updated: Utc::now().to_rfc3339(),
209      templates: Vec::new(),
210    }
211  };
212
213  let path = Path::new(template_name);
214  if !ctx.paths.templates.join(path).exists() {
215    return Ok(false);
216  }
217
218  Ok(
219    templates_info
220      .templates
221      .iter()
222      .any(|t| t.name == template_name),
223  )
224}