diff options
| author | pablu <me@pablu.de> | 2026-06-02 19:55:57 +0200 |
|---|---|---|
| committer | pablu <me@pablu.de> | 2026-06-02 19:55:57 +0200 |
| commit | 0f9492c0da3ac6d391b7b0d32ea2a391b94a18f4 (patch) | |
| tree | a1fb59a02068ad49ea40d055e3b5e00da3db63a3 /src/gentoo | |
| parent | b547e6758c635682c94794e5c13070a73c918203 (diff) | |
near feature parity with previous architecture
Diffstat (limited to 'src/gentoo')
| -rw-r--r-- | src/gentoo/mod.rs | 2 | ||||
| -rw-r--r-- | src/gentoo/portage.rs | 347 | ||||
| -rw-r--r-- | src/gentoo/portage_loader.rs | 344 |
3 files changed, 356 insertions, 337 deletions
diff --git a/src/gentoo/mod.rs b/src/gentoo/mod.rs index e2cc3df..f1bc744 100644 --- a/src/gentoo/mod.rs +++ b/src/gentoo/mod.rs @@ -1,7 +1,9 @@ pub mod package; pub mod portage; +pub mod portage_loader; pub mod useflags; pub use package::InstalledPackage; pub use portage::Portage; pub use useflags::UseFlag; +// pub use portage_loader diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs index b444a5b..c837eb6 100644 --- a/src/gentoo/portage.rs +++ b/src/gentoo/portage.rs @@ -1,16 +1,8 @@ -use std::{ - collections::{HashMap, HashSet}, - fs, - path::{Path, PathBuf}, -}; - -use chrono::DateTime; -use color_eyre::eyre::Context; -use quick_xml::{Reader, events::Event}; +use std::collections::HashSet; use crate::gentoo::{ - InstalledPackage, UseFlag, - package::{Metadata, Package, PackageKey, Version}, + InstalledPackage, + package::{Package, PackageKey}, }; #[derive(Debug, Default)] @@ -20,71 +12,6 @@ pub struct Portage { pub available_packages: Vec<Package>, } -fn split_pkg(input: &str) -> (&str, &str) { - let bytes = input.as_bytes(); - - for i in (0..bytes.len()).rev() { - if bytes[i] == b'-' - && let Some(next) = bytes.get(i + 1) - && next.is_ascii_digit() - { - let name = &input[..i]; - let version = &input[i + 1..]; - return (name, version); - } - } - - (input, "") -} - -fn extract_maintainer(path: &Path) -> color_eyre::Result<Option<String>> { - let s = fs::read_to_string(path)?; - let mut reader = Reader::from_str(s.as_str()); - reader.config_mut().trim_text(true); - - let mut buf = Vec::new(); - let mut in_maintainer = false; - - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(e)) if e.name().as_ref() == b"maintainer" => { - in_maintainer = true; - } - - Ok(Event::Text(e)) if in_maintainer => { - match e.xml_content() { - Ok(s) => { - return Ok(Some(s.into())); - } - Err(_) => return Ok(None), - }; - } - - Ok(Event::End(e)) if e.name().as_ref() == b"maintainer" => { - in_maintainer = false; - } - - Ok(Event::Eof) | Err(_) => break, - - _ => {} - } - - buf.clear(); - } - - Ok(None) -} - -fn parse_cache_file(content: String) -> HashMap<String, String> { - content - .lines() - .filter_map(|line| { - line.split_once('=') - .map(|(k, v)| (k.to_string(), v.to_string())) - }) - .collect() -} - impl Portage { pub fn new() -> Self { Self { @@ -94,270 +21,16 @@ impl Portage { } } - pub fn load_world_packages() -> color_eyre::Result<HashSet<PackageKey>> { - let world = fs::read_to_string("/var/lib/portage/world")?; - Ok(world - .split_whitespace() - .filter_map(PackageKey::from_atom) - .collect()) - } - - pub fn load_available_packages(&mut self) -> color_eyre::Result<()> { - let repos = fs::read_dir("/var/db/repos/").wrap_err("failed to read /var/db/repos")?; - - let mut packages: HashMap<PackageKey, Package> = HashMap::new(); - - for repo in repos { - let Ok(repo) = repo else { - continue; - }; - let repo_name = repo.file_name(); - let Some(repo_name) = repo_name.to_str() else { - continue; - }; - - let Ok(categories) = fs::read_dir(repo.path().join("metadata/md5-cache")) else { - // TODO: Someday parse actual ebuilds instead of md5-cache - continue; - }; - - for category in categories { - let Ok(category) = category else { - continue; - }; - - let Ok(cat_name) = category.file_name().into_string() else { - continue; - }; - - if cat_name == "Manifest.gz" { - continue; - } - - let pkgs = fs::read_dir(category.path())?; - - for pkg in pkgs { - let Ok(pkg) = pkg else { - continue; - }; - - let Ok(pkg_name) = pkg.file_name().into_string() else { - continue; - }; - - if pkg_name == "Manifest.gz" { - continue; - } - - let (pkg_name, pkg_version) = split_pkg(&pkg_name); - - let version_file = fs::read_to_string(pkg.path()).wrap_err_with(|| { - format!( - "failed to read version_file for {}/{}-{} in repo {}", - cat_name, pkg_name, pkg_version, repo_name - ) - })?; - let data = parse_cache_file(version_file); - - let homepages: Vec<String> = data - .get("HOMEPAGE") - .and_then(|s| { - Some( - s.split_whitespace() - .map(std::string::ToString::to_string) - .collect(), - ) - }) - .unwrap_or(vec![]); - - let keywords: Vec<String> = data - .get("KEYWORDS") - .and_then(|s| { - Some( - s.split_whitespace() - .map(std::string::ToString::to_string) - .collect(), - ) - }) - .unwrap_or(vec![]); - - let iuse: Vec<UseFlag> = data - .get("IUSE") - .and_then(|s| { - Some( - s.split_whitespace() - .map(|original_iuse| { - let iuse = original_iuse - .strip_prefix("+") - .unwrap_or(original_iuse); - - UseFlag { - name: iuse.to_string(), - default: iuse != original_iuse, - } - }) - .collect(), - ) - }) - .unwrap_or(vec![]); - - let repo_path = PathBuf::from("/var/db/repos/") - .join(&repo_name) - .join(&cat_name) - .join(pkg_name) - .join("metadata.xml"); - - let maintainer = extract_maintainer(&repo_path).unwrap_or(None); - - let version = Version { - version: pkg_version.to_string(), - metadata: Metadata { - maintainer: maintainer, - description: data.get("DESCRIPTION").cloned(), - homepage: homepages, - license: data.get("LICENSE").cloned(), - repository: repo_name.to_string(), - }, - keywords: keywords, - iuse: iuse, - }; - - let pkg_key = PackageKey { - category: cat_name.clone(), - name: pkg_name.to_string(), - }; - - if let Some(package) = packages.get_mut(&pkg_key) { - package.versions.push(version); - } else { - let pkg = Package { - atom: pkg_key.clone(), - versions: vec![version], - }; - packages.insert(pkg_key, pkg); - } - } - } + pub fn get_installed_package(&self, key: PackageKey) -> Option<InstalledPackage> { + let pkg = self.installed_packages.iter().find(|pkg| pkg.atom == key); + if let Some(pkg) = pkg { + return Some(pkg.clone()); } - self.available_packages = packages.into_values().collect(); - Ok(()) + None } - pub fn load_installed_packages() -> color_eyre::Result<Vec<InstalledPackage>> { - let categories = fs::read_dir("/var/db/pkg")?; - - let mut v = vec![]; - - for category in categories { - let Ok(category) = category else { - continue; - }; - - let Ok(cat_name) = category.file_name().into_string() else { - continue; - }; - - let pkgs = fs::read_dir(category.path())?; - for pkg in pkgs { - let Ok(pkg) = pkg else { - continue; - }; - - let pkg_name = pkg.file_name(); - let Some(pkg_name) = pkg_name.to_str() else { - continue; - }; - - let (pkg_name, pkg_version) = split_pkg(pkg_name); - - let use_flags_file = fs::read_to_string(pkg.path().join("USE"))?; - let enabled_use_flags: HashSet<String> = use_flags_file - .split_whitespace() - .map(|s| s.to_string()) - .collect(); - - let iuse_flags_file = fs::read_to_string(pkg.path().join("IUSE"))?; - - let use_flags: Vec<UseFlag> = iuse_flags_file - .split_whitespace() - .map(|original_iuse| { - let iuse = original_iuse.strip_prefix("+").unwrap_or(original_iuse); - - UseFlag { - name: iuse.to_string(), - default: iuse != original_iuse, - } - }) - .collect(); - - let repository = fs::read_to_string(pkg.path().join("repository"))? - .trim() - .to_string(); - - let homepage: Vec<String> = match fs::read_to_string(pkg.path().join("HOMEPAGE")) { - Ok(homepage) => homepage - .split_whitespace() - .map(std::string::ToString::to_string) - .collect(), - Err(_) => vec![], - }; - - let license: Option<String> = match fs::read_to_string(pkg.path().join("LICENSE")) { - Ok(license) => Some(license.trim().to_string()), - Err(_) => None, - }; - - let description: Option<String> = - match fs::read_to_string(pkg.path().join("DESCRIPTION")) { - Ok(description) => Some(description.trim().to_string()), - Err(_) => None, - }; - - let size: usize = fs::read_to_string(pkg.path().join("SIZE"))? - .trim() - .parse() - .unwrap_or(0); - - let repo_path = PathBuf::from("/var/db/repos/") - .join(&repository) - .join(&cat_name) - .join(pkg_name) - .join("metadata.xml"); - - let build_time = fs::read_to_string(pkg.path().join("BUILD_TIME"))? - .trim() - .parse::<i64>() - .ok() - .and_then(DateTime::from_timestamp_secs) - .unwrap_or(DateTime::from_timestamp_nanos(0)); - - let maintainer = extract_maintainer(&repo_path).unwrap_or(None); - - let atom = PackageKey { - category: cat_name.clone(), - name: pkg_name.to_string(), - }; - - v.push(InstalledPackage { - atom: atom, - version: pkg_version.to_string(), - build_time, - slot: 0, - metadata: Metadata { - maintainer, - description, - homepage: homepage, - license, - repository: repository, - }, - enabled_use_flags: enabled_use_flags, - iuse: use_flags, - size, - }); - } - } - - Ok(v) + pub fn total_installed_size(&self) -> usize { + self.installed_packages.iter().map(|p| p.size).sum() } } diff --git a/src/gentoo/portage_loader.rs b/src/gentoo/portage_loader.rs new file mode 100644 index 0000000..792e53d --- /dev/null +++ b/src/gentoo/portage_loader.rs @@ -0,0 +1,344 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, +}; + +use chrono::DateTime; +use color_eyre::eyre::Context; +use quick_xml::{Reader, events::Event}; + +use crate::gentoo::{ + InstalledPackage, UseFlag, + package::{Metadata, Package, PackageKey, Version}, +}; + +fn split_pkg(input: &str) -> (&str, &str) { + let bytes = input.as_bytes(); + + for i in (0..bytes.len()).rev() { + if bytes[i] == b'-' + && let Some(next) = bytes.get(i + 1) + && next.is_ascii_digit() + { + let name = &input[..i]; + let version = &input[i + 1..]; + return (name, version); + } + } + + (input, "") +} + +fn extract_maintainer(path: &Path) -> color_eyre::Result<Option<String>> { + let s = fs::read_to_string(path)?; + let mut reader = Reader::from_str(s.as_str()); + reader.config_mut().trim_text(true); + + let mut buf = Vec::new(); + let mut in_maintainer = false; + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(e)) if e.name().as_ref() == b"maintainer" => { + in_maintainer = true; + } + + Ok(Event::Text(e)) if in_maintainer => { + match e.xml_content() { + Ok(s) => { + return Ok(Some(s.into())); + } + Err(_) => return Ok(None), + }; + } + + Ok(Event::End(e)) if e.name().as_ref() == b"maintainer" => { + in_maintainer = false; + } + + Ok(Event::Eof) | Err(_) => break, + + _ => {} + } + + buf.clear(); + } + + Ok(None) +} + +fn parse_cache_file(content: String) -> HashMap<String, String> { + content + .lines() + .filter_map(|line| { + line.split_once('=') + .map(|(k, v)| (k.to_string(), v.to_string())) + }) + .collect() +} + +pub fn load_world_packages() -> color_eyre::Result<HashSet<PackageKey>> { + let world = fs::read_to_string("/var/lib/portage/world")?; + Ok(world + .split_whitespace() + .filter_map(PackageKey::from_atom) + .collect()) +} + +pub fn load_available_packages() -> color_eyre::Result<Vec<Package>> { + let repos = fs::read_dir("/var/db/repos/").wrap_err("failed to read /var/db/repos")?; + + let mut packages: HashMap<PackageKey, Package> = HashMap::new(); + + for repo in repos { + let Ok(repo) = repo else { + continue; + }; + let repo_name = repo.file_name(); + let Some(repo_name) = repo_name.to_str() else { + continue; + }; + + let Ok(categories) = fs::read_dir(repo.path().join("metadata/md5-cache")) else { + // TODO: Someday parse actual ebuilds instead of md5-cache + continue; + }; + + for category in categories { + let Ok(category) = category else { + continue; + }; + + let Ok(cat_name) = category.file_name().into_string() else { + continue; + }; + + if cat_name == "Manifest.gz" { + continue; + } + + let pkgs = fs::read_dir(category.path())?; + + for pkg in pkgs { + let Ok(pkg) = pkg else { + continue; + }; + + let Ok(pkg_name) = pkg.file_name().into_string() else { + continue; + }; + + if pkg_name == "Manifest.gz" { + continue; + } + + let (pkg_name, pkg_version) = split_pkg(&pkg_name); + + let version_file = fs::read_to_string(pkg.path()).wrap_err_with(|| { + format!( + "failed to read version_file for {}/{}-{} in repo {}", + cat_name, pkg_name, pkg_version, repo_name + ) + })?; + let data = parse_cache_file(version_file); + + let homepages: Vec<String> = data + .get("HOMEPAGE") + .and_then(|s| { + Some( + s.split_whitespace() + .map(std::string::ToString::to_string) + .collect(), + ) + }) + .unwrap_or(vec![]); + + let keywords: Vec<String> = data + .get("KEYWORDS") + .and_then(|s| { + Some( + s.split_whitespace() + .map(std::string::ToString::to_string) + .collect(), + ) + }) + .unwrap_or(vec![]); + + let iuse: Vec<UseFlag> = data + .get("IUSE") + .and_then(|s| { + Some( + s.split_whitespace() + .map(|original_iuse| { + let iuse = + original_iuse.strip_prefix("+").unwrap_or(original_iuse); + + UseFlag { + name: iuse.to_string(), + default: iuse != original_iuse, + } + }) + .collect(), + ) + }) + .unwrap_or(vec![]); + + let repo_path = PathBuf::from("/var/db/repos/") + .join(&repo_name) + .join(&cat_name) + .join(pkg_name) + .join("metadata.xml"); + + let maintainer = extract_maintainer(&repo_path).unwrap_or(None); + + let version = Version { + version: pkg_version.to_string(), + metadata: Metadata { + maintainer: maintainer, + description: data.get("DESCRIPTION").cloned(), + homepage: homepages, + license: data.get("LICENSE").cloned(), + repository: repo_name.to_string(), + }, + keywords: keywords, + iuse: iuse, + }; + + let pkg_key = PackageKey { + category: cat_name.clone(), + name: pkg_name.to_string(), + }; + + if let Some(package) = packages.get_mut(&pkg_key) { + package.versions.push(version); + } else { + let pkg = Package { + atom: pkg_key.clone(), + versions: vec![version], + }; + packages.insert(pkg_key, pkg); + } + } + } + } + + Ok(packages.into_values().collect()) +} + +pub fn load_installed_packages() -> color_eyre::Result<Vec<InstalledPackage>> { + let categories = fs::read_dir("/var/db/pkg")?; + + let mut v = vec![]; + + for category in categories { + let Ok(category) = category else { + continue; + }; + + let Ok(cat_name) = category.file_name().into_string() else { + continue; + }; + + let pkgs = fs::read_dir(category.path())?; + for pkg in pkgs { + let Ok(pkg) = pkg else { + continue; + }; + + let pkg_name = pkg.file_name(); + let Some(pkg_name) = pkg_name.to_str() else { + continue; + }; + + let (pkg_name, pkg_version) = split_pkg(pkg_name); + + let use_flags_file = fs::read_to_string(pkg.path().join("USE"))?; + let enabled_use_flags: HashSet<String> = use_flags_file + .split_whitespace() + .map(|s| s.to_string()) + .collect(); + + let iuse_flags_file = fs::read_to_string(pkg.path().join("IUSE"))?; + + let use_flags: Vec<UseFlag> = iuse_flags_file + .split_whitespace() + .map(|original_iuse| { + let iuse = original_iuse.strip_prefix("+").unwrap_or(original_iuse); + + UseFlag { + name: iuse.to_string(), + default: iuse != original_iuse, + } + }) + .collect(); + + let repository = fs::read_to_string(pkg.path().join("repository"))? + .trim() + .to_string(); + + let homepage: Vec<String> = match fs::read_to_string(pkg.path().join("HOMEPAGE")) { + Ok(homepage) => homepage + .split_whitespace() + .map(std::string::ToString::to_string) + .collect(), + Err(_) => vec![], + }; + + let license: Option<String> = match fs::read_to_string(pkg.path().join("LICENSE")) { + Ok(license) => Some(license.trim().to_string()), + Err(_) => None, + }; + + let description: Option<String> = + match fs::read_to_string(pkg.path().join("DESCRIPTION")) { + Ok(description) => Some(description.trim().to_string()), + Err(_) => None, + }; + + let size: usize = fs::read_to_string(pkg.path().join("SIZE"))? + .trim() + .parse() + .unwrap_or(0); + + let repo_path = PathBuf::from("/var/db/repos/") + .join(&repository) + .join(&cat_name) + .join(pkg_name) + .join("metadata.xml"); + + let build_time = fs::read_to_string(pkg.path().join("BUILD_TIME"))? + .trim() + .parse::<i64>() + .ok() + .and_then(DateTime::from_timestamp_secs) + .unwrap_or(DateTime::from_timestamp_nanos(0)); + + let maintainer = extract_maintainer(&repo_path).unwrap_or(None); + + let atom = PackageKey { + category: cat_name.clone(), + name: pkg_name.to_string(), + }; + + v.push(InstalledPackage { + atom: atom, + version: pkg_version.to_string(), + build_time, + slot: 0, + metadata: Metadata { + maintainer, + description, + homepage: homepage, + license, + repository: repository, + }, + enabled_use_flags: enabled_use_flags, + iuse: use_flags, + size, + }); + } + } + + Ok(v) +} |
