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