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