use std::fs;
use std::io::Write;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use super::manifest::ModuleManifest;
use super::paths;
#[derive(Default, Serialize, Deserialize)]
pub struct ModuleRegistry {
#[serde(default)]
pub modules: Vec<InstalledModule>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct InstalledModule {
pub id: String,
pub root: PathBuf,
pub enabled: bool,
#[serde(default)]
pub source: Option<String>,
pub manifest: ModuleManifest,
#[serde(default)]
pub warning: Option<String>,
}
impl InstalledModule {
pub fn is_runnable(&self) -> bool {
self.enabled && self.warning.is_none() && self.manifest.allowed_on_platform()
}
}
impl ModuleRegistry {
pub fn find(&self, spec: &str) -> Option<&InstalledModule> {
match self.index_of(spec) {
Some(i) => self.modules.get(i),
None => None,
}
}
pub fn find_mut(&mut self, spec: &str) -> Option<&mut InstalledModule> {
match self.index_of(spec) {
Some(i) => self.modules.get_mut(i),
None => None,
}
}
fn index_of(&self, spec: &str) -> Option<usize> {
if let Some(i) = self.modules.iter().position(|m| m.id == spec) {
return Some(i);
}
if !spec.contains('/') {
return None;
}
let want = spec.trim_end_matches('/');
self.modules.iter().position(|m| {
m.source.as_deref().is_some_and(|s| {
let base = s.rsplit_once('@').map_or(s, |(b, _)| b);
base.eq_ignore_ascii_case(want)
})
})
}
pub fn revalidate(&mut self) {
for m in &mut self.modules {
match ModuleManifest::load(&m.root) {
Ok(fresh) => {
m.manifest = fresh;
m.warning = None;
}
Err(e) => m.warning = Some(format!("manifest unavailable: {e}")),
}
}
}
}
pub fn load() -> ModuleRegistry {
let mut reg: ModuleRegistry = fs::read_to_string(paths::registry_path())
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
reg.revalidate();
reg
}
pub fn save(reg: &ModuleRegistry) {
let dir = crate::persist::config_dir();
if fs::create_dir_all(&dir).is_err() {
return;
}
let Ok(json) = serde_json::to_string_pretty(reg) else {
return;
};
let path = paths::registry_path();
let tmp = path.with_extension("json.tmp");
if let Ok(mut f) = fs::File::create(&tmp) {
if f.write_all(json.as_bytes()).is_ok() && f.flush().is_ok() {
let _ = fs::rename(&tmp, &path);
}
}
}