use std::collections::BTreeSet;
use super::{
package::Package,
state::{InstallState, RegistryState},
workspace::Workspace,
};
use crate::Registry;
pub struct RegistryListing {
pub name: String,
pub outcome: Result<Vec<ComponentStatus>, String>,
}
pub struct ComponentStatus {
pub name: String,
pub status: InstallStatus,
}
pub enum InstallStatus {
Available { hash: String },
UpToDate { hash: String },
Update { installed: String, latest: String },
Orphaned { installed: String },
}
pub fn list(package: &Package, selected: Option<&str>) -> Result<Vec<RegistryListing>, String> {
let state = InstallState::load(package)?;
let workspace = Workspace::load(package)?;
let names: BTreeSet<String> = workspace
.available_registries()
.into_iter()
.chain(state.registries.keys().cloned())
.collect();
if let Some(name) = selected
&& !names.contains(name)
{
return Err(format!("unknown registry `{name}`"));
}
let empty = RegistryState::default();
let listings = names
.iter()
.filter(|name| selected.is_none_or(|chosen| chosen == name.as_str()))
.map(|name| {
let tracked = state.registries.get(name).unwrap_or(&empty);
listing_for(&workspace, name, tracked)
})
.collect();
Ok(listings)
}
fn listing_for(workspace: &Workspace, name: &str, state: &RegistryState) -> RegistryListing {
let outcome = match workspace.registry_dir(name).and_then(|dir| {
Registry::load(dir).map_err(|error| format!("failed to load registry `{name}`: {error}"))
}) {
Ok(registry) => statuses(®istry, state),
Err(error) if state.components.is_empty() => Err(error),
Err(_) => Ok(orphaned(state)),
};
RegistryListing {
name: name.to_string(),
outcome,
}
}
fn statuses(registry: &Registry, state: &RegistryState) -> Result<Vec<ComponentStatus>, String> {
let names: Vec<&str> = registry.names().collect();
let mut out = Vec::new();
for component_name in &names {
let component = registry
.get(component_name)
.expect("name came from the registry");
let latest = component
.hash()
.map_err(|error| format!("failed to hash component `{component_name}`: {error}"))?;
let status = match state.components.get(*component_name) {
None => InstallStatus::Available { hash: latest },
Some(installed) if installed.hash == latest => InstallStatus::UpToDate { hash: latest },
Some(installed) => InstallStatus::Update {
installed: installed.hash.clone(),
latest,
},
};
out.push(ComponentStatus {
name: (*component_name).to_string(),
status,
});
}
for (component_name, installed) in &state.components {
if !names.contains(&component_name.as_str()) {
out.push(ComponentStatus {
name: component_name.clone(),
status: InstallStatus::Orphaned {
installed: installed.hash.clone(),
},
});
}
}
Ok(out)
}
fn orphaned(state: &RegistryState) -> Vec<ComponentStatus> {
state
.components
.iter()
.map(|(name, installed)| ComponentStatus {
name: name.clone(),
status: InstallStatus::Orphaned {
installed: installed.hash.clone(),
},
})
.collect()
}