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, id: &str) -> Option<&InstalledModule> {
self.modules.iter().find(|m| m.id == id)
}
pub fn find_mut(&mut self, id: &str) -> Option<&mut InstalledModule> {
self.modules.iter_mut().find(|m| m.id == id)
}
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);
}
}
}