mirror of
https://github.com/Alexander-D-Karpov/netfetch.git
synced 2026-03-16 22:07:03 +03:00
526 lines
10 KiB
Go
526 lines
10 KiB
Go
package collector
|
|
|
|
import (
|
|
"bufio"
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
type PackageCount struct {
|
|
Manager string
|
|
Count int
|
|
}
|
|
|
|
func (c *Collector) collectPackages() {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if !c.activeModules["packages"] {
|
|
c.info.Packages = "Unknown"
|
|
return
|
|
}
|
|
|
|
managers := detectPackageManagers()
|
|
counts := make(chan PackageCount, len(managers))
|
|
var wg sync.WaitGroup
|
|
|
|
for _, manager := range managers {
|
|
wg.Add(1)
|
|
go func(m string) {
|
|
defer wg.Done()
|
|
count := countPackages(m)
|
|
if count > 0 {
|
|
counts <- PackageCount{Manager: m, Count: count}
|
|
}
|
|
}(manager)
|
|
}
|
|
|
|
go func() {
|
|
wg.Wait()
|
|
close(counts)
|
|
}()
|
|
|
|
totalPackages := 0
|
|
var details []string
|
|
|
|
for count := range counts {
|
|
totalPackages += count.Count
|
|
details = append(details, fmt.Sprintf("%d (%s)", count.Count, count.Manager))
|
|
}
|
|
|
|
if totalPackages > 0 {
|
|
if len(details) == 1 {
|
|
c.info.Packages = details[0]
|
|
} else {
|
|
c.info.Packages = strings.Join(details, ", ")
|
|
}
|
|
} else {
|
|
c.info.Packages = "Unknown"
|
|
}
|
|
}
|
|
|
|
func detectPackageManagers() []string {
|
|
var managers []string
|
|
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
if pathExists("/var/lib/dpkg/status") {
|
|
managers = append(managers, "dpkg")
|
|
}
|
|
|
|
if pathExists("/var/lib/pacman/local") {
|
|
managers = append(managers, "pacman")
|
|
}
|
|
|
|
if pathExists("/var/lib/rpm/rpmdb.sqlite") {
|
|
managers = append(managers, "rpm-sqlite")
|
|
} else if pathExists("/var/lib/rpm/Packages") || pathExists("/var/lib/rpm/Packages.db") {
|
|
managers = append(managers, "rpm")
|
|
}
|
|
|
|
if pathExists("/var/db/pkg") && !pathExists("/var/lib/pacman") {
|
|
managers = append(managers, "emerge")
|
|
}
|
|
|
|
if pathExists("/lib/apk/db/installed") {
|
|
managers = append(managers, "apk")
|
|
}
|
|
|
|
if pathExists("/var/db/xbps") {
|
|
managers = append(managers, "xbps")
|
|
}
|
|
|
|
if isNixOS() || pathExists("/nix/var/nix/profiles") {
|
|
managers = append(managers, "nix")
|
|
}
|
|
|
|
if pathExists("/var/lib/flatpak/app") || pathExists(filepath.Join(os.Getenv("HOME"), ".local/share/flatpak/app")) {
|
|
managers = append(managers, "flatpak")
|
|
}
|
|
|
|
if pathExists("/snap") {
|
|
managers = append(managers, "snap")
|
|
}
|
|
|
|
if pathExists("/var/cache/apk") {
|
|
managers = append(managers, "apk")
|
|
}
|
|
|
|
case "darwin":
|
|
if pathExists("/usr/local/Cellar") || pathExists("/opt/homebrew/Cellar") {
|
|
managers = append(managers, "brew")
|
|
}
|
|
if pathExists("/opt/homebrew/Caskroom") || pathExists("/usr/local/Caskroom") {
|
|
managers = append(managers, "brew-cask")
|
|
}
|
|
if pathExists("/opt/local/var/macports") {
|
|
managers = append(managers, "macports")
|
|
}
|
|
if pathExists("/nix/var/nix/profiles") {
|
|
managers = append(managers, "nix")
|
|
}
|
|
|
|
case "freebsd":
|
|
if pathExists("/var/db/pkg/local.sqlite") {
|
|
managers = append(managers, "pkg-freebsd")
|
|
}
|
|
|
|
case "openbsd", "netbsd":
|
|
if pathExists("/var/db/pkg") {
|
|
managers = append(managers, "pkg-bsd")
|
|
}
|
|
}
|
|
|
|
return managers
|
|
}
|
|
|
|
func isNixOS() bool {
|
|
if data, err := os.ReadFile("/etc/os-release"); err == nil {
|
|
return strings.Contains(string(data), "ID=nixos")
|
|
}
|
|
return false
|
|
}
|
|
|
|
func countPackages(manager string) int {
|
|
switch manager {
|
|
case "dpkg":
|
|
return countDpkgPackages()
|
|
case "pacman":
|
|
return countPacmanPackages()
|
|
case "rpm-sqlite":
|
|
return countRPMSqlitePackages()
|
|
case "rpm":
|
|
return countRPMPackages()
|
|
case "emerge":
|
|
return countEmergePackages()
|
|
case "apk":
|
|
return countApkPackages()
|
|
case "xbps":
|
|
return countXbpsPackages()
|
|
case "nix":
|
|
return countNixPackages()
|
|
case "flatpak":
|
|
return countFlatpakPackages()
|
|
case "snap":
|
|
return countSnapPackages()
|
|
case "brew":
|
|
return countBrewPackages()
|
|
case "brew-cask":
|
|
return countBrewCaskPackages()
|
|
case "macports":
|
|
return countMacportsPackages()
|
|
case "pkg-freebsd":
|
|
return countPkgFreeBSDPackages()
|
|
case "pkg-bsd":
|
|
return countPkgBSDPackages()
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func countDpkgPackages() int {
|
|
file, err := os.Open("/var/lib/dpkg/status")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
defer file.Close()
|
|
|
|
count := 0
|
|
scanner := bufio.NewScanner(file)
|
|
var currentStatus string
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
if strings.HasPrefix(line, "Status:") {
|
|
currentStatus = line
|
|
} else if line == "" {
|
|
if strings.Contains(currentStatus, "install ok installed") {
|
|
count++
|
|
}
|
|
currentStatus = ""
|
|
}
|
|
}
|
|
|
|
if strings.Contains(currentStatus, "install ok installed") {
|
|
count++
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countPacmanPackages() int {
|
|
entries, err := os.ReadDir("/var/lib/pacman/local")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
count := 0
|
|
for _, entry := range entries {
|
|
if entry.IsDir() && entry.Name() != "ALPM_DB_VERSION" {
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countRPMSqlitePackages() int {
|
|
db, err := sql.Open("sqlite3", "/var/lib/rpm/rpmdb.sqlite?mode=ro")
|
|
if err != nil {
|
|
return countRPMPackages()
|
|
}
|
|
defer db.Close()
|
|
|
|
var count int
|
|
err = db.QueryRow("SELECT COUNT(*) FROM Packages WHERE NOT (name LIKE 'gpg-pubkey%')").Scan(&count)
|
|
if err != nil {
|
|
return countRPMPackages()
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countRPMPackages() int {
|
|
out, err := exec.Command("rpm", "-qa", "--qf", "x").Output()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return len(string(out))
|
|
}
|
|
|
|
func countEmergePackages() int {
|
|
count := 0
|
|
baseDir := "/var/db/pkg"
|
|
|
|
categories, err := os.ReadDir(baseDir)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
for _, category := range categories {
|
|
if !category.IsDir() || strings.HasPrefix(category.Name(), ".") {
|
|
continue
|
|
}
|
|
|
|
categoryPath := filepath.Join(baseDir, category.Name())
|
|
packages, err := os.ReadDir(categoryPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, pkg := range packages {
|
|
if pkg.IsDir() && !strings.HasPrefix(pkg.Name(), ".") {
|
|
sizePath := filepath.Join(categoryPath, pkg.Name(), "SIZE")
|
|
if _, err := os.Stat(sizePath); err == nil {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countApkPackages() int {
|
|
file, err := os.Open("/lib/apk/db/installed")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
defer file.Close()
|
|
|
|
count := 0
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.HasPrefix(line, "C:Q") {
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countXbpsPackages() int {
|
|
matches, err := filepath.Glob("/var/db/xbps/pkgdb-*.plist")
|
|
if err != nil || len(matches) == 0 {
|
|
return 0
|
|
}
|
|
|
|
data, err := os.ReadFile(matches[0])
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
return strings.Count(string(data), "<string>installed</string>")
|
|
}
|
|
|
|
func countNixPackages() int {
|
|
count := 0
|
|
|
|
profilePaths := []string{
|
|
"/run/current-system/sw",
|
|
filepath.Join(os.Getenv("HOME"), ".nix-profile"),
|
|
"/etc/profiles/per-user/" + os.Getenv("USER"),
|
|
"/nix/var/nix/profiles/default",
|
|
}
|
|
|
|
for _, profilePath := range profilePaths {
|
|
manifestJson := filepath.Join(profilePath, "manifest.json")
|
|
if data, err := os.ReadFile(manifestJson); err == nil {
|
|
count += strings.Count(string(data), `"name":`)
|
|
continue
|
|
}
|
|
|
|
manifestNix := filepath.Join(profilePath, "manifest.nix")
|
|
if data, err := os.ReadFile(manifestNix); err == nil {
|
|
count += strings.Count(string(data), "name = ")
|
|
continue
|
|
}
|
|
}
|
|
|
|
if count == 0 {
|
|
out, err := exec.Command("nix-store", "-qR", "/run/current-system/sw").Output()
|
|
if err == nil {
|
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
|
for _, line := range lines {
|
|
if line != "" {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countFlatpakPackages() int {
|
|
count := 0
|
|
|
|
systemPath := "/var/lib/flatpak/app"
|
|
if entries, err := os.ReadDir(systemPath); err == nil {
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
appPath := filepath.Join(systemPath, entry.Name())
|
|
if hasCurrentSymlink(appPath) {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
homeDir := os.Getenv("HOME")
|
|
if homeDir != "" {
|
|
userPath := filepath.Join(homeDir, ".local/share/flatpak/app")
|
|
if entries, err := os.ReadDir(userPath); err == nil {
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
appPath := filepath.Join(userPath, entry.Name())
|
|
if hasCurrentSymlink(appPath) {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func hasCurrentSymlink(appPath string) bool {
|
|
arches, err := os.ReadDir(appPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, arch := range arches {
|
|
if arch.IsDir() {
|
|
branchPath := filepath.Join(appPath, arch.Name())
|
|
branches, err := os.ReadDir(branchPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, branch := range branches {
|
|
if branch.IsDir() {
|
|
currentPath := filepath.Join(branchPath, branch.Name(), "active")
|
|
if _, err := os.Lstat(currentPath); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func countSnapPackages() int {
|
|
entries, err := os.ReadDir("/snap")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
count := 0
|
|
skipDirs := map[string]bool{"bin": true, "README": true}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() && !skipDirs[entry.Name()] && !strings.HasPrefix(entry.Name(), ".") {
|
|
snapPath := filepath.Join("/snap", entry.Name(), "current")
|
|
if _, err := os.Lstat(snapPath); err == nil {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countBrewPackages() int {
|
|
cellarPaths := []string{
|
|
"/opt/homebrew/Cellar",
|
|
"/usr/local/Cellar",
|
|
}
|
|
|
|
for _, cellarPath := range cellarPaths {
|
|
if entries, err := os.ReadDir(cellarPath); err == nil {
|
|
return len(entries)
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func countBrewCaskPackages() int {
|
|
caskPaths := []string{
|
|
"/opt/homebrew/Caskroom",
|
|
"/usr/local/Caskroom",
|
|
}
|
|
|
|
for _, caskPath := range caskPaths {
|
|
if entries, err := os.ReadDir(caskPath); err == nil {
|
|
return len(entries)
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func countMacportsPackages() int {
|
|
out, err := exec.Command("port", "installed").Output()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
lines := strings.Split(string(out), "\n")
|
|
count := 0
|
|
for _, line := range lines {
|
|
if strings.Contains(line, "(active)") {
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countPkgFreeBSDPackages() int {
|
|
db, err := sql.Open("sqlite3", "/var/db/pkg/local.sqlite?mode=ro")
|
|
if err != nil {
|
|
return countPkgBSDPackages()
|
|
}
|
|
defer db.Close()
|
|
|
|
var count int
|
|
err = db.QueryRow("SELECT COUNT(*) FROM packages").Scan(&count)
|
|
if err != nil {
|
|
return countPkgBSDPackages()
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func countPkgBSDPackages() int {
|
|
entries, err := os.ReadDir("/var/db/pkg")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
count := 0
|
|
for _, entry := range entries {
|
|
if entry.IsDir() && !strings.HasPrefix(entry.Name(), ".") {
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|