Skip to main content

retch_sysinfo/
packages.rs

1// SPDX-FileCopyrightText: 2026 Ken Tobias
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! Installed package count detection.
5//!
6//! Supports Pacman (Arch), Dpkg (Debian), XBPS (Void), RPM (Fedora/RHEL) on Linux,
7//! Homebrew (Formulae and Casks) and MacPorts on macOS, and Scoop/Chocolatey on Windows.
8
9pub(crate) fn detect_packages() -> Option<usize> {
10    #[cfg(target_os = "macos")]
11    {
12        let mut count = 0;
13
14        for cellar_path in &["/opt/homebrew/Cellar", "/usr/local/Cellar"] {
15            if let Ok(entries) = std::fs::read_dir(cellar_path) {
16                count += entries.filter_map(|e| e.ok()).count();
17            }
18        }
19
20        for cask_path in &["/opt/homebrew/Caskroom", "/usr/local/Caskroom"] {
21            if let Ok(entries) = std::fs::read_dir(cask_path) {
22                count += entries.filter_map(|e| e.ok()).count();
23            }
24        }
25
26        if let Ok(entries) = std::fs::read_dir("/opt/local/var/macports/software") {
27            count += entries.filter_map(|e| e.ok()).count();
28        }
29
30        if count > 0 {
31            Some(count)
32        } else {
33            None
34        }
35    }
36
37    #[cfg(target_os = "windows")]
38    {
39        let mut count = 0;
40
41        if let Some(home) = dirs::home_dir() {
42            let scoop_dir = std::env::var("SCOOP")
43                .map(std::path::PathBuf::from)
44                .unwrap_or_else(|_| home.join("scoop"));
45            if let Ok(entries) = std::fs::read_dir(scoop_dir.join("apps")) {
46                count += entries.filter_map(|e| e.ok()).count();
47            }
48        }
49
50        let choco_install = std::env::var("ChocolateyInstall")
51            .unwrap_or_else(|_| "C:\\ProgramData\\chocolatey".to_string());
52        if let Ok(entries) = std::fs::read_dir(std::path::Path::new(&choco_install).join("lib")) {
53            count += entries.filter_map(|e| e.ok()).count();
54        }
55
56        if count > 0 {
57            Some(count)
58        } else {
59            None
60        }
61    }
62
63    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
64    {
65        if let Ok(entries) = std::fs::read_dir("/var/lib/pacman/local") {
66            let count = entries.filter_map(|e| e.ok()).count();
67            if count > 0 {
68                return Some(count);
69            }
70        }
71
72        if let Ok(entries) = std::fs::read_dir("/var/lib/dpkg/info") {
73            let count = entries
74                .filter_map(|e| e.ok())
75                .filter(|e| e.path().extension().is_some_and(|ext| ext == "list"))
76                .count();
77            if count > 0 {
78                return Some(count);
79            }
80        }
81
82        if let Ok(entries) = std::fs::read_dir("/var/db/pkg") {
83            let count: usize = entries
84                .filter_map(|e| e.ok())
85                .map(|e| {
86                    std::fs::read_dir(e.path())
87                        .map(|d| d.filter(|_| true).count())
88                        .unwrap_or(0)
89                })
90                .sum();
91            if count > 0 {
92                return Some(count);
93            }
94        }
95
96        if let Ok(entries) = std::fs::read_dir("/var/db/xbps") {
97            let count = entries
98                .filter_map(|e| e.ok())
99                .filter(|e| e.path().extension().is_some_and(|ext| ext == "plist"))
100                .count();
101            if count > 0 {
102                return Some(count);
103            }
104        }
105
106        let rpm_db = "/var/lib/rpm/rpmdb.sqlite";
107        if std::path::Path::new(rpm_db).exists() {
108            match rusqlite::Connection::open(rpm_db) {
109                Ok(conn) => {
110                    if let Ok(count) = conn.query_row("SELECT COUNT(*) FROM Packages", [], |row| {
111                        row.get::<_, i64>(0)
112                    }) {
113                        if count > 0 {
114                            return Some(count as usize);
115                        }
116                    }
117                }
118                Err(e) => {
119                    eprintln!("warning: failed to open RPM database at {}: {}", rpm_db, e);
120                }
121            }
122        }
123
124        None
125    }
126}