mod manager;
mod utils;
mod xml;
pub(crate) use manager::{RegistryEntry, RegistryManager};
use std::{collections::HashMap, fs, path::PathBuf};
use crate::{
Result,
types::{AvailableUpdate, ComponentType, InstalledComponent},
};
pub(crate) fn scan_registry_components(
component_type: ComponentType,
) -> Result<Vec<InstalledComponent>> {
let Some(manager) = RegistryManager::for_component_type(component_type) else {
return Ok(Vec::new());
};
let entries = manager.read_entries()?;
let components = entries
.into_iter()
.filter_map(|entry| {
let directory_name = utils::extract_directory_name(&entry.installed_path)?;
let path = utils::resolve_component_path(entry.installed_path);
Some(InstalledComponent {
name: entry.name,
directory_name,
version: entry.version,
component_type,
path,
is_system: false,
release_date: entry.release_date,
})
})
.collect();
Ok(components)
}
pub(crate) fn load_registry_map(component_type: ComponentType) -> HashMap<String, RegistryEntry> {
RegistryManager::for_component_type(component_type)
.map(|m| m.load_entry_map())
.unwrap_or_default()
}
pub(crate) fn registry_path(component_type: ComponentType) -> Option<PathBuf> {
component_type
.registry_file()
.map(|f| crate::paths::knewstuff_dir().join(f))
}
fn is_system_path(path: &str) -> bool {
path.starts_with("/usr") || path.starts_with("/lib")
}
pub(crate) fn build_id_cache(system: bool) -> HashMap<String, u64> {
let mut cache = HashMap::new();
let knewstuff = crate::paths::knewstuff_dir();
for &ct in ComponentType::all() {
let Some(file) = ct.registry_file() else {
continue;
};
let path = knewstuff.join(file);
let Ok(content) = fs::read_to_string(&path) else {
continue;
};
for raw in xml::parse_raw_entries(&content) {
let Some(id) = raw.content_id() else {
continue;
};
if let Some(installed_path) = raw.first_installed_path()
&& let Some(dir_name) = utils::extract_directory_name(&installed_path)
{
let path_str = installed_path.to_string_lossy();
if system == is_system_path(&path_str) {
cache.insert(dir_name, id);
}
}
}
}
cache
}
pub(crate) fn update_registry_after_install(update: &AvailableUpdate) -> Result<()> {
let component = &update.installed;
let Some(reg_path) = registry_path(component.component_type) else {
log::debug!(
target: "registry",
"no registry file for {}",
component.component_type
);
return Ok(());
};
let release_date = utils::extract_date_from_iso(&update.release_date);
if let Some(parent) = reg_path.parent() {
fs::create_dir_all(parent)?;
}
let content = if reg_path.exists() {
fs::read_to_string(®_path)?
} else {
xml::create_empty_registry()
};
let fields = xml::UpdateFields {
directory_name: &component.directory_name,
content_id: update.content_id,
new_version: &update.latest_version,
download_url: &update.download_url,
installed_path: &component.path,
release_date: &release_date,
};
let updated = xml::update_entry(&content, &fields)?;
if let Some(new_content) = updated {
fs::write(®_path, new_content)?;
log::debug!(
target: "registry",
"updated {} for {}",
reg_path.display(),
component.name
);
} else {
let entry = xml::NewEntry {
name: &component.name,
component_type: component.component_type,
content_id: update.content_id,
version: &update.latest_version,
download_url: &update.download_url,
installed_path: &component.path,
release_date: &release_date,
};
let new_content = xml::add_entry(&content, &entry);
fs::write(®_path, new_content)?;
log::debug!(
target: "registry",
"added {} to {}",
component.name,
reg_path.display()
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_system_path_detects_system_paths() {
assert!(is_system_path("/usr/share/plasma/plasmoids/foo"));
assert!(is_system_path("/usr/lib/something"));
assert!(is_system_path("/lib/firmware/thing"));
assert!(!is_system_path("/home/user/.local/share/plasma/plasmoids/foo"));
assert!(!is_system_path("/tmp/test"));
}
}