oxide_cli/addons/
cache.rs1use 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}