use std::fs;
use std::path::Path;
use crate::modules::enums::PackageShorthand;
pub fn get_packages(shorthand: PackageShorthand) -> Option<String> {
let mut packages = 0u64;
let mut managers = vec![];
let mut manager_string = vec![];
if let Some(count) = count_dpkg_packages() {
packages += count;
managers.push(format!("{} ({})", count, "dpkg"));
manager_string.push("dpkg");
}
if let Some(count) = count_pacman_packages() {
packages += count;
managers.push(format!("{} ({})", count, "pacman"));
manager_string.push("pacman");
}
if let Some(count) = count_rpm_packages() {
packages += count;
managers.push(format!("{} ({})", count, "rpm"));
manager_string.push("rpm");
}
if let Some(count) = count_flatpak_packages() {
packages += count;
managers.push(format!("{} ({})", count, "flatpak"));
manager_string.push("flatpak");
}
if is_snapd_running() {
if let Some(count) = count_snap_packages() {
packages += count;
managers.push(format!("{} ({})", count, "snap"));
manager_string.push("snap");
}
}
if packages == 0 {
return None;
}
match shorthand {
PackageShorthand::Off => Some(format!("{} total", packages)),
PackageShorthand::On => Some(managers.join(", ")),
PackageShorthand::Tiny => Some(format!("{} ({})", packages, manager_string.join(", "))),
}
}
fn is_snapd_running() -> bool {
Path::new("/run/snapd.socket").exists() || Path::new("/var/run/snapd.socket").exists()
}
fn count_dpkg_packages() -> Option<u64> {
let status = fs::read_to_string("/var/lib/dpkg/status").ok()?;
let count = status
.lines()
.filter(|line| line.starts_with("Package: "))
.count() as u64;
Some(count)
}
fn count_pacman_packages() -> Option<u64> {
let entries = fs::read_dir("/var/lib/pacman/local").ok()?;
let count = entries
.filter_map(|entry| entry.ok())
.filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
.count() as u64;
Some(count)
}
fn count_rpm_packages() -> Option<u64> {
if let Ok(db_path) = fs::read_dir("/var/lib/rpm") {
for entry in db_path.flatten() {
let name = entry.file_name();
if name.to_string_lossy().starts_with("Packages") {
if let Ok(content) = fs::read_to_string(entry.path()) {
return Some(content.lines().filter(|l| !l.is_empty()).count() as u64);
}
}
}
}
if let Ok(entries) = fs::read_dir("/var/cache/apt") {
let count = entries.filter_map(|e| e.ok()).count() as u64;
if count > 0 {
return Some(count);
}
}
None
}
fn count_flatpak_packages() -> Option<u64> {
let paths = ["/var/lib/flatpak/app", "/home/.local/share/flatpak/app"];
for path in &paths {
if let Ok(entries) = fs::read_dir(path) {
let count = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.count() as u64;
if count > 0 {
return Some(count);
}
}
}
if let Ok(home) = std::env::var("HOME") {
let user_path = format!("{}/.local/share/flatpak/app", home);
if let Ok(entries) = fs::read_dir(&user_path) {
let count = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.count() as u64;
if count > 0 {
return Some(count);
}
}
}
None
}
fn count_snap_packages() -> Option<u64> {
let snap_data_path = "/var/lib/snapd/state.json";
if let Ok(content) = fs::read_to_string(snap_data_path) {
let count = content.matches("\"name\":").count() as u64;
if count > 0 {
return Some(count.saturating_sub(1)); }
}
if let Ok(entries) = fs::read_dir("/snap") {
let count = entries
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir() && e.file_name() != "snap")
.count() as u64;
if count > 0 {
return Some(count);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::EnvLock;
#[test]
fn returns_none_when_no_managers_found() {
let env_lock = EnvLock::acquire(&["PATH"]);
env_lock.set_var("PATH", "/nonexistent");
let result = get_packages(PackageShorthand::Off);
assert!(result.is_none());
drop(env_lock);
}
}