diff options
Diffstat (limited to 'src/gentoo')
| -rw-r--r-- | src/gentoo/mod.rs | 2 | ||||
| -rw-r--r-- | src/gentoo/package.rs | 92 | ||||
| -rw-r--r-- | src/gentoo/portage.rs | 281 | ||||
| -rw-r--r-- | src/gentoo/useflags.rs | 11 |
4 files changed, 290 insertions, 96 deletions
diff --git a/src/gentoo/mod.rs b/src/gentoo/mod.rs index ff343bc..e2cc3df 100644 --- a/src/gentoo/mod.rs +++ b/src/gentoo/mod.rs @@ -2,6 +2,6 @@ pub mod package; pub mod portage; pub mod useflags; -pub use package::Package; +pub use package::InstalledPackage; pub use portage::Portage; pub use useflags::UseFlag; diff --git a/src/gentoo/package.rs b/src/gentoo/package.rs index a74a09c..d1596f7 100644 --- a/src/gentoo/package.rs +++ b/src/gentoo/package.rs @@ -1,44 +1,70 @@ use std::collections::HashSet; +use chrono::{DateTime, Utc}; + use crate::gentoo::UseFlag; -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct Package { +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct PackageKey { + pub category: String, pub name: String, - pub use_flags: HashSet<UseFlag>, - pub version: String, - pub repository: String, +} + +impl PackageKey { + pub fn from_atom(s: &str) -> Option<Self> { + let (category, name) = s.split_once('/')?; + Some(Self { + category: category.to_string(), + name: name.to_string(), + }) + } + + pub fn qualified_name(&self) -> String { + format!("{}/{}", self.category, self.name) + } +} + +#[derive(Debug, Clone)] +pub struct Metadata { pub maintainer: Option<String>, pub description: Option<String>, - pub homepage: Option<Vec<String>>, + pub homepage: Vec<String>, pub license: Option<String>, + pub repository: String, +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct InstalledPackage { + pub atom: PackageKey, + + pub version: String, + + pub build_time: DateTime<Utc>, + + pub slot: i32, + + pub metadata: Metadata, + + pub enabled_use_flags: HashSet<String>, + pub iuse: Vec<UseFlag>, + pub size: usize, } -// #[allow(dead_code)] -// impl Package { -// pub fn new( -// name: String, -// version: String, -// repository: String, -// size: usize, -// homepage: Option<Vec<String>>, -// license: Option<String>, -// description: Option<String>, -// maintainer: Option<String>, -// use_flags: HashSet<UseFlag>, -// ) -> Self { -// Package { -// name, -// use_flags, -// version, -// repository, -// maintainer, -// description, -// homepage, -// license, -// size, -// } -// } -// } +#[derive(Debug)] +pub struct Package { + pub atom: PackageKey, + + pub versions: Vec<Version>, +} + +#[derive(Debug, Clone)] +pub struct Version { + pub version: String, + + pub metadata: Metadata, + + pub keywords: Vec<String>, + pub iuse: Vec<UseFlag>, +} diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs index bc9bb32..51dc60a 100644 --- a/src/gentoo/portage.rs +++ b/src/gentoo/portage.rs @@ -1,20 +1,26 @@ use std::{ - collections::HashSet, - fs, io, + collections::{HashMap, HashSet}, + fs, path::{Path, PathBuf}, }; +use chrono::DateTime; +use color_eyre::eyre::Context; use quick_xml::{Reader, events::Event}; -use crate::gentoo::{Package, UseFlag}; +use crate::gentoo::{ + InstalledPackage, UseFlag, + package::{Metadata, Package, PackageKey, Version}, +}; #[derive(Debug, Default)] pub struct Portage { - pub installed_packages: Vec<Package>, - pub world_packages: Vec<String>, + pub installed_packages: Vec<InstalledPackage>, + pub world_packages: HashSet<PackageKey>, + pub available_packages: Vec<Package>, } -fn split_pkg(input: &str) -> (&str, Option<&str>) { +fn split_pkg(input: &str) -> (&str, &str) { let bytes = input.as_bytes(); for i in (0..bytes.len()).rev() { @@ -24,14 +30,14 @@ fn split_pkg(input: &str) -> (&str, Option<&str>) { { let name = &input[..i]; let version = &input[i + 1..]; - return (name, Some(version)); + return (name, version); } } - (input, None) + (input, "") } -fn extract_maintainer(path: &Path) -> io::Result<Option<String>> { +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); @@ -69,32 +75,189 @@ fn extract_maintainer(path: &Path) -> io::Result<Option<String>> { 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 { installed_packages: vec![], - world_packages: vec![], + world_packages: HashSet::new(), + available_packages: vec![], } } - pub fn load_world_packages(&mut self) -> io::Result<()> { + pub fn load_world_packages() -> color_eyre::Result<HashSet<PackageKey>> { let world = fs::read_to_string("/var/lib/portage/world")?; - self.world_packages = world + Ok(world .split_whitespace() - .map(std::string::ToString::to_string) - .collect(); + .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); + } + } + } + } + + self.available_packages = packages.into_values().collect(); Ok(()) } - pub fn load_installed_packages(&mut self) -> io::Result<()> { + 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 cat_name = category.file_name(); + 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 { @@ -109,37 +272,36 @@ impl Portage { let (pkg_name, pkg_version) = split_pkg(pkg_name); let use_flags_file = fs::read_to_string(pkg.path().join("USE"))?; - let use_flags: HashSet<&str> = use_flags_file.trim().split(' ').collect(); + 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 iuse_flags: Vec<&str> = iuse_flags_file.trim().split(' ').collect(); - let use_flags: HashSet<UseFlag> = iuse_flags - .iter() - .filter(|x| !x.trim().is_empty()) - .map(|&original_iuse| { + let use_flags: Vec<UseFlag> = iuse_flags_file + .split_whitespace() + .map(|original_iuse| { let iuse = original_iuse.strip_prefix("+").unwrap_or(original_iuse); - let is_default = iuse != original_iuse; - - let is_active = use_flags.contains(&iuse); - UseFlag::new(iuse.into(), is_active, is_default) + 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: Option<Vec<String>> = - match fs::read_to_string(pkg.path().join("HOMEPAGE")) { - Ok(homepage) => Some( - homepage - .split_whitespace() - .map(std::string::ToString::to_string) - .collect(), - ), - Err(_) => None, - }; + + 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()), @@ -152,33 +314,50 @@ impl Portage { Err(_) => None, }; - let size: usize = match fs::read_to_string(pkg.path().join("SIZE")) { - Ok(size) => size.trim().parse().unwrap_or(0), - Err(_) => 0, - }; + 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.to_str().unwrap_or_default()) + .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)?; - self.installed_packages.push(Package { - name: format!("{}/{}", cat_name.to_str().unwrap_or_default(), pkg_name), - use_flags, - version: pkg_version.unwrap_or_default().into(), - repository, - maintainer, - description, - homepage, - license, + 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(()) + Ok(v) } } diff --git a/src/gentoo/useflags.rs b/src/gentoo/useflags.rs index 1e83b32..2bcd678 100644 --- a/src/gentoo/useflags.rs +++ b/src/gentoo/useflags.rs @@ -1,16 +1,5 @@ #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct UseFlag { pub name: String, - pub enabled: bool, pub default: bool, } - -impl UseFlag { - pub fn new(name: String, enabled: bool, default: bool) -> Self { - UseFlag { - name, - enabled, - default, - } - } -} |
