use std::collections::HashSet;
use std::process::{Command, Stdio};
use crate::error::{ArchToolkitError, Result};
use crate::types::index::InstalledPackagesMode;
#[allow(clippy::implicit_hasher)]
pub fn refresh_explicit_cache(
mode: InstalledPackagesMode,
cache: Option<&mut HashSet<String>>,
) -> Result<HashSet<String>> {
let args: &[&str] = match mode {
InstalledPackagesMode::LeafOnly => &["-Qetq"], InstalledPackagesMode::AllExplicit => &["-Qeq"], };
tracing::debug!("Running: pacman {:?}", args);
let output = Command::new("pacman")
.args(args)
.env("LC_ALL", "C")
.env("LANG", "C")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output();
let packages = match output {
Ok(output) => {
if output.status.success() {
let text = String::from_utf8_lossy(&output.stdout);
let packages: HashSet<String> = text
.lines()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
tracing::debug!(
"Successfully retrieved {} explicit packages (mode: {:?})",
packages.len(),
mode
);
packages
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::error!(
"pacman {:?} failed with status {:?}: {}",
args,
output.status.code(),
stderr
);
HashSet::new()
}
}
Err(e) => {
tracing::error!("Failed to execute pacman {:?}: {}", args, e);
HashSet::new()
}
};
if let Some(cache_ref) = cache {
cache_ref.clone_from(&packages);
}
Ok(packages)
}
#[cfg(feature = "index")]
#[allow(clippy::implicit_hasher)]
pub async fn refresh_explicit_cache_async(
mode: InstalledPackagesMode,
cache: Option<&mut HashSet<String>>,
) -> Result<HashSet<String>> {
let result = tokio::task::spawn_blocking(move || refresh_explicit_cache(mode, None))
.await
.map_err(|e| ArchToolkitError::Parse(format!("Blocking task failed: {e}")))?;
if let (Ok(packages), Some(cache_ref)) = (result.as_ref(), cache) {
cache_ref.clone_from(packages);
}
result
}
#[must_use]
#[allow(clippy::implicit_hasher)]
pub fn is_explicit(
name: &str,
mode: InstalledPackagesMode,
cache: Option<&HashSet<String>>,
) -> bool {
if let Some(cache_ref) = cache {
return cache_ref.contains(name);
}
let args: &[&str] = match mode {
InstalledPackagesMode::LeafOnly => &["-Qet", name],
InstalledPackagesMode::AllExplicit => &["-Qe", name],
};
tracing::debug!("Running: pacman {:?}", args);
let output = Command::new("pacman")
.args(args)
.env("LC_ALL", "C")
.env("LANG", "C")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output();
match output {
Ok(output) => output.status.success(),
Err(e) => {
tracing::error!("Failed to execute pacman {:?}: {}", args, e);
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn refresh_explicit_cache_updates_cache() {
let mut cache = HashSet::new();
let _result = refresh_explicit_cache(InstalledPackagesMode::AllExplicit, Some(&mut cache));
}
#[test]
fn refresh_explicit_cache_without_cache() {
let result_leaf = refresh_explicit_cache(InstalledPackagesMode::LeafOnly, None);
assert!(result_leaf.is_ok());
let result_all = refresh_explicit_cache(InstalledPackagesMode::AllExplicit, None);
assert!(result_all.is_ok());
}
#[test]
fn is_explicit_uses_cache() {
let cache = HashSet::from(["vim".to_string(), "git".to_string()]);
assert!(is_explicit(
"vim",
InstalledPackagesMode::AllExplicit,
Some(&cache)
));
assert!(is_explicit(
"git",
InstalledPackagesMode::LeafOnly,
Some(&cache)
));
assert!(!is_explicit(
"nonexistent",
InstalledPackagesMode::AllExplicit,
Some(&cache)
));
}
#[test]
fn is_explicit_without_cache() {
let _result_leaf = is_explicit("vim", InstalledPackagesMode::LeafOnly, None);
let _result_all = is_explicit("vim", InstalledPackagesMode::AllExplicit, None);
}
#[cfg(feature = "index")]
#[tokio::test]
async fn refresh_explicit_cache_async_works() {
let mut cache = HashSet::new();
let result =
refresh_explicit_cache_async(InstalledPackagesMode::AllExplicit, Some(&mut cache))
.await;
assert!(result.is_ok());
}
}