diff --git a/Cargo.lock b/Cargo.lock index 98c0f68f3..32bf0d408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.68.1" @@ -3125,6 +3134,7 @@ version = "0.45.5" dependencies = [ "ahash", "apt-auth-config", + "bincode", "bon", "chrono", "cxx", diff --git a/data/apt.conf.d/50oma.conf b/data/apt.conf.d/50oma.conf index 2875346dc..3fee7052c 100644 --- a/data/apt.conf.d/50oma.conf +++ b/data/apt.conf.d/50oma.conf @@ -75,3 +75,7 @@ Acquire::IndexTargets { Identifier "Contents-deb"; } } + +APT::Update::Post-Invoke-Success { + "oma gencache"; +}; diff --git a/oma-pm/Cargo.toml b/oma-pm/Cargo.toml index c20290583..ec0c9d74b 100644 --- a/oma-pm/Cargo.toml +++ b/oma-pm/Cargo.toml @@ -39,6 +39,7 @@ memchr = "2" serde = { version = "1", features = ["derive"] } apt-auth-config = { version = "0.2.0", path = "../apt-auth-config" } once_cell = "1.20" +bincode = "1.3.3" [dev-dependencies] flume = "0.11" diff --git a/oma-pm/src/search.rs b/oma-pm/src/search.rs index 5977fd706..47abe47ea 100644 --- a/oma-pm/src/search.rs +++ b/oma-pm/src/search.rs @@ -1,5 +1,4 @@ use ahash::{AHashMap, RandomState}; -use cxx::UniquePtr; use glob_match::glob_match; use indexmap::map::Entry; use indicium::simple::{Indexable, SearchIndex}; @@ -7,11 +6,16 @@ use memchr::memmem; use oma_apt::{ cache::{Cache, PackageSort}, error::{AptError, AptErrors}, - raw::{IntoRawIter, PkgIterator}, + raw::IntoRawIter, Package, }; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; +use std::{ + fmt::Debug, + fs, + io::{self, BufReader, ErrorKind}, +}; +use tracing::warn; type IndexSet = indexmap::IndexSet; type IndexMap = indexmap::IndexMap; @@ -56,13 +60,13 @@ impl Ord for PackageStatus { } } +#[derive(Serialize, Deserialize)] pub struct SearchEntry { name: String, description: String, - status: PackageStatus, provides: IndexSet, has_dbg: bool, - raw_pkg: UniquePtr, + #[cfg(feature = "aosc")] section_is_base: bool, } @@ -71,11 +75,8 @@ impl Debug for SearchEntry { f.debug_struct("SearchEntry") .field("pkgname", &self.name) .field("description", &self.description) - .field("status", &self.status) .field("provides", &self.provides) .field("has_dbg", &self.has_dbg) - .field("raw_pkg", &self.raw_pkg.fullname(true)) - .field("section_is_base", &self.section_is_base) .finish() } } @@ -116,6 +117,7 @@ pub struct SearchResult { pub full_match: bool, pub dbg_package: bool, pub status: PackageStatus, + #[cfg(feature = "aosc")] pub is_base: bool, } @@ -140,7 +142,9 @@ impl OmaSearch for IndiciumSearch<'_> { } for i in res { - let entry = self.search_result(i, Some(&query))?; + let Some(entry) = self.search_result(i, Some(&query))? else { + continue; + }; search_res.push(entry); } @@ -159,127 +163,43 @@ impl OmaSearch for IndiciumSearch<'_> { impl<'a> IndiciumSearch<'a> { pub fn new(cache: &'a Cache, progress: impl Fn(usize)) -> OmaSearchResult { - let sort = PackageSort::default().include_virtual(); - let packages = cache.packages(&sort); - - let mut pkg_map = IndexMap::with_hasher(RandomState::new()); - - for (i, pkg) in packages.enumerate() { - let name = pkg.fullname(true); - progress(i); - - if name.contains("-dbg") { - continue; - } - - let status = if pkg.is_upgradable() { - PackageStatus::Upgrade - } else if pkg.is_installed() { - PackageStatus::Installed - } else { - PackageStatus::Avail - }; - - if let Some(cand) = pkg.candidate() { - if let Entry::Vacant(e) = pkg_map.entry(name.clone()) { - e.insert(SearchEntry { - name, - description: cand - .summary() - .unwrap_or_else(|| "No description".to_string()), - status, - provides: pkg.provides().map(|x| x.to_string()).collect(), - has_dbg: has_dbg(cache, &pkg, &cand), - raw_pkg: unsafe { pkg.unique() } - .make_safe() - .ok_or(OmaSearchError::PtrIsNone(PtrIsNone))?, - section_is_base: cand.section().map(|x| x == "Bases").unwrap_or(false), - }); - } - } else { - // Provides - let mut real_pkgs = vec![]; - for i in pkg.provides() { - real_pkgs.push(( - i.name().to_string(), - unsafe { i.target_pkg() } - .make_safe() - .ok_or(OmaSearchError::PtrIsNone(PtrIsNone))?, - )); - } - - for (provide, i) in real_pkgs { - let pkg = Package::new(cache, i); - let name = pkg.fullname(true); - - let status = if pkg.is_upgradable() { - PackageStatus::Upgrade - } else if pkg.is_installed() { - PackageStatus::Installed - } else { - PackageStatus::Avail - }; + let f: Option> = + fs::File::open("/var/cache/oma/search_index_cache") + .ok() + .map(BufReader::new) + .and_then(|reader| bincode::deserialize_from(reader).ok()); + + if let Some(f) = f { + let mut search_index: SearchIndex = SearchIndex::default(); + + f.iter().enumerate().for_each(|(index, (key, value))| { + search_index.insert(key, value); + progress(index); + }); - if let Some(cand) = pkg.candidate() { - pkg_map - .entry(name.clone()) - .and_modify(|x| { - if !x.provides.contains(&provide) { - x.provides.insert(provide.clone()); - } - }) - .or_insert(SearchEntry { - name, - description: cand - .summary() - .unwrap_or_else(|| "No description".to_string()), - status, - provides: { - let mut set = IndexSet::with_hasher(RandomState::new()); - set.insert(provide.clone()); - set - }, - has_dbg: has_dbg(cache, &pkg, &cand), - raw_pkg: unsafe { pkg.unique() } - .make_safe() - .ok_or(OmaSearchError::PtrIsNone(PtrIsNone))?, - section_is_base: cand - .section() - .map(|x| x == "Bases") - .unwrap_or(false), - }); - } - } - } + Ok(Self { + cache, + pkg_map: f, + index: search_index, + }) + } else { + make_search_cache(cache, progress) } - - let mut search_index: SearchIndex = SearchIndex::default(); - - pkg_map - .iter() - .for_each(|(key, value)| search_index.insert(key, value)); - - Ok(Self { - cache, - pkg_map, - index: search_index, - }) } pub fn search_result( &self, i: &str, query: Option<&str>, - ) -> Result { + ) -> Result, OmaSearchError> { let entry = self.pkg_map.get(i).unwrap(); let search_name = entry.name.clone(); let desc = entry.description.clone(); - let status = entry.status; let has_dbg = entry.has_dbg; - let pkg = unsafe { entry.raw_pkg.unique() } - .make_safe() - .ok_or(OmaSearchError::PtrIsNone(PtrIsNone))?; - let pkg = Package::new(self.cache, pkg); + + let Some(pkg) = self.cache.get(&search_name) else { + return Ok(None); + }; let full_match = if let Some(query) = query { query == search_name || entry.provides.iter().any(|x| x == query) @@ -287,6 +207,14 @@ impl<'a> IndiciumSearch<'a> { false }; + let status = if pkg.is_upgradable() { + PackageStatus::Upgrade + } else if pkg.is_installed() { + PackageStatus::Installed + } else { + PackageStatus::Avail + }; + let old_version = if status != PackageStatus::Upgrade { None } else { @@ -298,9 +226,10 @@ impl<'a> IndiciumSearch<'a> { .map(|x| x.version().to_string()) .ok_or_else(|| OmaSearchError::FailedGetCandidate(pkg.fullname(true)))?; + #[cfg(feature = "aosc")] let is_base = entry.section_is_base; - Ok(SearchResult { + Ok(Some(SearchResult { name: pkg.fullname(true), desc, old_version, @@ -308,11 +237,110 @@ impl<'a> IndiciumSearch<'a> { full_match, dbg_package: has_dbg, status, + #[cfg(feature = "aosc")] is_base, - }) + })) } } +pub fn make_search_cache( + cache: &Cache, + progress: impl Fn(usize), +) -> Result, OmaSearchError> { + let sort = PackageSort::default().include_virtual(); + let packages = cache.packages(&sort); + + let mut pkg_map = IndexMap::with_hasher(RandomState::new()); + + for (i, pkg) in packages.enumerate() { + let name = pkg.fullname(true); + progress(i); + + if name.contains("-dbg") { + continue; + } + + if let Some(cand) = pkg.candidate() { + if let Entry::Vacant(e) = pkg_map.entry(name.clone()) { + e.insert(SearchEntry { + name, + description: cand + .summary() + .unwrap_or_else(|| "No description".to_string()), + provides: pkg.provides().map(|x| x.to_string()).collect(), + has_dbg: has_dbg(cache, &pkg, &cand), + #[cfg(feature = "aosc")] + section_is_base: cand.section().map(|x| x == "Bases").unwrap_or(false), + }); + } + } else { + // Provides + let mut real_pkgs = vec![]; + for i in pkg.provides() { + real_pkgs.push(( + i.name().to_string(), + unsafe { i.target_pkg() } + .make_safe() + .ok_or(OmaSearchError::PtrIsNone(PtrIsNone))?, + )); + } + + for (provide, i) in real_pkgs { + let pkg = Package::new(cache, i); + let name = pkg.fullname(true); + + if let Some(cand) = pkg.candidate() { + pkg_map + .entry(name.clone()) + .and_modify(|x| { + if !x.provides.contains(&provide) { + x.provides.insert(provide.clone()); + } + }) + .or_insert(SearchEntry { + name, + description: cand + .summary() + .unwrap_or_else(|| "No description".to_string()), + provides: { + let mut set = IndexSet::with_hasher(RandomState::new()); + set.insert(provide.clone()); + set + }, + has_dbg: has_dbg(cache, &pkg, &cand), + #[cfg(feature = "aosc")] + section_is_base: cand.section().map(|x| x == "Bases").unwrap_or(false), + }); + } + } + } + } + + let mut search_index: SearchIndex = SearchIndex::default(); + + pkg_map + .iter() + .for_each(|(key, value)| search_index.insert(key, value)); + + let ty = Ok(()).and_then(|_| -> io::Result<()> { + let pkg_map_cache = + bincode::serialize(&pkg_map).map_err(|e| io::Error::new(ErrorKind::Other, e))?; + fs::create_dir_all("/var/cache/oma/")?; + fs::write("/var/cache/oma/search_index_cache", &pkg_map_cache)?; + Ok(()) + }); + + if let Err(e) = ty { + warn!("Failed to write search index to cache: {e}"); + } + + Ok(IndiciumSearch { + cache, + pkg_map, + index: search_index, + }) +} + pub struct StrSimSearch<'a> { cache: &'a Cache, } @@ -395,7 +423,10 @@ impl OmaSearch for StrSimSearch<'_> { .ok_or_else(|| OmaSearchError::FailedGetCandidate(pkg.fullname(true)))?; let name = pkg.fullname(true); + + #[cfg(feature = "aosc")] let is_base = name.ends_with("-base"); + let full_match = query == name; v.push(SearchResult { @@ -420,6 +451,7 @@ impl OmaSearch for StrSimSearch<'_> { } else { PackageStatus::Avail }, + #[cfg(feature = "aosc")] is_base, }); } @@ -475,7 +507,10 @@ impl OmaSearch for TextSearch<'_> { && !name.ends_with("-dbg") { let full_match = query == name; + + #[cfg(feature = "aosc")] let is_base = name.ends_with("-base"); + let upgrade = pkg.is_upgradable(); let installed = pkg.is_installed(); if let Some(cand) = cand { @@ -501,6 +536,7 @@ impl OmaSearch for TextSearch<'_> { } else { PackageStatus::Avail }, + #[cfg(feature = "aosc")] is_base, }) } diff --git a/src/args.rs b/src/args.rs index a1c174408..867ca4c2b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -12,6 +12,7 @@ use crate::{ download::Download, error::OutputError, fix_broken::FixBroken, + gencache::GenCache, generate::Generate, history::{History, Undo}, install::Install, @@ -123,6 +124,9 @@ pub enum SubCmd { #[command(hide = true)] /// Generate shell completions and manpages Generate(Generate), + #[command(hide = true)] + /// Generate search index cache + Gencache(GenCache), } #[derive(Debug, Args)] diff --git a/src/subcommand/gencache.rs b/src/subcommand/gencache.rs new file mode 100644 index 000000000..20088bfb9 --- /dev/null +++ b/src/subcommand/gencache.rs @@ -0,0 +1,23 @@ +use clap::Args; +use oma_pm::{ + apt::{AptConfig, OmaApt, OmaAptArgs}, + search::make_search_cache, +}; + +use crate::{args::CliExecuter, config::Config, error::OutputError}; + +#[derive(Debug, Args)] +pub struct GenCache; + +impl CliExecuter for GenCache { + fn execute(self, _config: &Config, _no_progress: bool) -> Result { + let apt = OmaApt::new( + vec![], + OmaAptArgs::builder().build(), + false, + AptConfig::new(), + )?; + make_search_cache(&apt.cache, |_| {})?; + Ok(0) + } +} diff --git a/src/subcommand/mod.rs b/src/subcommand/mod.rs index aa7d8bec7..b53122b13 100644 --- a/src/subcommand/mod.rs +++ b/src/subcommand/mod.rs @@ -4,6 +4,7 @@ pub mod contents_find; pub mod depends; pub mod download; pub mod fix_broken; +pub mod gencache; pub mod generate; pub mod history; pub mod install; diff --git a/src/utils.rs b/src/utils.rs index 500bd7869..30aad6f3d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -137,6 +137,7 @@ pub struct SearchResultDisplay<'a>(pub &'a SearchResult); impl Display for SearchResultDisplay<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let i = self.0; + #[cfg(feature = "aosc")] let mut pkg_info_line = if i.is_base { color_formatter() .color_str(&i.name, Action::Purple) @@ -149,6 +150,12 @@ impl Display for SearchResultDisplay<'_> { .to_string() }; + #[cfg(not(feature = "aosc"))] + let mut pkg_info_line = color_formatter() + .color_str(&i.name, Action::Emphasis) + .bold() + .to_string(); + pkg_info_line.push(' '); if i.status == PackageStatus::Upgrade {