use std::collections::HashSet;
use super::explicit_lock;
use crate::state::InstalledPackagesMode;
pub async fn refresh_explicit_cache(mode: InstalledPackagesMode) {
let args: &[&str] = match mode {
InstalledPackagesMode::LeafOnly => &["-Qetq"], InstalledPackagesMode::AllExplicit => &["-Qeq"], };
if let Ok(Ok(body)) =
tokio::task::spawn_blocking(move || crate::util::pacman::run_pacman(args)).await
{
let set: HashSet<String> = body.lines().map(|s| s.trim().to_string()).collect();
if let Ok(mut g) = explicit_lock().write() {
*g = set;
}
}
}
#[must_use]
pub fn explicit_names() -> HashSet<String> {
explicit_lock()
.read()
.map(|s| s.clone())
.unwrap_or_default()
}
#[must_use]
pub fn query_explicit_packages_sync(mode: InstalledPackagesMode) -> Vec<String> {
let args: &[&str] = match mode {
InstalledPackagesMode::LeafOnly => &["-Qetq"], InstalledPackagesMode::AllExplicit => &["-Qeq"], };
match crate::util::pacman::run_pacman(args) {
Ok(body) => {
let mut names: Vec<String> = body
.lines()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
names.sort();
names
}
Err(e) => {
tracing::warn!(
mode = ?mode,
error = %e,
"Failed to query explicit packages from pacman"
);
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn explicit_names_returns_empty_when_uninitialized() {
let _guard = crate::global_test_mutex_lock();
if let Ok(mut g) = super::explicit_lock().write() {
g.clear();
}
let set = super::explicit_names();
assert!(set.is_empty());
}
#[test]
fn explicit_names_returns_cloned_set() {
let _guard = crate::global_test_mutex_lock();
if let Ok(mut g) = super::explicit_lock().write() {
g.clear();
g.insert("a".to_string());
g.insert("b".to_string());
}
let mut set = super::explicit_names();
assert_eq!(set.len(), 2);
let mut v: Vec<String> = set.drain().collect();
v.sort();
assert_eq!(v, vec!["a", "b"]);
}
#[cfg(not(target_os = "windows"))]
#[allow(clippy::await_holding_lock)]
#[tokio::test]
async fn refresh_explicit_cache_populates_cache_from_pacman_output() {
struct PathGuard {
original: String,
}
impl Drop for PathGuard {
fn drop(&mut self) {
unsafe {
std::env::set_var("PATH", &self.original);
}
}
}
let _guard = crate::global_test_mutex_lock();
if let Ok(mut g) = super::explicit_lock().write() {
g.clear();
}
let old_path = std::env::var("PATH").unwrap_or_default();
let _path_guard = PathGuard {
original: old_path.clone(),
};
let mut root = std::env::temp_dir();
root.push(format!(
"pacsea_fake_pacman_qetq_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("System time is before UNIX epoch")
.as_nanos()
));
std::fs::create_dir_all(&root).expect("failed to create test root directory");
let mut bin = root.clone();
bin.push("bin");
std::fs::create_dir_all(&bin).expect("failed to create test bin directory");
let mut script = bin.clone();
script.push("pacman");
let body = r#"#!/usr/bin/env bash
set -e
if [[ "$1" == "-Qetq" ]]; then
echo "alpha"
echo "beta"
exit 0
fi
exit 1
"#;
std::fs::write(&script, body).expect("failed to write test pacman script");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perm = std::fs::metadata(&script)
.expect("failed to read test pacman script metadata")
.permissions();
perm.set_mode(0o755);
std::fs::set_permissions(&script, perm)
.expect("failed to set test pacman script permissions");
}
let new_path = format!("{}:{old_path}", bin.to_string_lossy());
unsafe {
std::env::set_var("PATH", &new_path);
}
super::refresh_explicit_cache(crate::state::InstalledPackagesMode::LeafOnly).await;
let _ = std::fs::remove_dir_all(&root);
let set = super::explicit_names();
assert_eq!(set.len(), 2);
assert!(set.contains("alpha"));
assert!(set.contains("beta"));
}
#[cfg(not(target_os = "windows"))]
#[allow(clippy::await_holding_lock)]
#[tokio::test]
async fn refresh_explicit_cache_populates_cache_with_all_explicit_mode() {
struct PathGuard {
original: String,
}
impl Drop for PathGuard {
fn drop(&mut self) {
unsafe {
std::env::set_var("PATH", &self.original);
}
}
}
let _guard = crate::global_test_mutex_lock();
if let Ok(mut g) = super::explicit_lock().write() {
g.clear();
}
let old_path = std::env::var("PATH").unwrap_or_default();
let _path_guard = PathGuard {
original: old_path.clone(),
};
let mut root = std::env::temp_dir();
root.push(format!(
"pacsea_fake_pacman_qeq_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("System time is before UNIX epoch")
.as_nanos()
));
std::fs::create_dir_all(&root).expect("failed to create test root directory");
let mut bin = root.clone();
bin.push("bin");
std::fs::create_dir_all(&bin).expect("failed to create test bin directory");
let mut script = bin.clone();
script.push("pacman");
let body = r#"#!/usr/bin/env bash
set -e
if [[ "$1" == "-Qeq" ]]; then
echo "git"
echo "python"
echo "wget"
exit 0
fi
exit 1
"#;
std::fs::write(&script, body).expect("failed to write test pacman script");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perm = std::fs::metadata(&script)
.expect("failed to read test pacman script metadata")
.permissions();
perm.set_mode(0o755);
std::fs::set_permissions(&script, perm)
.expect("failed to set test pacman script permissions");
}
let new_path = format!("{}:{old_path}", bin.to_string_lossy());
unsafe {
std::env::set_var("PATH", &new_path);
}
super::refresh_explicit_cache(crate::state::InstalledPackagesMode::AllExplicit).await;
let _ = std::fs::remove_dir_all(&root);
let set = super::explicit_names();
assert_eq!(set.len(), 3);
assert!(set.contains("git"));
assert!(set.contains("python"));
assert!(set.contains("wget"));
}
}