use super::pkgbuild_cache::{PkgbuildSourceKind, parse_pkgbuild_cached};
use super::pkgbuild_fetch::fetch_pkgbuild_sync;
use crate::state::types::Source;
use std::process::Command;
fn parse_file_list_from_output(output: &[u8]) -> Vec<String> {
let text = String::from_utf8_lossy(output);
text.lines()
.filter_map(|line| line.split_once(' ').map(|(_pkg, path)| path.to_string()))
.collect()
}
fn try_aur_helper_file_list(helper: &str, name: &str) -> Option<Vec<String>> {
tracing::debug!("Trying {} -Fl {} for AUR package file list", helper, name);
let output = Command::new(helper)
.args(["-Fl", name])
.env("LC_ALL", "C")
.env("LANG", "C")
.output()
.ok()?;
if !output.status.success() {
return None;
}
let files = parse_file_list_from_output(&output.stdout);
if files.is_empty() {
return None;
}
tracing::debug!(
"Found {} files from {} -Fl for {}",
files.len(),
helper,
name
);
Some(files)
}
fn get_aur_file_list(name: &str) -> Vec<String> {
if let Ok(installed_files) = get_installed_file_list(name)
&& !installed_files.is_empty()
{
tracing::debug!(
"Found {} files from installed AUR package {}",
installed_files.len(),
name
);
return installed_files;
}
let has_paru = Command::new("paru").args(["--version"]).output().is_ok();
let has_yay = Command::new("yay").args(["--version"]).output().is_ok();
if has_paru && let Some(files) = try_aur_helper_file_list("paru", name) {
return files;
}
if has_yay && let Some(files) = try_aur_helper_file_list("yay", name) {
return files;
}
if let Ok(pkgbuild) = fetch_pkgbuild_sync(name) {
let entry = parse_pkgbuild_cached(name, None, PkgbuildSourceKind::Aur, &pkgbuild);
let files = entry.install_paths;
if !files.is_empty() {
tracing::debug!(
"Found {} files from PKGBUILD parsing for {}",
files.len(),
name
);
return files;
}
} else {
tracing::debug!("Failed to fetch PKGBUILD for {}", name);
}
tracing::debug!(
"AUR package {}: file list not available (not installed, not cached, PKGBUILD parsing failed)",
name
);
Vec::new()
}
fn get_official_file_list(name: &str, repo: &str) -> Result<Vec<String>, String> {
tracing::debug!("Running: pacman -Fl {}", name);
let spec = if repo.is_empty() {
name.to_string()
} else {
format!("{repo}/{name}")
};
let output = Command::new("pacman")
.args(["-Fl", &spec])
.env("LC_ALL", "C")
.env("LANG", "C")
.output()
.map_err(|e| {
tracing::error!("Failed to execute pacman -Fl {}: {}", spec, e);
format!("pacman -Fl failed: {e}")
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("database file") && stderr.contains("does not exist") {
tracing::warn!(
"File database not synced for {} (pacman -Fy requires root). Skipping file list.",
name
);
return Ok(Vec::new()); }
tracing::error!(
"pacman -Fl {} failed with status {:?}: {}",
spec,
output.status.code(),
stderr
);
return Err(format!("pacman -Fl failed for {spec}: {stderr}"));
}
let files = parse_file_list_from_output(&output.stdout);
tracing::debug!("Found {} files in remote package {}", files.len(), name);
Ok(files)
}
pub fn get_remote_file_list(name: &str, source: &Source) -> Result<Vec<String>, String> {
match source {
Source::Official { repo, .. } => get_official_file_list(name, repo),
Source::Aur => Ok(get_aur_file_list(name)),
}
}
pub fn get_installed_file_list(name: &str) -> Result<Vec<String>, String> {
tracing::debug!("Running: pacman -Ql {}", name);
let output = Command::new("pacman")
.args(["-Ql", name])
.env("LC_ALL", "C")
.env("LANG", "C")
.output()
.map_err(|e| {
tracing::error!("Failed to execute pacman -Ql {}: {}", name, e);
format!("pacman -Ql failed: {e}")
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("was not found") {
tracing::debug!("Package {} is not installed", name);
return Ok(Vec::new());
}
tracing::error!(
"pacman -Ql {} failed with status {:?}: {}",
name,
output.status.code(),
stderr
);
return Err(format!("pacman -Ql failed for {name}: {stderr}"));
}
let files = parse_file_list_from_output(&output.stdout);
tracing::debug!("Found {} files in installed package {}", files.len(), name);
Ok(files)
}