Skip to main content

oxide_cli/addons/
cache.rs

1use std::{fs, path::Path};
2
3use anyhow::Result;
4use chrono::Utc;
5use comfy_table::{Attribute, Cell, Table};
6use serde::{Deserialize, Serialize};
7
8use super::manifest::AddonManifest;
9
10#[derive(Serialize, Deserialize)]
11pub struct AddonsCache {
12  #[serde(rename = "lastUpdated")]
13  pub last_updated: String,
14  pub addons: Vec<CachedAddon>,
15}
16
17#[derive(Serialize, Deserialize)]
18pub struct CachedAddon {
19  pub id: String,
20  pub name: String,
21  pub version: String,
22  pub path: String,
23  pub commit_sha: String,
24  pub repo_url: String,
25}
26
27fn read_cache(addons_dir: &Path) -> Result<AddonsCache> {
28  let index = addons_dir.join("oxide-addons.json");
29  if index.exists() {
30    let content = fs::read_to_string(&index)?;
31    Ok(serde_json::from_str(&content)?)
32  } else {
33    Ok(AddonsCache {
34      last_updated: Utc::now().to_rfc3339(),
35      addons: Vec::new(),
36    })
37  }
38}
39
40fn write_cache(addons_dir: &Path, cache: &AddonsCache) -> Result<()> {
41  let index = addons_dir.join("oxide-addons.json");
42  fs::write(index, serde_json::to_string_pretty(cache)?)?;
43  Ok(())
44}
45
46pub fn update_addons_cache(
47  addons_dir: &Path,
48  subdir: &str,
49  manifest: &AddonManifest,
50  commit_sha: &str,
51) -> Result<()> {
52  let mut cache = read_cache(addons_dir)?;
53
54  cache.last_updated = Utc::now().to_rfc3339();
55  cache.addons.retain(|a| a.id != manifest.id);
56  cache.addons.push(CachedAddon {
57    id: manifest.id.clone(),
58    name: manifest.name.clone(),
59    version: manifest.version.clone(),
60    path: subdir.to_string(),
61    commit_sha: commit_sha.to_string(),
62    repo_url: String::new(),
63  });
64
65  write_cache(addons_dir, &cache)
66}
67
68pub fn get_cached_addon(addons_dir: &Path, addon_id: &str) -> Result<Option<CachedAddon>> {
69  let cache = read_cache(addons_dir)?;
70  Ok(cache.addons.into_iter().find(|a| a.id == addon_id))
71}
72
73pub fn remove_addon_from_cache(addons_dir: &Path, addon_id: &str) -> Result<()> {
74  let mut cache = read_cache(addons_dir)?;
75
76  let entry = cache
77    .addons
78    .iter()
79    .find(|a| a.id == addon_id)
80    .ok_or_else(|| anyhow::anyhow!("Addon '{}' is not installed", addon_id))?;
81
82  let addon_dir = addons_dir.join(&entry.path);
83  if addon_dir.exists() {
84    fs::remove_dir_all(&addon_dir)?;
85  }
86
87  cache.last_updated = Utc::now().to_rfc3339();
88  cache.addons.retain(|a| a.id != addon_id);
89
90  write_cache(addons_dir, &cache)?;
91  println!("✓ Removed addon '{}'", addon_id);
92  Ok(())
93}
94
95pub fn get_installed_addons(addons_dir: &Path) -> Result<()> {
96  let cache = read_cache(addons_dir)?;
97
98  if cache.addons.is_empty() {
99    println!("No addons installed yet.");
100    return Ok(());
101  }
102
103  let mut table = Table::new();
104  table.set_header(vec![
105    Cell::new("ID").add_attribute(Attribute::Bold),
106    Cell::new("Name").add_attribute(Attribute::Bold),
107    Cell::new("Version").add_attribute(Attribute::Bold),
108  ]);
109
110  for addon in &cache.addons {
111    table.add_row(vec![
112      Cell::new(&addon.id),
113      Cell::new(&addon.name),
114      Cell::new(&addon.version),
115    ]);
116  }
117
118  println!(
119    "\nInstalled addons (last updated: {}):",
120    cache.last_updated
121  );
122  println!("{table}");
123
124  Ok(())
125}
126
127pub fn is_addon_installed(addons_dir: &Path, addon_id: &str) -> Result<bool> {
128  let cache = read_cache(addons_dir)?;
129  let found = cache.addons.iter().find(|a| a.id == addon_id);
130  match found {
131    Some(entry) => Ok(addons_dir.join(&entry.path).exists()),
132    None => Ok(false),
133  }
134}