leenfetch_core/modules/linux/
packages.rs1#![allow(
2 clippy::collapsible_if,
3 clippy::needless_borrows_for_generic_args
4)]
5
6use std::fs;
7use std::path::Path;
8
9use crate::modules::enums::PackageShorthand;
10
11pub fn get_packages(shorthand: PackageShorthand) -> Option<String> {
12 let mut packages = 0u64;
13 let mut managers = vec![];
14 let mut manager_string = vec![];
15
16 if let Some(count) = count_dpkg_packages() {
18 packages += count;
19 managers.push(format!("{} ({})", count, "dpkg"));
20 manager_string.push("dpkg");
21 }
22
23 if let Some(count) = count_pacman_packages() {
25 packages += count;
26 managers.push(format!("{} ({})", count, "pacman"));
27 manager_string.push("pacman");
28 }
29
30 if let Some(count) = count_rpm_packages() {
32 packages += count;
33 managers.push(format!("{} ({})", count, "rpm"));
34 manager_string.push("rpm");
35 }
36
37 if let Some(count) = count_flatpak_packages() {
39 packages += count;
40 managers.push(format!("{} ({})", count, "flatpak"));
41 manager_string.push("flatpak");
42 }
43
44 if is_snapd_running() {
46 if let Some(count) = count_snap_packages() {
47 packages += count;
48 managers.push(format!("{} ({})", count, "snap"));
49 manager_string.push("snap");
50 }
51 }
52
53 if packages == 0 {
54 return None;
55 }
56
57 match shorthand {
58 PackageShorthand::Off => Some(format!("{} total", packages)),
59 PackageShorthand::On => Some(managers.join(", ")),
60 PackageShorthand::Tiny => Some(format!("{} ({})", packages, manager_string.join(", "))),
61 }
62}
63
64fn is_snapd_running() -> bool {
65 Path::new("/run/snapd.socket").exists() || Path::new("/var/run/snapd.socket").exists()
67}
68
69fn pkg_root() -> String {
70 std::env::var("LEENFETCH_PKG_ROOT").unwrap_or_default()
71}
72
73fn count_dpkg_packages() -> Option<u64> {
74 let root = pkg_root();
75 let status = fs::read_to_string(format!("{root}/var/lib/dpkg/status")).ok()?;
76 let count = status
77 .lines()
78 .filter(|line| line.starts_with("Package: "))
79 .count() as u64;
80 Some(count)
81}
82
83fn count_pacman_packages() -> Option<u64> {
84 let root = pkg_root();
85 let entries = fs::read_dir(format!("{root}/var/lib/pacman/local")).ok()?;
86 let count = entries
87 .filter_map(|entry| entry.ok())
88 .filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
89 .count() as u64;
90 Some(count)
91}
92
93fn count_rpm_packages() -> Option<u64> {
94 let root = pkg_root();
95 if let Ok(db_path) = fs::read_dir(format!("{root}/var/lib/rpm")) {
97 for entry in db_path.flatten() {
99 let name = entry.file_name();
100 if name.to_string_lossy().starts_with("Packages") {
101 if let Ok(content) = fs::read_to_string(entry.path()) {
103 return Some(content.lines().filter(|l| !l.is_empty()).count() as u64);
104 }
105 }
106 }
107 }
108
109 if let Ok(entries) = fs::read_dir(format!("{root}/var/cache/apt")) {
111 let count = entries.filter_map(|e| e.ok()).count() as u64;
112 if count > 0 {
113 return Some(count);
114 }
115 }
116
117 None
118}
119
120fn count_flatpak_packages() -> Option<u64> {
121 let root = pkg_root();
122 let paths = [
124 format!("{root}/var/lib/flatpak/app"),
125 format!("{root}/home/.local/share/flatpak/app"),
126 ];
127
128 for path in &paths {
129 if let Ok(entries) = fs::read_dir(&path) {
130 let count = entries
131 .filter_map(|e| e.ok())
132 .filter(|e| e.path().is_dir())
133 .count() as u64;
134 if count > 0 {
135 return Some(count);
136 }
137 }
138 }
139
140 if let Ok(home) = std::env::var("HOME") {
142 let user_path = format!("{root}{home}/.local/share/flatpak/app");
143 if let Ok(entries) = fs::read_dir(&user_path) {
144 let count = entries
145 .filter_map(|e| e.ok())
146 .filter(|e| e.path().is_dir())
147 .count() as u64;
148 if count > 0 {
149 return Some(count);
150 }
151 }
152 }
153
154 None
155}
156
157fn count_snap_packages() -> Option<u64> {
158 let root = pkg_root();
159 let snap_data_path = format!("{root}/var/lib/snapd/state.json");
161
162 if let Ok(content) = fs::read_to_string(&snap_data_path) {
163 let count = content.matches("\"name\":").count() as u64;
166 if count > 0 {
167 return Some(count.saturating_sub(1)); }
169 }
170
171 if let Ok(entries) = fs::read_dir(format!("{root}/snap")) {
173 let count = entries
174 .filter_map(|e| e.ok())
175 .filter(|e| e.path().is_dir() && e.file_name() != "snap")
176 .count() as u64;
177 if count > 0 {
178 return Some(count);
179 }
180 }
181
182 None
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::test_utils::EnvLock;
189
190 #[test]
191 fn returns_none_when_no_managers_found() {
192 let env_lock = EnvLock::acquire(&["LEENFETCH_PKG_ROOT"]);
193 env_lock.set_var("LEENFETCH_PKG_ROOT", "/nonexistent");
194 let result = get_packages(PackageShorthand::Off);
195 assert!(result.is_none());
196 drop(env_lock);
197 }
198}