retch_sysinfo/
packages.rs1#[cfg(any(not(any(target_os = "macos", target_os = "windows")), test))]
20fn rpm_db_uri(path: &str) -> String {
21 format!("file:{path}?immutable=1")
22}
23
24pub(crate) fn detect_packages() -> Option<usize> {
25 #[cfg(target_os = "macos")]
26 {
27 let mut count = 0;
28
29 for cellar_path in &["/opt/homebrew/Cellar", "/usr/local/Cellar"] {
30 if let Ok(entries) = std::fs::read_dir(cellar_path) {
31 count += entries.filter_map(|e| e.ok()).count();
32 }
33 }
34
35 for cask_path in &["/opt/homebrew/Caskroom", "/usr/local/Caskroom"] {
36 if let Ok(entries) = std::fs::read_dir(cask_path) {
37 count += entries.filter_map(|e| e.ok()).count();
38 }
39 }
40
41 if let Ok(entries) = std::fs::read_dir("/opt/local/var/macports/software") {
42 count += entries.filter_map(|e| e.ok()).count();
43 }
44
45 if count > 0 {
46 Some(count)
47 } else {
48 None
49 }
50 }
51
52 #[cfg(target_os = "windows")]
53 {
54 let mut count = 0;
55
56 if let Some(home) = dirs::home_dir() {
57 let scoop_dir = std::env::var("SCOOP")
58 .map(std::path::PathBuf::from)
59 .unwrap_or_else(|_| home.join("scoop"));
60 if let Ok(entries) = std::fs::read_dir(scoop_dir.join("apps")) {
61 count += entries.filter_map(|e| e.ok()).count();
62 }
63 }
64
65 let choco_install = std::env::var("ChocolateyInstall")
66 .unwrap_or_else(|_| "C:\\ProgramData\\chocolatey".to_string());
67 if let Ok(entries) = std::fs::read_dir(std::path::Path::new(&choco_install).join("lib")) {
68 count += entries.filter_map(|e| e.ok()).count();
69 }
70
71 if count > 0 {
72 Some(count)
73 } else {
74 None
75 }
76 }
77
78 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
79 {
80 if let Ok(entries) = std::fs::read_dir("/var/lib/pacman/local") {
81 let count = entries.filter_map(|e| e.ok()).count();
82 if count > 0 {
83 return Some(count);
84 }
85 }
86
87 if let Ok(entries) = std::fs::read_dir("/var/lib/dpkg/info") {
88 let count = entries
89 .filter_map(|e| e.ok())
90 .filter(|e| e.path().extension().is_some_and(|ext| ext == "list"))
91 .count();
92 if count > 0 {
93 return Some(count);
94 }
95 }
96
97 if let Ok(entries) = std::fs::read_dir("/var/db/pkg") {
98 let count: usize = entries
99 .filter_map(|e| e.ok())
100 .map(|e| {
101 std::fs::read_dir(e.path())
102 .map(|d| d.filter(|_| true).count())
103 .unwrap_or(0)
104 })
105 .sum();
106 if count > 0 {
107 return Some(count);
108 }
109 }
110
111 if let Ok(entries) = std::fs::read_dir("/var/db/xbps") {
112 let count = entries
113 .filter_map(|e| e.ok())
114 .filter(|e| e.path().extension().is_some_and(|ext| ext == "plist"))
115 .count();
116 if count > 0 {
117 return Some(count);
118 }
119 }
120
121 let rpm_db = "/var/lib/rpm/rpmdb.sqlite";
122 if std::path::Path::new(rpm_db).exists() {
123 use rusqlite::OpenFlags;
124 let flags = OpenFlags::SQLITE_OPEN_READ_ONLY
125 | OpenFlags::SQLITE_OPEN_URI
126 | OpenFlags::SQLITE_OPEN_NO_MUTEX;
127 match rusqlite::Connection::open_with_flags(rpm_db_uri(rpm_db), flags) {
128 Ok(conn) => {
129 match conn.query_row("SELECT COUNT(*) FROM Packages", [], |row| {
130 row.get::<_, i64>(0)
131 }) {
132 Ok(count) if count > 0 => return Some(count as usize),
133 Ok(_) => {}
134 Err(e) => {
138 eprintln!("warning: failed to query RPM database at {rpm_db}: {e}");
139 }
140 }
141 }
142 Err(e) => {
143 eprintln!("warning: failed to open RPM database at {rpm_db}: {e}");
144 }
145 }
146 }
147
148 None
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_rpm_db_uri_requests_immutable() {
158 assert_eq!(
161 rpm_db_uri("/var/lib/rpm/rpmdb.sqlite"),
162 "file:/var/lib/rpm/rpmdb.sqlite?immutable=1"
163 );
164 }
165
166 #[test]
167 fn test_rpm_db_uri_is_a_file_uri() {
168 let uri = rpm_db_uri("/tmp/some.sqlite");
171 assert!(uri.starts_with("file:"));
172 assert!(uri.contains("?immutable=1"));
173 }
174}