diff --git a/Cargo.toml b/Cargo.toml index 62a5fcb9..aa32a03e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,22 +12,22 @@ build = "build.rs" [dependencies] cfg-if = "1.0.0" -libc = "0.2.112" -home = "0.5.3" +libc = { version = "0.2.112", optional = true } +home = { version = "0.5.3", optional = true } [target.'cfg(not(target_os = "windows"))'.dependencies] -if-addrs = "0.7.0" +if-addrs = { version = "0.7.0", optional = true } [target.'cfg(any(target_os="freebsd", target_os = "linux"))'.dependencies] -sqlite = "0.26.0" +sqlite = { version = "0.26.0", optional = true } [target.'cfg(any(target_os="freebsd", target_os = "netbsd"))'.dependencies] -x11rb = "0.9.0" +x11rb = { version = "0.9.0", optional = true } [target.'cfg(target_os = "linux")'.dependencies] -dirs = "4.0" -walkdir = "2.3.2" -os-release = "0.1" +dirs = { version = "4.0", optional = true } +walkdir = { version = "2.3.2", optional = true } +os-release = { version = "0.1", optional = true } [target.'cfg(target_os = "netbsd")'.dependencies] nix = "0.23.1" @@ -39,7 +39,7 @@ core-graphics = "0.22.3" mach = "0.3.2" [target.'cfg(target_family = "unix")'.dependencies] -num_cpus = "1.13.1" +num_cpus = { version = "1.13.1", optional = true } [target.'cfg(target_os = "windows")'.dependencies] local-ip-address = "0.4.4" @@ -54,10 +54,10 @@ windows = { version = "0.29.0", features = [ wmi = "0.9.2" [target.'cfg(any(target_os = "linux", target_os = "netbsd", target_os = "android"))'.dependencies] -itertools = "0.10.3" +itertools = { version = "0.10.3", optional = true } [target.'cfg(not(any(target_os = "netbsd", target_os = "windows")))'.dependencies] -sysctl = "0.4.3" +sysctl = { version = "0.4.3", optional = true } [target.'cfg(any(target_os = "linux", target_os = "netbsd"))'.build-dependencies] pkg-config = { version = "0.3.24", optional = true} @@ -69,5 +69,16 @@ default-features = false features = ["build","cargo","git","rustc"] [features] +default = ["general", "kernel", "memory", "network", "package", "product", "graphical", "processor"] +general = ["libc", "os-release", "x11rb", "dirs", "sysctl"] +kernel = ["libc", "sysctl"] +memory = [] +network = ["if-addrs"] +package = ["sqlite", "walkdir", "home"] +product = ["itertools"] +battery = [] +processor = ["num_cpus", "libc"] +graphical = ["libc", "dirs"] + openwrt = [] -hash = ["vergen"] +version = ["vergen", "pkg-config"] diff --git a/src/android/mod.rs b/src/android/mod.rs index 15df0ef7..3dcf765d 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -17,6 +17,7 @@ impl From for ReadoutError { ReadoutError::Other(e.to_string()) } } + impl From for ReadoutError { fn from(e: std::num::ParseFloatError) -> Self { ReadoutError::Other(e.to_string()) @@ -237,6 +238,7 @@ impl GeneralReadout for AndroidGeneralReadout { } } } + match (hardware, model, processor) { (Some(hardware), _, _) => Ok(hardware), (_, Some(model), _) => Ok(model), @@ -436,7 +438,7 @@ impl AndroidPackageReadout { }; let dpkg_dir = Path::new(&prefix).join("var/lib/dpkg/info"); - + extra::get_entries(&dpkg_dir).map(|entries| { entries .iter() diff --git a/src/enums.rs b/src/enums.rs new file mode 100644 index 00000000..6e18554f --- /dev/null +++ b/src/enums.rs @@ -0,0 +1,119 @@ +use std::fmt; + +/// This enum contains possible error types when doing sensor & variable readouts. +#[derive(Debug, Clone)] +pub enum ReadoutError { + /// A specific metric might not be available on all systems (e. g. battery percentage on a + /// desktop). \ + /// If you encounter this error, it means that the requested value is not available. + MetricNotAvailable, + + /// The default error for any readout that is not implemented by a particular platform. + NotImplemented, + + /// A readout for a metric might be available, but fails due to missing dependencies or other + /// unsatisfied requirements. + Other(String), + + /// Getting a readout on a specific operating system might not make sense or causes some other + /// kind of warning. This is not necessarily an error. + Warning(String), +} + +impl ToString for ReadoutError { + fn to_string(&self) -> String { + match self { + ReadoutError::MetricNotAvailable => { + String::from("Metric is not available on this system.") + } + ReadoutError::NotImplemented => { + String::from("This metric is not available on this platform or is not yet implemented by libmacchina.") + } + ReadoutError::Other(s) => s.clone(), + ReadoutError::Warning(s) => s.clone(), + } + } +} + +impl From<&ReadoutError> for ReadoutError { + fn from(r: &ReadoutError) -> Self { + r.to_owned() + } +} + +/// Holds the possible variants for battery status. +pub enum BatteryState { + Charging, + Discharging, +} + +impl From for &'static str { + fn from(state: BatteryState) -> &'static str { + match state { + BatteryState::Charging => "Charging", + BatteryState::Discharging => "Discharging", + } + } +} + +/// The currently running shell is a program, whose path +/// can be _relative_, or _absolute_. +#[derive(Debug)] +pub enum ShellFormat { + Relative, + Absolute, +} + +#[derive(Debug)] +/// There are two distinct kinds of shells, a so called *"current"* shell, i.e. the shell the user is currently using. +/// And a default shell, i.e. that the user sets for themselves using the `chsh` tool. +pub enum ShellKind { + Current, + Default, +} + +#[derive(Debug)] +/// The supported package managers whose packages can be extracted. +pub enum PackageManager { + Homebrew, + MacPorts, + Pacman, + Portage, + Dpkg, + Opkg, + Xbps, + Pkgsrc, + Apk, + Eopkg, + Rpm, + Cargo, + Flatpak, + Snap, + Android, + Pkg, + Scoop, +} + +impl fmt::Display for PackageManager { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + PackageManager::Homebrew => write!(f, "homebrew"), + PackageManager::MacPorts => write!(f, "macports"), + PackageManager::Pacman => write!(f, "pacman"), + PackageManager::Portage => write!(f, "portage"), + PackageManager::Dpkg => write!(f, "dpkg"), + PackageManager::Opkg => write!(f, "opkg"), + PackageManager::Xbps => write!(f, "xbps"), + PackageManager::Pkgsrc => write!(f, "pkgsrc"), + PackageManager::Apk => write!(f, "apk"), + PackageManager::Eopkg => write!(f, "eopkg"), + PackageManager::Rpm => write!(f, "rpm"), + PackageManager::Cargo => write!(f, "cargo"), + PackageManager::Flatpak => write!(f, "flatpak"), + PackageManager::Snap => write!(f, "snap"), + PackageManager::Android => write!(f, "android"), + PackageManager::Pkg => write!(f, "pkg"), + PackageManager::Scoop => write!(f, "scoop"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3741a700..af50f669 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,104 +5,197 @@ cfg_if! { mod extra; mod openwrt; + #[cfg(feature = "battery")] pub type BatteryReadout = openwrt::OpenWrtBatteryReadout; + #[cfg(feature = "kernel")] pub type KernelReadout = openwrt::OpenWrtKernelReadout; + #[cfg(feature = "memory")] pub type MemoryReadout = openwrt::OpenWrtMemoryReadout; + #[cfg(feature = "general")] pub type GeneralReadout = openwrt::OpenWrtGeneralReadout; + #[cfg(feature = "product")] pub type ProductReadout = openwrt::OpenWrtProductReadout; + #[cfg(feature = "package")] pub type PackageReadout = openwrt::OpenWrtPackageReadout; + #[cfg(feature = "network")] pub type NetworkReadout = openwrt::OpenWrtNetworkReadout; + #[cfg(feature = "graphical")] + pub type GraphicalReadout = openwrt::OpenWrtGraphicalReadout; + #[cfg(feature = "processor")] + pub type ProcessorReadout = openwrt::OpenWrtProcessorReadout; } else if #[cfg(all(target_os = "linux", not(feature = "openwrt")))] { + #[cfg(feature = "graphical")] + mod winman; mod extra; mod linux; - mod winman; - pub type BatteryReadout = linux::LinuxBatteryReadout; - pub type KernelReadout = linux::LinuxKernelReadout; - pub type MemoryReadout = linux::LinuxMemoryReadout; - pub type GeneralReadout = linux::LinuxGeneralReadout; - pub type ProductReadout = linux::LinuxProductReadout; - pub type PackageReadout = linux::LinuxPackageReadout; - pub type NetworkReadout = linux::LinuxNetworkReadout; + #[cfg(feature = "battery")] + pub type BatteryReadout = linux::battery::LinuxBatteryReadout; + #[cfg(feature = "kernel")] + pub type KernelReadout = linux::kernel::LinuxKernelReadout; + #[cfg(feature = "memory")] + pub type MemoryReadout = linux::memory::LinuxMemoryReadout; + #[cfg(feature = "general")] + pub type GeneralReadout = linux::general::LinuxGeneralReadout; + #[cfg(feature = "product")] + pub type ProductReadout = linux::product::LinuxProductReadout; + #[cfg(feature = "package")] + pub type PackageReadout = linux::package::LinuxPackageReadout; + #[cfg(feature = "network")] + pub type NetworkReadout = linux::network::LinuxNetworkReadout; + #[cfg(feature = "graphical")] + pub type GraphicalReadout = linux::graphical::LinuxGraphicalReadout; + #[cfg(feature = "processor")] + pub type ProcessorReadout = linux::processor::LinuxProcessorReadout; } else if #[cfg(target_os = "macos")] { mod extra; mod macos; + #[cfg(feature = "battery")] pub type BatteryReadout = macos::MacOSBatteryReadout; + #[cfg(feature = "kernel")] pub type KernelReadout = macos::MacOSKernelReadout; + #[cfg(feature = "memory")] pub type MemoryReadout = macos::MacOSMemoryReadout; + #[cfg(feature = "general")] pub type GeneralReadout = macos::MacOSGeneralReadout; + #[cfg(feature = "product")] pub type ProductReadout = macos::MacOSProductReadout; + #[cfg(feature = "package")] pub type PackageReadout = macos::MacOSPackageReadout; + #[cfg(feature = "network")] pub type NetworkReadout = macos::MacOSNetworkReadout; + #[cfg(feature = "graphical")] + pub type GraphicalReadout = macos::MacOSGraphicalReadout; + #[cfg(feature = "processor")] + pub type ProcessorReadout = macos::MacOSProcessorReadout; } else if #[cfg(target_os = "netbsd")] { + #[cfg(feature = "graphical")] + mod winman; mod extra; mod netbsd; - mod winman; pub mod dirs; + #[cfg(feature = "battery")] pub type BatteryReadout = netbsd::NetBSDBatteryReadout; + #[cfg(feature = "kernel")] pub type KernelReadout = netbsd::NetBSDKernelReadout; + #[cfg(feature = "memory")] pub type MemoryReadout = netbsd::NetBSDMemoryReadout; + #[cfg(feature = "general")] pub type GeneralReadout = netbsd::NetBSDGeneralReadout; + #[cfg(feature = "product")] pub type ProductReadout = netbsd::NetBSDProductReadout; + #[cfg(feature = "package")] pub type PackageReadout = netbsd::NetBSDPackageReadout; + #[cfg(feature = "network")] pub type NetworkReadout = netbsd::NetBSDNetworkReadout; + #[cfg(feature = "graphical")] + pub type GraphicalReadout = netbsd::NetBSDGraphicalReadout; + #[cfg(feature = "processor")] + pub type ProcessorReadout = netbsd::NetBSDProcessorReadout; } else if #[cfg(target_os = "windows")] { mod windows; + #[cfg(feature = "battery")] pub type BatteryReadout = windows::WindowsBatteryReadout; + #[cfg(feature = "kernel")] pub type KernelReadout = windows::WindowsKernelReadout; + #[cfg(feature = "memory")] pub type MemoryReadout = windows::WindowsMemoryReadout; + #[cfg(feature = "general")] pub type GeneralReadout = windows::WindowsGeneralReadout; + #[cfg(feature = "product")] pub type ProductReadout = windows::WindowsProductReadout; + #[cfg(feature = "package")] pub type PackageReadout = windows::WindowsPackageReadout; + #[cfg(feature = "network")] pub type NetworkReadout = windows::WindowsNetworkReadout; + #[cfg(feature = "graphical")] + pub type GraphicalReadout = windows::WindowsGraphicalReadout; + #[cfg(feature = "processor")] + pub type ProcessorReadout = windows::WindowsProcessorReadout; } else if #[cfg(target_os = "android")] { mod android; mod extra; + #[cfg(feature = "battery")] pub type BatteryReadout = android::AndroidBatteryReadout; + #[cfg(feature = "kernel")] pub type KernelReadout = android::AndroidKernelReadout; + #[cfg(feature = "memory")] pub type MemoryReadout = android::AndroidMemoryReadout; + #[cfg(feature = "general")] pub type GeneralReadout = android::AndroidGeneralReadout; + #[cfg(feature = "product")] pub type ProductReadout = android::AndroidProductReadout; + #[cfg(feature = "package")] pub type PackageReadout = android::AndroidPackageReadout; + #[cfg(feature = "network")] pub type NetworkReadout = android::AndroidNetworkReadout; + #[cfg(feature = "graphical")] + pub type GraphicalReadout = android::AndroidGraphicalReadout; + #[cfg(feature = "processor")] + pub type ProcessorReadout = android::AndroidProcessorReadout; } else if #[cfg(target_os = "freebsd")] { + #[cfg(feature = "graphical")] + mod winman; mod extra; mod freebsd; - mod winman; + #[cfg(feature = "battery")] pub type BatteryReadout = freebsd::FreeBSDBatteryReadout; + #[cfg(feature = "kernel")] pub type KernelReadout = freebsd::FreeBSDKernelReadout; + #[cfg(feature = "memory")] pub type MemoryReadout = freebsd::FreeBSDMemoryReadout; + #[cfg(feature = "general")] pub type GeneralReadout = freebsd::FreeBSDGeneralReadout; + #[cfg(feature = "product")] pub type ProductReadout = freebsd::FreeBSDProductReadout; + #[cfg(feature = "package")] pub type PackageReadout = freebsd::FreeBSDPackageReadout; + #[cfg(feature = "network")] pub type NetworkReadout = freebsd::FreeBSDNetworkReadout; + #[cfg(feature = "graphical")] + pub type NetworkReadout = freebsd::FreeBSDGraphicalReadout; + #[cfg(feature = "processor")] + pub type NetworkReadout = freebsd::FreeBSDProcessorReadout; } else { compiler_error!("This platform is currently not supported by libmacchina."); } } pub struct Readouts { + #[cfg(feature = "battery")] pub battery: BatteryReadout, + #[cfg(feature = "kernel")] pub kernel: KernelReadout, + #[cfg(feature = "memory")] pub memory: MemoryReadout, + #[cfg(feature = "general")] pub general: GeneralReadout, + #[cfg(feature = "product")] pub product: ProductReadout, + #[cfg(feature = "package")] pub packages: PackageReadout, - pub network: PackageReadout, + #[cfg(feature = "network")] + pub network: NetworkReadout, + #[cfg(feature = "graphical")] + pub graphical: GraphicalReadout, + #[cfg(feature = "processor")] + pub processor: ProcessorReadout, } +#[cfg(feature = "version")] pub fn version() -> &'static str { if let Some(git_sha) = option_env!("VERGEN_GIT_SHA_SHORT") { - return Box::leak(format!("{} ({})", env!("CARGO_PKG_VERSION"), git_sha).into_boxed_str()); + Box::leak(format!("{} ({})", env!("CARGO_PKG_VERSION"), git_sha).into_boxed_str()) } else { - return env!("CARGO_PKG_VERSION"); + env!("CARGO_PKG_VERSION") } } +pub mod enums; mod shared; pub mod traits; diff --git a/src/linux/battery.rs b/src/linux/battery.rs new file mode 100644 index 00000000..2609832d --- /dev/null +++ b/src/linux/battery.rs @@ -0,0 +1,126 @@ +use crate::enums::{BatteryState, ReadoutError}; +use crate::extra; +use crate::traits::BatteryReadout; +use std::fs; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; + +pub struct LinuxBatteryReadout; + +impl BatteryReadout for LinuxBatteryReadout { + fn new() -> Self { + LinuxBatteryReadout + } + + fn percentage(&self) -> Result { + if let Some(entries) = extra::get_entries(Path::new("/sys/class/power_supply")) { + let dirs: Vec = entries + .into_iter() + .filter(|x| { + !x.components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .starts_with("ADP") + }) + .collect(); + + if let Some(battery) = dirs.first() { + let path_to_capacity = battery.join("capacity"); + let percentage_text = extra::pop_newline(fs::read_to_string(path_to_capacity)?); + let percentage_parsed = percentage_text.parse::(); + + match percentage_parsed { + Ok(p) => return Ok(p), + Err(e) => { + return Err(ReadoutError::Other(format!( + "Could not parse the value '{}' into a digit: {:?}", + percentage_text, e + ))) + } + }; + } + }; + + Err(ReadoutError::Other("No batteries detected.".to_string())) + } + + fn status(&self) -> Result { + if let Some(entries) = extra::get_entries(Path::new("/sys/class/power_supply")) { + let dirs: Vec = entries + .into_iter() + .filter(|x| { + !x.components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .starts_with("ADP") + }) + .collect(); + + if let Some(battery) = dirs.first() { + let path_to_status = battery.join("status"); + let status_text = + extra::pop_newline(fs::read_to_string(path_to_status)?).to_lowercase(); + + match &status_text[..] { + "charging" => return Ok(BatteryState::Charging), + "discharging" | "full" => return Ok(BatteryState::Discharging), + s => { + return Err(ReadoutError::Other(format!( + "Got an unexpected value \"{}\" reading battery status", + s, + ))) + } + } + } + } + + Err(ReadoutError::Other("No batteries detected.".to_string())) + } + + fn health(&self) -> Result { + if let Some(entries) = extra::get_entries(Path::new("/sys/class/power_supply")) { + let dirs: Vec = entries + .into_iter() + .filter(|x| { + !x.components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .starts_with("ADP") + }) + .collect(); + + if let Some(battery) = dirs.first() { + let energy_full = + extra::pop_newline(fs::read_to_string(battery.join("energy_full"))?) + .parse::(); + + let energy_full_design = + extra::pop_newline(fs::read_to_string(battery.join("energy_full_design"))?) + .parse::(); + + match (energy_full, energy_full_design) { + (Ok(ef), Ok(efd)) => { + if ef > efd { + return Ok(100); + } + + return Ok(((ef as f64 / efd as f64) * 100_f64) as u64); + } + _ => { + return Err(ReadoutError::Other( + "Error calculating battery health.".to_string(), + )) + } + } + } + } + + Err(ReadoutError::Other("No batteries detected.".to_string())) + } +} diff --git a/src/linux/sysinfo_ffi.rs b/src/linux/ffi.rs similarity index 90% rename from src/linux/sysinfo_ffi.rs rename to src/linux/ffi.rs index ab7e474d..48a45b70 100644 --- a/src/linux/sysinfo_ffi.rs +++ b/src/linux/ffi.rs @@ -2,7 +2,7 @@ use std::os::raw::*; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct sysinfo { +pub struct Sysinfo { pub uptime: c_long, pub loads: [c_ulong; 3], pub totalram: c_ulong, @@ -20,12 +20,12 @@ pub struct sysinfo { } extern "C" { - pub fn sysinfo(info: *mut sysinfo) -> c_int; + pub fn sysinfo(info: *mut Sysinfo) -> c_int; } -impl sysinfo { +impl Sysinfo { pub fn new() -> Self { - sysinfo { + Sysinfo { uptime: 0, loads: [0; 3], totalram: 0, diff --git a/src/linux/general.rs b/src/linux/general.rs new file mode 100644 index 00000000..ebdf32ba --- /dev/null +++ b/src/linux/general.rs @@ -0,0 +1,135 @@ +use crate::enums::{ReadoutError, ShellFormat, ShellKind}; +use crate::extra; +use crate::linux::ffi; +use crate::shared; +use crate::traits::GeneralReadout; +use std::fs; +use std::io::{BufRead, BufReader}; +use std::path::Path; +use std::process::{Command, Stdio}; +use sysctl::Ctl; +use sysctl::Sysctl; + +pub struct LinuxGeneralReadout { + hostname_ctl: Option, + sysinfo: ffi::Sysinfo, +} + +impl GeneralReadout for LinuxGeneralReadout { + fn new() -> Self { + LinuxGeneralReadout { + hostname_ctl: Ctl::new("kernel.hostname").ok(), + sysinfo: ffi::Sysinfo::new(), + } + } + + fn backlight(&self) -> Result { + if let Some(base) = extra::get_entries(Path::new("/sys/class/backlight/")) { + if let Some(backlight_path) = base.into_iter().next() { + let max_brightness_path = backlight_path.join("max_brightness"); + let current_brightness_path = backlight_path.join("brightness"); + + let max_brightness_value = + extra::pop_newline(fs::read_to_string(max_brightness_path)?) + .parse::() + .ok(); + + let current_brightness_value = + extra::pop_newline(fs::read_to_string(current_brightness_path)?) + .parse::() + .ok(); + + match (current_brightness_value, max_brightness_value) { + (Some(c), Some(m)) => { + let brightness = c as f64 / m as f64 * 100f64; + return Ok(brightness.round() as usize); + } + _ => { + return Err(ReadoutError::Other(String::from( + "Error occurred while calculating backlight (brightness) value.", + ))); + } + } + } + } + + Err(ReadoutError::Other(String::from( + "Could not obtain backlight information.", + ))) + } + + fn resolution(&self) -> Result { + let drm = Path::new("/sys/class/drm"); + + if let Some(entries) = extra::get_entries(drm) { + let mut resolutions: Vec = Vec::new(); + entries.into_iter().for_each(|entry| { + // Append "modes" to /sys/class/drm// + let modes = entry.join("modes"); + if let Ok(file) = fs::File::open(modes) { + // Push the resolution to the resolutions vector. + if let Some(Ok(res)) = BufReader::new(file).lines().next() { + resolutions.push(res); + } + } + }); + + return Ok(resolutions.join(", ")); + } + + Err(ReadoutError::Other( + "Could not obtain screen resolution from /sys/class/drm".to_string(), + )) + } + + fn username(&self) -> Result { + shared::username() + } + + fn hostname(&self) -> Result { + Ok(self + .hostname_ctl + .as_ref() + .ok_or(ReadoutError::MetricNotAvailable)? + .value_string()?) + } + + fn distribution(&self) -> Result { + use os_release::OsRelease; + let content = OsRelease::new()?; + + if !content.version.is_empty() { + return Ok(format!("{} {}", content.name, content.version)); + } else if !content.version_id.is_empty() { + return Ok(format!("{} {}", content.name, content.version_id)); + } + + Ok(content.name) + } + + fn shell(&self, format: ShellFormat, kind: ShellKind) -> Result { + shared::shell(format, kind) + } + + fn uptime(&self) -> Result { + let mut info = self.sysinfo; + let info_ptr: *mut ffi::Sysinfo = &mut info; + let ret = unsafe { ffi::sysinfo(info_ptr) }; + + if ret != -1 { + return Ok(info.uptime as usize); + } + + Err(ReadoutError::Other( + "Something went wrong during the initialization of the sysinfo struct.".to_string(), + )) + } + + fn operating_system(&self) -> Result { + Err(ReadoutError::NotImplemented) + } + + fn disk_space(&self) -> Result<(u128, u128), ReadoutError> { + shared::disk_space(String::from("/")) + } +} diff --git a/src/linux/graphical.rs b/src/linux/graphical.rs new file mode 100644 index 00000000..cae3ff94 --- /dev/null +++ b/src/linux/graphical.rs @@ -0,0 +1,95 @@ +use crate::enums::ReadoutError; +use crate::extra; +use crate::shared; +use crate::traits::GraphicalReadout; +use std::fs; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +pub struct LinuxGraphicalReadout; + +#[cfg(feature = "graphical")] +impl GraphicalReadout for LinuxGraphicalReadout { + fn new() -> Self { + LinuxGraphicalReadout + } + fn desktop_environment(&self) -> Result { + shared::desktop_environment() + } + + fn session(&self) -> Result { + shared::session() + } + + fn window_manager(&self) -> Result { + shared::window_manager() + } + + fn terminal(&self) -> Result { + // This function returns the PPID of a given PID: + // - The file used to extract this data: /proc//status + // - This function parses and returns the value of the ppid line. + fn get_parent(pid: i32) -> i32 { + let process_path = PathBuf::from("/proc").join(pid.to_string()).join("status"); + let file = fs::File::open(process_path); + match file { + Ok(content) => { + let reader = BufReader::new(content); + for line in reader.lines().flatten() { + if line.to_uppercase().starts_with("PPID") { + let s_mem_kb: String = + line.chars().filter(|c| c.is_digit(10)).collect(); + return s_mem_kb.parse::().unwrap_or(-1); + } + } + + -1 + } + + Err(_) => -1, + } + } + + // This function returns the name associated with a given PPID + fn terminal_name() -> String { + let mut terminal_pid = get_parent(unsafe { libc::getppid() }); + + let path = PathBuf::from("/proc") + .join(terminal_pid.to_string()) + .join("comm"); + + // The below loop will traverse /proc to find the + // terminal inside of which the user is operating + if let Ok(mut terminal_name) = fs::read_to_string(path) { + // Any command_name we find that matches + // one of the elements within this table + // is effectively ignored + while extra::common_shells().contains(&terminal_name.replace('\n', "").as_str()) { + let ppid = get_parent(terminal_pid); + terminal_pid = ppid; + + let path = PathBuf::from("/proc").join(ppid.to_string()).join("comm"); + + if let Ok(comm) = fs::read_to_string(path) { + terminal_name = comm; + } + } + + return terminal_name; + } + + String::new() + } + + let terminal = terminal_name(); + + if terminal.is_empty() { + return Err(ReadoutError::Other( + "Querying terminal information failed".to_string(), + )); + } + + Ok(terminal) + } +} diff --git a/src/linux/kernel.rs b/src/linux/kernel.rs new file mode 100644 index 00000000..231fe48c --- /dev/null +++ b/src/linux/kernel.rs @@ -0,0 +1,35 @@ +use crate::enums::ReadoutError; +use crate::traits::KernelReadout; +use sysctl::Ctl; +use sysctl::Sysctl; +use sysctl::SysctlError; + +pub struct LinuxKernelReadout { + os_release_ctl: Option, + os_type_ctl: Option, +} + +impl KernelReadout for LinuxKernelReadout { + fn new() -> Self { + LinuxKernelReadout { + os_release_ctl: Ctl::new("kernel.osrelease").ok(), + os_type_ctl: Ctl::new("kernel.ostype").ok(), + } + } + + fn os_release(&self) -> Result { + Ok(self + .os_release_ctl + .as_ref() + .ok_or(ReadoutError::MetricNotAvailable)? + .value_string()?) + } + + fn os_type(&self) -> Result { + Ok(self + .os_type_ctl + .as_ref() + .ok_or(ReadoutError::MetricNotAvailable)? + .value_string()?) + } +} diff --git a/src/linux/memory.rs b/src/linux/memory.rs new file mode 100644 index 00000000..789f1e7a --- /dev/null +++ b/src/linux/memory.rs @@ -0,0 +1,72 @@ +use crate::enums::ReadoutError; +use crate::linux::ffi; +use crate::shared; +use crate::traits::MemoryReadout; + +pub struct LinuxMemoryReadout { + sysinfo: ffi::Sysinfo, +} + +impl MemoryReadout for LinuxMemoryReadout { + fn new() -> Self { + LinuxMemoryReadout { + sysinfo: ffi::Sysinfo::new(), + } + } + + fn total(&self) -> Result { + let mut info = self.sysinfo; + let info_ptr: *mut ffi::Sysinfo = &mut info; + let ret = unsafe { ffi::sysinfo(info_ptr) }; + if ret != -1 { + Ok(info.totalram as u64 * info.mem_unit as u64 / 1024) + } else { + Err(ReadoutError::Other( + "Something went wrong during the initialization of the sysinfo struct.".to_string(), + )) + } + } + + fn free(&self) -> Result { + let mut info = self.sysinfo; + let info_ptr: *mut ffi::Sysinfo = &mut info; + let ret = unsafe { ffi::sysinfo(info_ptr) }; + if ret != -1 { + Ok(info.freeram as u64 * info.mem_unit as u64 / 1024) + } else { + Err(ReadoutError::Other( + "Something went wrong during the initialization of the sysinfo struct.".to_string(), + )) + } + } + + fn buffers(&self) -> Result { + let mut info = self.sysinfo; + let info_ptr: *mut ffi::Sysinfo = &mut info; + let ret = unsafe { ffi::sysinfo(info_ptr) }; + if ret != -1 { + Ok(info.bufferram as u64 * info.mem_unit as u64 / 1024) + } else { + Err(ReadoutError::Other( + "Something went wrong during the initialization of the sysinfo struct.".to_string(), + )) + } + } + + fn cached(&self) -> Result { + Ok(shared::get_meminfo_value("Cached")) + } + + fn reclaimable(&self) -> Result { + Ok(shared::get_meminfo_value("SReclaimable")) + } + + fn used(&self) -> Result { + let total = self.total().unwrap(); + let free = self.free().unwrap(); + let cached = self.cached().unwrap(); + let reclaimable = self.reclaimable().unwrap(); + let buffers = self.buffers().unwrap(); + Ok(total - free - cached - reclaimable - buffers) + } +} diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 85860eb9..a35b3ac2 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -1,870 +1,33 @@ -mod sysinfo_ffi; - -use crate::extra; -use crate::extra::get_entries; -use crate::extra::path_extension; -use crate::shared; -use crate::traits::*; -use itertools::Itertools; -use std::fs; -use std::fs::read_dir; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use sysctl::{Ctl, Sysctl}; -use sysinfo_ffi::sysinfo; - -impl From for ReadoutError { - fn from(e: sqlite::Error) -> Self { - ReadoutError::Other(e.to_string()) - } -} - -pub struct LinuxKernelReadout { - os_release_ctl: Option, - os_type_ctl: Option, -} - -pub struct LinuxGeneralReadout { - hostname_ctl: Option, - sysinfo: sysinfo, -} - -pub struct LinuxMemoryReadout { - sysinfo: sysinfo, -} - -pub struct LinuxBatteryReadout; -pub struct LinuxProductReadout; -pub struct LinuxPackageReadout; -pub struct LinuxNetworkReadout; - -impl BatteryReadout for LinuxBatteryReadout { - fn new() -> Self { - LinuxBatteryReadout - } - - fn percentage(&self) -> Result { - if let Some(entries) = get_entries(Path::new("/sys/class/power_supply")) { - let dirs: Vec = entries - .into_iter() - .filter(|x| { - !x.components() - .last() - .unwrap() - .as_os_str() - .to_string_lossy() - .starts_with("ADP") - }) - .collect(); - - if let Some(battery) = dirs.first() { - let path_to_capacity = battery.join("capacity"); - let percentage_text = extra::pop_newline(fs::read_to_string(path_to_capacity)?); - let percentage_parsed = percentage_text.parse::(); - - match percentage_parsed { - Ok(p) => return Ok(p), - Err(e) => { - return Err(ReadoutError::Other(format!( - "Could not parse the value '{}' into a digit: {:?}", - percentage_text, e - ))) - } - }; - } - }; - - Err(ReadoutError::Other("No batteries detected.".to_string())) - } - - fn status(&self) -> Result { - if let Some(entries) = get_entries(Path::new("/sys/class/power_supply")) { - let dirs: Vec = entries - .into_iter() - .filter(|x| { - !x.components() - .last() - .unwrap() - .as_os_str() - .to_string_lossy() - .starts_with("ADP") - }) - .collect(); - - if let Some(battery) = dirs.first() { - let path_to_status = battery.join("status"); - let status_text = - extra::pop_newline(fs::read_to_string(path_to_status)?).to_lowercase(); - - match &status_text[..] { - "charging" => return Ok(BatteryState::Charging), - "discharging" | "full" => return Ok(BatteryState::Discharging), - s => { - return Err(ReadoutError::Other(format!( - "Got an unexpected value \"{}\" reading battery status", - s, - ))) - } - } - } - } - - Err(ReadoutError::Other("No batteries detected.".to_string())) - } - - fn health(&self) -> Result { - if let Some(entries) = get_entries(Path::new("/sys/class/power_supply")) { - let dirs: Vec = entries - .into_iter() - .filter(|x| { - !x.components() - .last() - .unwrap() - .as_os_str() - .to_string_lossy() - .starts_with("ADP") - }) - .collect(); - - if let Some(battery) = dirs.first() { - let energy_full = - extra::pop_newline(fs::read_to_string(battery.join("energy_full"))?) - .parse::(); - - let energy_full_design = - extra::pop_newline(fs::read_to_string(battery.join("energy_full_design"))?) - .parse::(); - - match (energy_full, energy_full_design) { - (Ok(mut ef), Ok(efd)) => { - if ef > efd { - ef = efd; - return Ok(((ef as f64 / efd as f64) * 100_f64) as u64); - } - - return Ok(((ef as f64 / efd as f64) * 100_f64) as u64); - } - _ => { - return Err(ReadoutError::Other( - "Error calculating battery health.".to_string(), - )) - } - } - } - } - - Err(ReadoutError::Other("No batteries detected.".to_string())) - } -} - -impl KernelReadout for LinuxKernelReadout { - fn new() -> Self { - LinuxKernelReadout { - os_release_ctl: Ctl::new("kernel.osrelease").ok(), - os_type_ctl: Ctl::new("kernel.ostype").ok(), - } - } - - fn os_release(&self) -> Result { - Ok(self - .os_release_ctl - .as_ref() - .ok_or(ReadoutError::MetricNotAvailable)? - .value_string()?) - } - - fn os_type(&self) -> Result { - Ok(self - .os_type_ctl - .as_ref() - .ok_or(ReadoutError::MetricNotAvailable)? - .value_string()?) - } -} - -impl NetworkReadout for LinuxNetworkReadout { - fn new() -> Self { - LinuxNetworkReadout - } - - fn tx_bytes(&self, interface: Option<&str>) -> Result { - if let Some(ifname) = interface { - let rx_file = PathBuf::from("/sys/class/net") - .join(ifname) - .join("statistics/tx_bytes"); - let content = std::fs::read_to_string(rx_file)?; - let bytes = extra::pop_newline(content) - .parse::() - .unwrap_or_default(); - Ok(bytes) - } else { - Err(ReadoutError::Other(String::from( - "Please specify a network interface to query.", - ))) - } - } - - fn tx_packets(&self, interface: Option<&str>) -> Result { - if let Some(ifname) = interface { - let rx_file = PathBuf::from("/sys/class/net") - .join(ifname) - .join("statistics/tx_packets"); - let content = std::fs::read_to_string(rx_file)?; - let packets = extra::pop_newline(content) - .parse::() - .unwrap_or_default(); - Ok(packets) - } else { - Err(ReadoutError::Other(String::from( - "Please specify a network interface to query.", - ))) - } - } - - fn rx_bytes(&self, interface: Option<&str>) -> Result { - if let Some(ifname) = interface { - let rx_file = PathBuf::from("/sys/class/net") - .join(ifname) - .join("statistics/rx_bytes"); - let content = std::fs::read_to_string(rx_file)?; - let bytes = extra::pop_newline(content) - .parse::() - .unwrap_or_default(); - Ok(bytes) - } else { - Err(ReadoutError::Other(String::from( - "Please specify a network interface to query.", - ))) - } - } - - fn rx_packets(&self, interface: Option<&str>) -> Result { - if let Some(ifname) = interface { - let rx_file = PathBuf::from("/sys/class/net") - .join(ifname) - .join("statistics/rx_packets"); - let content = std::fs::read_to_string(rx_file)?; - let packets = extra::pop_newline(content) - .parse::() - .unwrap_or_default(); - Ok(packets) - } else { - Err(ReadoutError::Other(String::from( - "Please specify a network interface to query.", - ))) - } - } - - fn physical_address(&self, interface: Option<&str>) -> Result { - if let Some(ifname) = interface { - let rx_file = PathBuf::from("/sys/class/net").join(ifname).join("address"); - let content = std::fs::read_to_string(rx_file)?; - Ok(content) - } else { - Err(ReadoutError::Other(String::from( - "Please specify a network interface to query.", - ))) - } - } - - fn logical_address(&self, interface: Option<&str>) -> Result { - shared::logical_address(interface) - } -} - -impl GeneralReadout for LinuxGeneralReadout { - fn new() -> Self { - LinuxGeneralReadout { - hostname_ctl: Ctl::new("kernel.hostname").ok(), - sysinfo: sysinfo::new(), - } - } - - fn backlight(&self) -> Result { - if let Some(base) = get_entries(Path::new("/sys/class/backlight/")) { - if let Some(backlight_path) = base.into_iter().next() { - let max_brightness_path = backlight_path.join("max_brightness"); - let current_brightness_path = backlight_path.join("brightness"); - - let max_brightness_value = - extra::pop_newline(fs::read_to_string(max_brightness_path)?) - .parse::() - .ok(); - - let current_brightness_value = - extra::pop_newline(fs::read_to_string(current_brightness_path)?) - .parse::() - .ok(); - - match (current_brightness_value, max_brightness_value) { - (Some(c), Some(m)) => { - let brightness = c as f64 / m as f64 * 100f64; - return Ok(brightness.round() as usize); - } - _ => { - return Err(ReadoutError::Other(String::from( - "Error occurred while calculating backlight (brightness) value.", - ))); - } - } - } - } - - Err(ReadoutError::Other(String::from( - "Could not obtain backlight information.", - ))) - } - - fn resolution(&self) -> Result { - let drm = Path::new("/sys/class/drm"); - - if let Some(entries) = get_entries(drm) { - let mut resolutions: Vec = Vec::new(); - entries.into_iter().for_each(|entry| { - // Append "modes" to /sys/class/drm// - let modes = entry.join("modes"); - if let Ok(file) = File::open(modes) { - // Push the resolution to the resolutions vector. - if let Some(Ok(res)) = BufReader::new(file).lines().next() { - resolutions.push(res); - } - } - }); - - return Ok(resolutions.join(", ")); - } - - Err(ReadoutError::Other( - "Could not obtain screen resolution from /sys/class/drm".to_string(), - )) - } - - fn username(&self) -> Result { - shared::username() - } - - fn hostname(&self) -> Result { - Ok(self - .hostname_ctl - .as_ref() - .ok_or(ReadoutError::MetricNotAvailable)? - .value_string()?) - } - - fn distribution(&self) -> Result { - use os_release::OsRelease; - let content = OsRelease::new()?; - - if !content.version.is_empty() { - return Ok(format!("{} {}", content.name, content.version)); - } else if !content.version_id.is_empty() { - return Ok(format!("{} {}", content.name, content.version_id)); - } - - Ok(content.name) - } - - fn desktop_environment(&self) -> Result { - shared::desktop_environment() - } - - fn session(&self) -> Result { - shared::session() - } - - fn window_manager(&self) -> Result { - shared::window_manager() - } - - fn terminal(&self) -> Result { - // This function returns the PPID of a given PID: - // - The file used to extract this data: /proc//status - // - This function parses and returns the value of the ppid line. - fn get_parent(pid: i32) -> i32 { - let process_path = PathBuf::from("/proc").join(pid.to_string()).join("status"); - let file = File::open(process_path); - match file { - Ok(content) => { - let reader = BufReader::new(content); - for line in reader.lines().flatten() { - if line.to_uppercase().starts_with("PPID") { - let s_mem_kb: String = - line.chars().filter(|c| c.is_digit(10)).collect(); - return s_mem_kb.parse::().unwrap_or(-1); - } - } - - -1 - } - - Err(_) => -1, - } - } - - // This function returns the name associated with a given PPID - fn terminal_name() -> String { - let mut terminal_pid = get_parent(unsafe { libc::getppid() }); - - let path = PathBuf::from("/proc") - .join(terminal_pid.to_string()) - .join("comm"); - - // The below loop will traverse /proc to find the - // terminal inside of which the user is operating - if let Ok(mut terminal_name) = fs::read_to_string(path) { - // Any command_name we find that matches - // one of the elements within this table - // is effectively ignored - while extra::common_shells().contains(&terminal_name.replace('\n', "").as_str()) { - let ppid = get_parent(terminal_pid); - terminal_pid = ppid; - - let path = PathBuf::from("/proc").join(ppid.to_string()).join("comm"); - - if let Ok(comm) = fs::read_to_string(path) { - terminal_name = comm; - } - } - - return terminal_name; - } - - String::new() - } - - let terminal = terminal_name(); - - if terminal.is_empty() { - return Err(ReadoutError::Other( - "Querying terminal information failed".to_string(), - )); - } - - Ok(terminal) - } - - fn shell(&self, format: ShellFormat, kind: ShellKind) -> Result { - shared::shell(format, kind) - } - - fn cpu_model_name(&self) -> Result { - Ok(shared::cpu_model_name()) - } - - fn cpu_usage(&self) -> Result { - let mut info = self.sysinfo; - let info_ptr: *mut sysinfo = &mut info; - let ret = unsafe { sysinfo(info_ptr) }; - - if ret != -1 { - let f_load = 1f64 / (1 << libc::SI_LOAD_SHIFT) as f64; - let cpu_usage = info.loads[0] as f64 * f_load; - let cpu_usage_u = - (cpu_usage / self.cpu_cores().unwrap() as f64 * 100.0).round() as usize; - return Ok(cpu_usage_u as usize); - } - - Err(ReadoutError::Other( - "Something went wrong during the initialization of the sysinfo struct.".to_string(), - )) - } - - fn cpu_physical_cores(&self) -> Result { - use std::io::{BufRead, BufReader}; - if let Ok(content) = File::open("/proc/cpuinfo") { - let reader = BufReader::new(content); - for line in reader.lines().flatten() { - if line.to_lowercase().starts_with("cpu cores") { - return Ok(line - .split(':') - .nth(1) - .unwrap() - .trim() - .parse::() - .unwrap()); - } - } - } - - Err(ReadoutError::MetricNotAvailable) - } - - fn cpu_cores(&self) -> Result { - Ok(unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) } as usize) - } - - fn uptime(&self) -> Result { - let mut info = self.sysinfo; - let info_ptr: *mut sysinfo = &mut info; - let ret = unsafe { sysinfo(info_ptr) }; - - if ret != -1 { - return Ok(info.uptime as usize); - } - - Err(ReadoutError::Other( - "Something went wrong during the initialization of the sysinfo struct.".to_string(), - )) - } - - fn machine(&self) -> Result { - let product_readout = LinuxProductReadout::new(); - - let vendor = product_readout.vendor()?; - let family = product_readout.family()?; - let product = product_readout.product()?; - let version = extra::pop_newline(fs::read_to_string("/sys/class/dmi/id/product_version")?); - - // If one field is generic, the others are likely the same, so fail the readout. - if vendor.eq_ignore_ascii_case("system manufacturer") { - return Err(ReadoutError::Other(String::from( - "Your manufacturer may have not specified your machine's product information.", - ))); - } - - let new_product = format!("{} {} {} {}", vendor, family, product, version) - .replace("To be filled by O.E.M.", ""); - - if family == product && family == version { - return Ok(family); - } else if version.is_empty() || version.len() <= 22 { - return Ok(new_product - .split_whitespace() - .into_iter() - .unique() - .join(" ")); - } - - Ok(version) - } - - fn os_name(&self) -> Result { - Err(ReadoutError::NotImplemented) - } - - fn disk_space(&self) -> Result<(u128, u128), ReadoutError> { - shared::disk_space(String::from("/")) - } -} - -impl MemoryReadout for LinuxMemoryReadout { - fn new() -> Self { - LinuxMemoryReadout { - sysinfo: sysinfo::new(), - } - } - - fn total(&self) -> Result { - let mut info = self.sysinfo; - let info_ptr: *mut sysinfo = &mut info; - let ret = unsafe { sysinfo(info_ptr) }; - if ret != -1 { - Ok(info.totalram as u64 * info.mem_unit as u64 / 1024) - } else { - Err(ReadoutError::Other( - "Something went wrong during the initialization of the sysinfo struct.".to_string(), - )) - } - } - - fn free(&self) -> Result { - let mut info = self.sysinfo; - let info_ptr: *mut sysinfo = &mut info; - let ret = unsafe { sysinfo(info_ptr) }; - if ret != -1 { - Ok(info.freeram as u64 * info.mem_unit as u64 / 1024) - } else { - Err(ReadoutError::Other( - "Something went wrong during the initialization of the sysinfo struct.".to_string(), - )) - } - } - - fn buffers(&self) -> Result { - let mut info = self.sysinfo; - let info_ptr: *mut sysinfo = &mut info; - let ret = unsafe { sysinfo(info_ptr) }; - if ret != -1 { - Ok(info.bufferram as u64 * info.mem_unit as u64 / 1024) - } else { - Err(ReadoutError::Other( - "Something went wrong during the initialization of the sysinfo struct.".to_string(), - )) - } - } - - fn cached(&self) -> Result { - Ok(shared::get_meminfo_value("Cached")) - } - - fn reclaimable(&self) -> Result { - Ok(shared::get_meminfo_value("SReclaimable")) - } - - fn used(&self) -> Result { - let total = self.total().unwrap(); - let free = self.free().unwrap(); - let cached = self.cached().unwrap(); - let reclaimable = self.reclaimable().unwrap(); - let buffers = self.buffers().unwrap(); - Ok(total - free - cached - reclaimable - buffers) - } -} - -impl ProductReadout for LinuxProductReadout { - fn new() -> Self { - LinuxProductReadout - } - - fn vendor(&self) -> Result { - Ok(extra::pop_newline(fs::read_to_string( - "/sys/class/dmi/id/sys_vendor", - )?)) - } - - fn family(&self) -> Result { - Ok(extra::pop_newline(fs::read_to_string( - "/sys/class/dmi/id/product_family", - )?)) - } - - fn product(&self) -> Result { - Ok(extra::pop_newline(fs::read_to_string( - "/sys/class/dmi/id/product_name", - )?)) - } -} - -impl PackageReadout for LinuxPackageReadout { - fn new() -> Self { - LinuxPackageReadout - } - - fn count_pkgs(&self) -> Vec<(PackageManager, usize)> { - let mut packages = Vec::new(); - let mut home = PathBuf::new(); - - // Acquire the value of HOME early on to avoid - // doing it multiple times. - if let Ok(path) = std::env::var("HOME") { - home = PathBuf::from(path); - } - - if let Some(c) = LinuxPackageReadout::count_pacman() { - packages.push((PackageManager::Pacman, c)); - } - - if let Some(c) = LinuxPackageReadout::count_dpkg() { - packages.push((PackageManager::Dpkg, c)); - } - - if let Some(c) = LinuxPackageReadout::count_rpm() { - packages.push((PackageManager::Rpm, c)); - } - - if let Some(c) = LinuxPackageReadout::count_portage() { - packages.push((PackageManager::Portage, c)); - } - - if let Some(c) = LinuxPackageReadout::count_cargo() { - packages.push((PackageManager::Cargo, c)); - } - - if let Some(c) = LinuxPackageReadout::count_xbps() { - packages.push((PackageManager::Xbps, c)); - } - - if let Some(c) = LinuxPackageReadout::count_eopkg() { - packages.push((PackageManager::Eopkg, c)); - } - - if let Some(c) = LinuxPackageReadout::count_apk() { - packages.push((PackageManager::Apk, c)); - } - - if let Some(c) = LinuxPackageReadout::count_flatpak(&home) { - packages.push((PackageManager::Flatpak, c)); - } - - if let Some(c) = LinuxPackageReadout::count_snap() { - packages.push((PackageManager::Snap, c)); - } - - if let Some(c) = LinuxPackageReadout::count_homebrew(&home) { - packages.push((PackageManager::Homebrew, c)); - } - - packages - } -} - -impl LinuxPackageReadout { - /// Returns the number of installed packages for systems - /// that utilize `rpm` as their package manager. - fn count_rpm() -> Option { - // Return the number of installed packages using sqlite (~1ms) - // as directly calling rpm or dnf is too expensive (~500ms) - let db = "/var/lib/rpm/rpmdb.sqlite"; - if !Path::new(db).is_file() { - return None; - } - - let connection = sqlite::open(db); - if let Ok(con) = connection { - let statement = con.prepare("SELECT COUNT(*) FROM Installtid"); - if let Ok(mut s) = statement { - if s.next().is_ok() { - return match s.read::>(0) { - Ok(Some(count)) => Some(count as usize), - _ => None, - }; - } - } - } - - None - } - - /// Returns the number of installed packages for systems - /// that utilize `pacman` as their package manager. - fn count_pacman() -> Option { - let pacman_dir = Path::new("/var/lib/pacman/local"); - if pacman_dir.is_dir() { - if let Ok(read_dir) = read_dir(pacman_dir) { - return Some(read_dir.count()); - }; - } - - None - } - - /// Returns the number of installed packages for systems - /// that utilize `eopkg` as their package manager. - fn count_eopkg() -> Option { - let eopkg_dir = Path::new("/var/lib/eopkg/package"); - if eopkg_dir.is_dir() { - if let Ok(read_dir) = read_dir(eopkg_dir) { - return Some(read_dir.count()); - }; - } - - None - } - - /// Returns the number of installed packages for systems - /// that utilize `portage` as their package manager. - fn count_portage() -> Option { - let pkg_dir = Path::new("/var/db/pkg"); - if pkg_dir.exists() { - return Some(walkdir::WalkDir::new(pkg_dir).into_iter().count()); - } - - None - } - - /// Returns the number of installed packages for systems - /// that utilize `dpkg` as their package manager. - fn count_dpkg() -> Option { - let dpkg_dir = Path::new("/var/lib/dpkg/info"); - - get_entries(dpkg_dir).map(|entries| { - entries - .iter() - .filter(|x| extra::path_extension(x).unwrap_or_default() == "list") - .into_iter() - .count() - }) - } - - /// Returns the number of installed packages for systems - /// that have `homebrew` installed. - fn count_homebrew(home: &Path) -> Option { - let mut base = home.join(".linuxbrew"); - if !base.is_dir() { - base = PathBuf::from("/home/linuxbrew/.linuxbrew"); - } - - match read_dir(base.join("Cellar")) { - // subtract 1 as ${base}/Cellar contains a ".keepme" file - Ok(dir) => Some(dir.count() - 1), - Err(_) => None, - } - } - - /// Returns the number of installed packages for systems - /// that utilize `xbps` as their package manager. - fn count_xbps() -> Option { - if !extra::which("xbps-query") { - return None; - } - - let xbps_output = Command::new("xbps-query") - .arg("-l") - .stdout(Stdio::piped()) - .output() - .unwrap(); - - extra::count_lines( - String::from_utf8(xbps_output.stdout) - .expect("ERROR: \"xbps-query -l\" output was not valid UTF-8"), - ) - } - - /// Returns the number of installed packages for systems - /// that utilize `apk` as their package manager. - fn count_apk() -> Option { - if !extra::which("apk") { - return None; - } - - let apk_output = Command::new("apk") - .arg("info") - .stdout(Stdio::piped()) - .output() - .unwrap(); - - extra::count_lines( - String::from_utf8(apk_output.stdout) - .expect("ERROR: \"apk info\" output was not valid UTF-8"), - ) - } - - /// Returns the number of installed packages for systems - /// that have `cargo` installed. - fn count_cargo() -> Option { - shared::count_cargo() - } - - /// Returns the number of installed packages for systems - /// that have `flatpak` installed. - fn count_flatpak(home: &Path) -> Option { - let global_flatpak_dir = Path::new("/var/lib/flatpak/app"); - let user_flatpak_dir = home.join(".local/share/flatpak/app"); - - match (read_dir(global_flatpak_dir), read_dir(user_flatpak_dir)) { - (Ok(g), Ok(u)) => Some(g.count() + u.count()), - (Ok(g), _) => Some(g.count()), - (_, Ok(u)) => Some(u.count()), - _ => None, - } - } - - /// Returns the number of installed packages for systems - /// that have `snap` installed. - fn count_snap() -> Option { - let snap_dir = Path::new("/var/lib/snapd/snaps"); - if let Some(entries) = get_entries(snap_dir) { - return Some( - entries - .iter() - .filter(|&x| path_extension(x).unwrap_or_default() == "snap") - .into_iter() - .count(), - ); - } - - None +#![allow(unused_imports)] + +use crate::enums::ReadoutError; +#[cfg(any(feature = "kernel", feature = "general"))] +use sysctl::SysctlError; + +#[cfg(feature = "battery")] +pub mod battery; +#[cfg(any(feature = "general", feature = "processor", feature = "memory"))] +pub mod ffi; +#[cfg(feature = "general")] +pub mod general; +#[cfg(feature = "graphical")] +pub mod graphical; +#[cfg(feature = "kernel")] +pub mod kernel; +#[cfg(feature = "memory")] +pub mod memory; +#[cfg(feature = "network")] +pub mod network; +#[cfg(feature = "package")] +pub mod package; +#[cfg(feature = "processor")] +pub mod processor; +#[cfg(feature = "product")] +pub mod product; + +#[cfg(any(feature = "kernel", feature = "general"))] +impl From for ReadoutError { + fn from(e: SysctlError) -> Self { + ReadoutError::Other(format!("Could not access sysctl: {:?}", e)) } } diff --git a/src/linux/network.rs b/src/linux/network.rs new file mode 100644 index 00000000..e59c8449 --- /dev/null +++ b/src/linux/network.rs @@ -0,0 +1,97 @@ +use crate::enums::ReadoutError; +use crate::extra; +use crate::shared; +use crate::traits::NetworkReadout; +use std::path::PathBuf; + +pub struct LinuxNetworkReadout; + +impl NetworkReadout for LinuxNetworkReadout { + fn new() -> Self { + LinuxNetworkReadout + } + + fn tx_bytes(&self, interface: Option<&str>) -> Result { + if let Some(ifname) = interface { + let rx_file = PathBuf::from("/sys/class/net") + .join(ifname) + .join("statistics/tx_bytes"); + let content = std::fs::read_to_string(rx_file)?; + let bytes = extra::pop_newline(content) + .parse::() + .unwrap_or_default(); + Ok(bytes) + } else { + Err(ReadoutError::Other(String::from( + "Please specify a network interface to query.", + ))) + } + } + + fn tx_packets(&self, interface: Option<&str>) -> Result { + if let Some(ifname) = interface { + let rx_file = PathBuf::from("/sys/class/net") + .join(ifname) + .join("statistics/tx_packets"); + let content = std::fs::read_to_string(rx_file)?; + let packets = extra::pop_newline(content) + .parse::() + .unwrap_or_default(); + Ok(packets) + } else { + Err(ReadoutError::Other(String::from( + "Please specify a network interface to query.", + ))) + } + } + + fn rx_bytes(&self, interface: Option<&str>) -> Result { + if let Some(ifname) = interface { + let rx_file = PathBuf::from("/sys/class/net") + .join(ifname) + .join("statistics/rx_bytes"); + let content = std::fs::read_to_string(rx_file)?; + let bytes = extra::pop_newline(content) + .parse::() + .unwrap_or_default(); + Ok(bytes) + } else { + Err(ReadoutError::Other(String::from( + "Please specify a network interface to query.", + ))) + } + } + + fn rx_packets(&self, interface: Option<&str>) -> Result { + if let Some(ifname) = interface { + let rx_file = PathBuf::from("/sys/class/net") + .join(ifname) + .join("statistics/rx_packets"); + let content = std::fs::read_to_string(rx_file)?; + let packets = extra::pop_newline(content) + .parse::() + .unwrap_or_default(); + Ok(packets) + } else { + Err(ReadoutError::Other(String::from( + "Please specify a network interface to query.", + ))) + } + } + + fn physical_address(&self, interface: Option<&str>) -> Result { + if let Some(ifname) = interface { + let rx_file = PathBuf::from("/sys/class/net").join(ifname).join("address"); + let content = std::fs::read_to_string(rx_file)?; + Ok(content) + } else { + Err(ReadoutError::Other(String::from( + "Please specify a network interface to query.", + ))) + } + } + + fn logical_address(&self, interface: Option<&str>) -> Result { + shared::logical_address(interface) + } +} diff --git a/src/linux/package.rs b/src/linux/package.rs new file mode 100644 index 00000000..f524949f --- /dev/null +++ b/src/linux/package.rs @@ -0,0 +1,243 @@ +use crate::enums::PackageManager; +use crate::extra; +use crate::extra::get_entries; +use crate::extra::path_extension; +use crate::shared; +use crate::traits::PackageReadout; +use std::fs::read_dir; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +pub struct LinuxPackageReadout; + +impl PackageReadout for LinuxPackageReadout { + fn new() -> Self { + LinuxPackageReadout + } + + fn count_pkgs(&self) -> Vec<(PackageManager, usize)> { + let mut packages = Vec::new(); + let mut home = PathBuf::new(); + + // Acquire the value of HOME early on to avoid + // doing it multiple times. + if let Ok(path) = std::env::var("HOME") { + home = PathBuf::from(path); + } + + if let Some(c) = LinuxPackageReadout::count_pacman() { + packages.push((PackageManager::Pacman, c)); + } + + if let Some(c) = LinuxPackageReadout::count_dpkg() { + packages.push((PackageManager::Dpkg, c)); + } + + if let Some(c) = LinuxPackageReadout::count_rpm() { + packages.push((PackageManager::Rpm, c)); + } + + if let Some(c) = LinuxPackageReadout::count_portage() { + packages.push((PackageManager::Portage, c)); + } + + if let Some(c) = LinuxPackageReadout::count_cargo() { + packages.push((PackageManager::Cargo, c)); + } + + if let Some(c) = LinuxPackageReadout::count_xbps() { + packages.push((PackageManager::Xbps, c)); + } + + if let Some(c) = LinuxPackageReadout::count_eopkg() { + packages.push((PackageManager::Eopkg, c)); + } + + if let Some(c) = LinuxPackageReadout::count_apk() { + packages.push((PackageManager::Apk, c)); + } + + if let Some(c) = LinuxPackageReadout::count_flatpak(&home) { + packages.push((PackageManager::Flatpak, c)); + } + + if let Some(c) = LinuxPackageReadout::count_snap() { + packages.push((PackageManager::Snap, c)); + } + + if let Some(c) = LinuxPackageReadout::count_homebrew(&home) { + packages.push((PackageManager::Homebrew, c)); + } + + packages + } +} + +impl LinuxPackageReadout { + /// Returns the number of installed packages for systems + /// that utilize `rpm` as their package manager. + fn count_rpm() -> Option { + // Return the number of installed packages using sqlite (~1ms) + // as directly calling rpm or dnf is too expensive (~500ms) + let db = "/var/lib/rpm/rpmdb.sqlite"; + if !Path::new(db).is_file() { + return None; + } + + let connection = sqlite::open(db); + if let Ok(con) = connection { + let statement = con.prepare("SELECT COUNT(*) FROM Installtid"); + if let Ok(mut s) = statement { + if s.next().is_ok() { + return match s.read::>(0) { + Ok(Some(count)) => Some(count as usize), + _ => None, + }; + } + } + } + + None + } + + /// Returns the number of installed packages for systems + /// that utilize `pacman` as their package manager. + fn count_pacman() -> Option { + let pacman_dir = Path::new("/var/lib/pacman/local"); + if pacman_dir.is_dir() { + if let Ok(read_dir) = read_dir(pacman_dir) { + return Some(read_dir.count()); + }; + } + + None + } + + /// Returns the number of installed packages for systems + /// that utilize `eopkg` as their package manager. + fn count_eopkg() -> Option { + let eopkg_dir = Path::new("/var/lib/eopkg/package"); + if eopkg_dir.is_dir() { + if let Ok(read_dir) = read_dir(eopkg_dir) { + return Some(read_dir.count()); + }; + } + + None + } + + /// Returns the number of installed packages for systems + /// that utilize `portage` as their package manager. + fn count_portage() -> Option { + let pkg_dir = Path::new("/var/db/pkg"); + if pkg_dir.exists() { + return Some(walkdir::WalkDir::new(pkg_dir).into_iter().count()); + } + + None + } + + /// Returns the number of installed packages for systems + /// that utilize `dpkg` as their package manager. + fn count_dpkg() -> Option { + let dpkg_dir = Path::new("/var/lib/dpkg/info"); + + get_entries(dpkg_dir).map(|entries| { + entries + .iter() + .filter(|x| extra::path_extension(x).unwrap_or_default() == "list") + .into_iter() + .count() + }) + } + + /// Returns the number of installed packages for systems + /// that have `homebrew` installed. + fn count_homebrew(home: &Path) -> Option { + let mut base = home.join(".linuxbrew"); + if !base.is_dir() { + base = PathBuf::from("/home/linuxbrew/.linuxbrew"); + } + + match read_dir(base.join("Cellar")) { + // subtract 1 as ${base}/Cellar contains a ".keepme" file + Ok(dir) => Some(dir.count() - 1), + Err(_) => None, + } + } + + /// Returns the number of installed packages for systems + /// that utilize `xbps` as their package manager. + fn count_xbps() -> Option { + if !extra::which("xbps-query") { + return None; + } + + let xbps_output = Command::new("xbps-query") + .arg("-l") + .stdout(Stdio::piped()) + .output() + .unwrap(); + + extra::count_lines( + String::from_utf8(xbps_output.stdout) + .expect("ERROR: \"xbps-query -l\" output was not valid UTF-8"), + ) + } + + /// Returns the number of installed packages for systems + /// that utilize `apk` as their package manager. + fn count_apk() -> Option { + if !extra::which("apk") { + return None; + } + + let apk_output = Command::new("apk") + .arg("info") + .stdout(Stdio::piped()) + .output() + .unwrap(); + + extra::count_lines( + String::from_utf8(apk_output.stdout) + .expect("ERROR: \"apk info\" output was not valid UTF-8"), + ) + } + + /// Returns the number of installed packages for systems + /// that have `cargo` installed. + fn count_cargo() -> Option { + shared::count_cargo() + } + + /// Returns the number of installed packages for systems + /// that have `flatpak` installed. + fn count_flatpak(home: &Path) -> Option { + let global_flatpak_dir = Path::new("/var/lib/flatpak/app"); + let user_flatpak_dir = home.join(".local/share/flatpak/app"); + + match (read_dir(global_flatpak_dir), read_dir(user_flatpak_dir)) { + (Ok(g), Ok(u)) => Some(g.count() + u.count()), + (Ok(g), _) => Some(g.count()), + (_, Ok(u)) => Some(u.count()), + _ => None, + } + } + + /// Returns the number of installed packages for systems + /// that have `snap` installed. + fn count_snap() -> Option { + let snap_dir = Path::new("/var/lib/snapd/snaps"); + if let Some(entries) = get_entries(snap_dir) { + return Some( + entries + .iter() + .filter(|&x| path_extension(x).unwrap_or_default() == "snap") + .into_iter() + .count(), + ); + } + + None + } +} diff --git a/src/linux/processor.rs b/src/linux/processor.rs new file mode 100644 index 00000000..5e4bc473 --- /dev/null +++ b/src/linux/processor.rs @@ -0,0 +1,63 @@ +use crate::enums::ReadoutError; +use crate::linux::ffi; +use crate::shared; +use crate::traits::ProcessorReadout; +use std::fs; +use std::io::{BufRead, BufReader}; + +pub struct LinuxProcessorReadout { + sysinfo: ffi::Sysinfo, +} + +impl ProcessorReadout for LinuxProcessorReadout { + fn new() -> Self { + LinuxProcessorReadout { + sysinfo: ffi::Sysinfo::new(), + } + } + fn cpu_model_name(&self) -> Result { + Ok(shared::cpu_model_name()) + } + + fn cpu_usage(&self) -> Result { + let mut info = self.sysinfo; + let info_ptr: *mut ffi::Sysinfo = &mut info; + let ret = unsafe { ffi::sysinfo(info_ptr) }; + + if ret != -1 { + let f_load = 1f64 / (1 << libc::SI_LOAD_SHIFT) as f64; + let cpu_usage = info.loads[0] as f64 * f_load; + let cpu_usage_u = + (cpu_usage / self.cpu_cores().unwrap() as f64 * 100.0).round() as usize; + return Ok(cpu_usage_u as usize); + } + + Err(ReadoutError::Other( + "Something went wrong during the initialization of the sysinfo struct.".to_string(), + )) + } + + fn cpu_physical_cores(&self) -> Result { + use std::io::{BufRead, BufReader}; + if let Ok(content) = fs::File::open("/proc/cpuinfo") { + let reader = BufReader::new(content); + for line in reader.lines().flatten() { + if line.to_lowercase().starts_with("cpu cores") { + return Ok(line + .split(':') + .nth(1) + .unwrap() + .trim() + .parse::() + .unwrap()); + } + } + } + + Err(ReadoutError::MetricNotAvailable) + } + + fn cpu_cores(&self) -> Result { + Ok(unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) } as usize) + } +} diff --git a/src/linux/product.rs b/src/linux/product.rs new file mode 100644 index 00000000..8d45ca9c --- /dev/null +++ b/src/linux/product.rs @@ -0,0 +1,60 @@ +use crate::enums::ReadoutError; +use crate::extra; +use crate::traits::ProductReadout; +use itertools::Itertools; +use std::fs; + +pub struct LinuxProductReadout; + +impl ProductReadout for LinuxProductReadout { + fn new() -> Self { + LinuxProductReadout + } + + fn vendor(&self) -> Result { + Ok(extra::pop_newline(fs::read_to_string( + "/sys/class/dmi/id/sys_vendor", + )?)) + } + + fn family(&self) -> Result { + Ok(extra::pop_newline(fs::read_to_string( + "/sys/class/dmi/id/product_family", + )?)) + } + + fn product(&self) -> Result { + Ok(extra::pop_newline(fs::read_to_string( + "/sys/class/dmi/id/product_name", + )?)) + } + + fn machine(&self) -> Result { + let vendor = self.vendor()?; + let family = self.family()?; + let product = self.product()?; + let version = extra::pop_newline(fs::read_to_string("/sys/class/dmi/id/product_version")?); + + // If one field is generic, the others are likely the same, so fail the readout. + if vendor.eq_ignore_ascii_case("system manufacturer") { + return Err(ReadoutError::Other(String::from( + "Your manufacturer may have not specified your machine's product information.", + ))); + } + + let new_product = format!("{} {} {} {}", vendor, family, product, version) + .replace("To be filled by O.E.M.", ""); + + if family == product && family == version { + return Ok(family); + } else if version.is_empty() || version.len() <= 22 { + return Ok(new_product + .split_whitespace() + .into_iter() + .unique() + .join(" ")); + } + + Ok(version) + } +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 3b88b88e..71d343a6 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -359,11 +359,6 @@ impl GeneralReadout for MacOSGeneralReadout { ))) } - fn machine(&self) -> Result { - let product_readout = MacOSProductReadout::new(); - product_readout.product() - } - fn os_name(&self) -> Result { let version: String = self.operating_system_version()?.into(); let major_version_name = macos_version_to_name(&self.operating_system_version()?); @@ -533,6 +528,11 @@ impl ProductReadout for MacOSProductReadout { Ok(mac_model) } + + fn machine(&self) -> Result { + let product_readout = MacOSProductReadout::new(); + product_readout.product() + } } impl PackageReadout for MacOSPackageReadout { diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 02458ac9..e25e25fc 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] #![allow(unused_imports)] -use crate::traits::{ReadoutError, ShellFormat, ShellKind}; +use crate::enums::{ReadoutError, ShellFormat, ShellKind}; +use std::ffi::CString; use std::fs::read_dir; use std::fs::read_to_string; use std::io::Error; @@ -11,16 +12,15 @@ use std::process::{Command, Stdio}; use std::{env, fs}; use std::{ffi::CStr, path::PathBuf}; -use std::ffi::CString; -#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] -use sysctl::SysctlError; +// #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] +// use sysctl::SysctlError; -#[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] -impl From for ReadoutError { - fn from(e: SysctlError) -> Self { - ReadoutError::Other(format!("Could not access sysctl: {:?}", e)) - } -} +// #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] +// impl From for ReadoutError { +// fn from(e: SysctlError) -> Self { +// ReadoutError::Other(format!("Could not access sysctl: {:?}", e)) +// } +// } impl From for ReadoutError { fn from(e: Error) -> Self { @@ -28,6 +28,7 @@ impl From for ReadoutError { } } +#[cfg(feature = "general")] #[cfg(not(any(target_os = "freebsd", target_os = "macos", target_os = "windows")))] pub(crate) fn uptime() -> Result { let uptime_file_text = fs::read_to_string("/proc/uptime")?; @@ -43,6 +44,7 @@ pub(crate) fn uptime() -> Result { } } +#[cfg(feature = "graphical")] #[cfg(not(any( feature = "openwrt", target_os = "android", @@ -67,6 +69,7 @@ pub(crate) fn desktop_environment() -> Result { } } +#[cfg(feature = "graphical")] #[cfg(not(any( feature = "openwrt", target_os = "android", @@ -82,6 +85,7 @@ pub(crate) fn session() -> Result { } } +#[cfg(feature = "graphical")] #[cfg(all(target_os = "linux", not(feature = "openwrt")))] pub(crate) fn window_manager() -> Result { use crate::winman::*; @@ -111,6 +115,7 @@ pub(crate) fn window_manager() -> Result { } } +#[cfg(feature = "graphical")] #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] pub(crate) fn resolution() -> Result { use x11rb::connection::Connection; @@ -132,6 +137,7 @@ pub(crate) fn resolution() -> Result { ))) } +#[cfg(feature = "general")] #[cfg(target_family = "unix")] fn get_passwd_struct() -> Result<*mut libc::passwd, ReadoutError> { let uid: libc::uid_t = unsafe { libc::geteuid() }; @@ -148,6 +154,7 @@ fn get_passwd_struct() -> Result<*mut libc::passwd, ReadoutError> { ))) } +#[cfg(feature = "general")] #[cfg(target_family = "unix")] pub(crate) fn username() -> Result { let passwd = get_passwd_struct()?; @@ -162,6 +169,7 @@ pub(crate) fn username() -> Result { ))) } +#[cfg(feature = "general")] #[cfg(target_family = "unix")] pub(crate) fn shell(shorthand: ShellFormat, kind: ShellKind) -> Result { match kind { @@ -213,6 +221,7 @@ pub(crate) fn shell(shorthand: ShellFormat, kind: ShellKind) -> Result String { use std::io::{BufRead, BufReader}; @@ -235,6 +244,7 @@ pub(crate) fn cpu_model_name() -> String { } } +#[cfg(feature = "processor")] #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))] pub(crate) fn cpu_usage() -> Result { let nelem: i32 = 1; @@ -252,16 +262,19 @@ pub(crate) fn cpu_usage() -> Result { ))) } -#[cfg(target_family = "unix")] +#[cfg(feature = "processor")] +#[cfg(all(target_family = "unix", not(target_os = "linux")))] pub(crate) fn cpu_cores() -> Result { Ok(num_cpus::get()) } -#[cfg(target_family = "unix")] +#[cfg(feature = "processor")] +#[cfg(all(target_family = "unix", not(target_os = "linux")))] pub(crate) fn cpu_physical_cores() -> Result { Ok(num_cpus::get_physical()) } +#[cfg(feature = "general")] #[cfg(not(any(target_os = "netbsd", target_os = "windows")))] pub(crate) fn disk_space(path: String) -> Result<(u128, u128), ReadoutError> { let mut s: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); @@ -284,6 +297,7 @@ pub(crate) fn disk_space(path: String) -> Result<(u128, u128), ReadoutError> { ))) } +#[cfg(feature = "memory")] /// Obtain the value of a specified field from `/proc/meminfo` needed to calculate memory usage #[cfg(not(any(target_os = "macos", target_os = "windows")))] pub(crate) fn get_meminfo_value(value: &str) -> u64 { @@ -305,6 +319,7 @@ pub(crate) fn get_meminfo_value(value: &str) -> u64 { } #[cfg(not(target_os = "windows"))] +#[cfg(feature = "network")] pub(crate) fn logical_address(interface: Option<&str>) -> Result { if let Some(ifname) = interface { if let Ok(addresses) = if_addrs::get_if_addrs() { @@ -326,6 +341,7 @@ pub(crate) fn logical_address(interface: Option<&str>) -> Result Option { if let Ok(cargo_home) = home::cargo_home() { let bin = cargo_home.join("bin"); diff --git a/src/traits.rs b/src/traits.rs index 34105e7c..cacdd25b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -2,46 +2,7 @@ //! different readouts from various operating systems. For each operating system, there must be an implementation of these traits. #![allow(unused_variables)] -/// This enum contains possible error types when doing sensor & variable readouts. -#[derive(Debug, Clone)] -pub enum ReadoutError { - /// A specific metric might not be available on all systems (e. g. battery percentage on a - /// desktop). \ - /// If you encounter this error, it means that the requested value is not available. - MetricNotAvailable, - - /// The default error for any readout that is not implemented by a particular platform. - NotImplemented, - - /// A readout for a metric might be available, but fails due to missing dependencies or other - /// unsatisfied requirements. - Other(String), - - /// Getting a readout on a specific operating system might not make sense or causes some other - /// kind of warning. This is not necessarily an error. - Warning(String), -} - -impl ToString for ReadoutError { - fn to_string(&self) -> String { - match self { - ReadoutError::MetricNotAvailable => { - String::from("Metric is not available on this system.") - } - ReadoutError::NotImplemented => { - String::from("This metric is not available on this platform or is not yet implemented by libmacchina.") - } - ReadoutError::Other(s) => s.clone(), - ReadoutError::Warning(s) => s.clone(), - } - } -} - -impl From<&ReadoutError> for ReadoutError { - fn from(r: &ReadoutError) -> Self { - r.to_owned() - } -} +use crate::enums::*; /** This trait provides the necessary functions for querying battery statistics from the host @@ -51,34 +12,7 @@ computer. A desktop computer might not be able to provide values such as `percen # Example ``` -use libmacchina::traits::BatteryReadout; -use libmacchina::traits::ReadoutError; -use libmacchina::traits::BatteryState; - -//You can add fields to this struct which will then need to be initialized in the -//BatteryReadout::new() function. -pub struct MacOSBatteryReadout; - -impl BatteryReadout for MacOSBatteryReadout { - fn new() -> Self { - MacOSBatteryReadout {} - } - - fn percentage(&self) -> Result { - //get the battery percentage somehow... - Ok(100u8) //always fully charged - } - - fn status(&self) -> Result { - //check if battery is being charged... - Ok(BatteryState::Charging) //always charging. - } - - fn health(&self) -> Result{ - //check the battery health... - Ok(100) //totally healtyh - } -} +// TODO: Add examples ``` */ pub trait BatteryReadout { @@ -99,32 +33,75 @@ pub trait BatteryReadout { } /** -This trait is used for implementing common functions for reading kernel properties, such as -kernel name and version. +This trait provides the necessary functions for querying the user's graphical environment. # Example ``` -use libmacchina::traits::KernelReadout; -use libmacchina::traits::ReadoutError; +// TODO: Add examples +``` +*/ +pub trait GraphicalReadout { + /// Creates a new instance of the structure which implements this trait. + fn new() -> Self; -pub struct MacOSKernelReadout; + /// This function should return the name of the used desktop environment. + /// + /// _e.g._ `Plasma` + fn desktop_environment(&self) -> Result; -impl KernelReadout for MacOSKernelReadout { - fn new() -> Self { - MacOSKernelReadout {} - } + /// This function should return the type of session that's in use. + /// + /// _e.g._ `Wayland` + fn session(&self) -> Result; - fn os_release(&self) -> Result { - // Get kernel version - Ok(String::from("20.0.1")) - } + /// This function should return the name of the used window manager. + /// + /// _e.g._ `Sway` + fn window_manager(&self) -> Result; - fn os_type(&self) -> Result { - // Get kernel name - Ok(String::from("Darwin")) - } + /// This function should return the name of the used terminal emulator. + /// + /// _e.g._ `kitty` + fn terminal(&self) -> Result; +} + +/** +This trait provides the necessary functions for querying the host's processor. + +# Example + +``` +// TODO: Add examples +``` +*/ +pub trait ProcessorReadout { + /// Creates a new instance of the structure which implements this trait. + fn new() -> Self; + + /// This function should return the model name of the CPU \ + /// + /// _e.g._ `Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz` + fn cpu_model_name(&self) -> Result; + + /// This function should return the average CPU usage over the last minute. + fn cpu_usage(&self) -> Result; + + /// This function should return the number of physical cores of the host's processor. + fn cpu_physical_cores(&self) -> Result; + + /// This function should return the number of logical cores of the host's processor. + fn cpu_cores(&self) -> Result; } + +/** +This trait is used for implementing common functions for reading kernel properties, such as +kernel name and version. + +# Example + +``` +// TODO: Add examples ``` */ pub trait KernelReadout { @@ -158,47 +135,7 @@ intending to calculate memory usage on your own. # Example ``` -use libmacchina::traits::MemoryReadout; -use libmacchina::traits::ReadoutError; - -pub struct MacOSMemoryReadout; - -impl MemoryReadout for MacOSMemoryReadout { - fn new() -> Self { - MacOSMemoryReadout {} - } - - fn total(&self) -> Result { - // Get the total physical memory for the machine - Ok(512 * 1024) // Return 512mb in kilobytes. - } - - fn free(&self) -> Result { - // Get the amount of free memory - Ok(256 * 1024) // Return 256mb in kilobytes. - } - - fn buffers(&self) -> Result { - // Get the current memory value for buffers - Ok(64 * 1024) // Return 64mb in kilobytes. - } - - fn cached(&self) -> Result { - // Get the amount of cached content in memory - Ok(128 * 1024) // Return 128mb in kilobytes. - } - - fn reclaimable(&self) -> Result { - // Get the amount of reclaimable memory - Ok(64 * 1024) // Return 64mb in kilobytes. - } - - fn used(&self) -> Result { - // Get the currently used memory. - Ok(256 * 1024) // Return 256mb in kilobytes. - } -} - +// TODO: Add examples ``` */ pub trait MemoryReadout { @@ -231,22 +168,7 @@ the host system. Almost all modern operating systems use some kind of package ma # Example ``` -use libmacchina::traits::{PackageReadout, PackageManager}; -use libmacchina::traits::ReadoutError; - -pub struct MacOSPackageReadout; - -impl PackageReadout for MacOSPackageReadout { - fn new() -> Self { - MacOSPackageReadout {} - } - - fn count_pkgs(&self) -> Vec<(PackageManager, usize)> { - // Check if homebrew 🍻 is installed and count installed packages... - vec![(PackageManager::Homebrew, 120)] - } -} -``` +// TODO: Add examples */ pub trait PackageReadout { /// Creates a new instance of the structure which implements this trait. @@ -264,40 +186,7 @@ This trait provides an interface to various networking statistics about the host # Example ``` -use libmacchina::traits::NetworkReadout; -use libmacchina::traits::ReadoutError; - -pub struct MacOSNetworkReadout; - -impl NetworkReadout for MacOSNetworkReadout { - fn new() -> Self { - MacOSNetworkReadout {} - } - - fn tx_bytes(&self, interface: Option<&str>) -> Result { - todo!() - } - - fn tx_packets(&self, interface: Option<&str>) -> Result { - todo!() - } - - fn rx_bytes(&self, interface: Option<&str>) -> Result { - todo!() - } - - fn rx_packets(&self, interface: Option<&str>) -> Result { - todo!() - } - - fn logical_address(&self, interface: Option<&str>) -> Result { - todo!() - } - - fn physical_address(&self, interface: Option<&str>) -> Result { - todo!() - } -} +// TODO: Add examples ``` */ @@ -341,28 +230,7 @@ about the host machine. # Example ``` -use libmacchina::traits::ProductReadout; -use libmacchina::traits::ReadoutError; - -pub struct MacOSProductReadout; - -impl ProductReadout for MacOSProductReadout { - fn new() -> Self { - MacOSProductReadout {} - } - - fn vendor(&self) -> Result { - Ok(String::from("Apple")) - } - - fn family(&self) -> Result { - Ok(String::from("MacBook Pro")) - } - - fn product(&self) -> Result { - Ok(String::from("MacBookPro16,1")) - } -} +// TODO: Add examples ``` */ pub trait ProductReadout { @@ -389,6 +257,11 @@ pub trait ProductReadout { /// /// This is set by the machine's manufacturer. fn product(&self) -> Result; + + /// This function should return the name of the physical machine. + /// + /// _e.g._ `MacBookPro11,5` + fn machine(&self) -> Result; } /** @@ -398,93 +271,7 @@ information about the running operating system and current user. # Example ``` -use libmacchina::traits::GeneralReadout; -use libmacchina::traits::ReadoutError; -use libmacchina::traits::ShellFormat; -use libmacchina::traits::ShellKind; - -pub struct MacOSGeneralReadout; - -impl GeneralReadout for MacOSGeneralReadout { - - fn new() -> Self { - MacOSGeneralReadout {} - } - - fn backlight(&self) -> Result { - Ok(100) // Brightness is at its maximum - } - - fn resolution(&self) -> Result { - Ok("1920x1080".to_string()) - } - - fn username(&self) -> Result { - //let username = NSUserName(); - Ok(String::from("johndoe")) - } - - fn hostname(&self) -> Result { - Ok("supercomputer".to_string()) - } - - fn distribution(&self) -> Result { - Ok("Arch Linux".to_string()) - } - - fn desktop_environment(&self) -> Result { - Ok("Plasma".to_string()) - } - - fn session(&self) -> Result { - Ok("Wayland".to_string()) - } - - fn window_manager(&self) -> Result { - Ok("KWin".to_string()) - } - - fn terminal(&self) -> Result { - Ok("kitty".to_string()) - } - - fn shell(&self, _shorthand: ShellFormat, kind: ShellKind) -> Result { - Ok("bash".to_string()) - } - - fn cpu_model_name(&self) -> Result { - Ok("Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz".to_string()) - } - - fn cpu_usage(&self) -> Result { - Ok(20) //20% CPU usage - } - - fn cpu_physical_cores(&self) -> Result { - Ok(4) - } - - fn cpu_cores(&self) -> Result { - Ok(8) - } - - fn uptime(&self) -> Result { - Ok(24 * 60 * 60) //1 day - } - - fn machine(&self) -> Result { - Ok("MacBookPro11,5".to_string()) - } - - fn os_name(&self) -> Result { - Ok("macOS 11.2.2 Big Sur".to_string()) - } - - fn disk_space(&self) -> Result<(u128, u128), ReadoutError> { - Ok((50000000,1000000000)) // Used / Total - } -} - +// TODO: Add examples ``` */ pub trait GeneralReadout { @@ -511,31 +298,16 @@ pub trait GeneralReadout { /// _e.g._ `supercomputer` fn hostname(&self) -> Result; + /// This function should return the name of the OS in a pretty format. + /// + /// _e.g._ `macOS 11.2.2 Big Sur` + fn operating_system(&self) -> Result; + /// This function should return the name of the distribution of the operating system. /// /// _e.g._ `Arch Linux` fn distribution(&self) -> Result; - /// This function should return the name of the used desktop environment. - /// - /// _e.g._ `Plasma` - fn desktop_environment(&self) -> Result; - - /// This function should return the type of session that's in use. - /// - /// _e.g._ `Wayland` - fn session(&self) -> Result; - - /// This function should return the name of the used window manager. - /// - /// _e.g._ `KWin` - fn window_manager(&self) -> Result; - - /// This function should return the name of the used terminal emulator. - /// - /// _e.g._ `kitty` - fn terminal(&self) -> Result; - /** This function should return the currently running shell depending on the `_shorthand` value. @@ -547,114 +319,13 @@ pub trait GeneralReadout { _e.g._ /bin/bash, /bin/zsh, etc. */ - fn shell(&self, _shorthand: ShellFormat, kind: ShellKind) -> Result; - /// This function should return the model name of the CPU \ - /// - /// _e.g._ `Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz` - fn cpu_model_name(&self) -> Result; - - /// This function should return the average CPU usage over the last minute. - fn cpu_usage(&self) -> Result; - - /// This function should return the number of physical cores of the host's processor. - fn cpu_physical_cores(&self) -> Result; - - /// This function should return the number of logical cores of the host's processor. - fn cpu_cores(&self) -> Result; - /// This function should return the uptime of the OS in seconds. fn uptime(&self) -> Result; - /// This function should return the name of the physical machine. - /// - /// _e.g._ `MacBookPro11,5` - fn machine(&self) -> Result; - - /// This function should return the name of the OS in a pretty format. - /// - /// _e.g._ `macOS 11.2.2 Big Sur` - fn os_name(&self) -> Result; - /// This function should return the used disk space in a human-readable and desirable format. /// /// _e.g._ '1.2TB / 2TB' fn disk_space(&self) -> Result<(u128, u128), ReadoutError>; } - -/// Holds the possible variants for battery status. -pub enum BatteryState { - Charging, - Discharging, -} - -impl From for &'static str { - fn from(state: BatteryState) -> &'static str { - match state { - BatteryState::Charging => "Charging", - BatteryState::Discharging => "Discharging", - } - } -} - -/// The currently running shell is a program, whose path -/// can be _relative_, or _absolute_. -#[derive(Debug)] -pub enum ShellFormat { - Relative, - Absolute, -} - -#[derive(Debug)] -/// There are two distinct kinds of shells, a so called *"current"* shell, i.e. the shell the user is currently using. -/// And a default shell, i.e. that the user sets for themselves using the `chsh` tool. -pub enum ShellKind { - Current, - Default, -} - -/// The supported package managers whose packages can be extracted. -pub enum PackageManager { - Homebrew, - MacPorts, - Pacman, - Portage, - Dpkg, - Opkg, - Xbps, - Pkgsrc, - Apk, - Eopkg, - Rpm, - Cargo, - Flatpak, - Snap, - Android, - Pkg, - Scoop, -} - -impl ToString for PackageManager { - fn to_string(&self) -> String { - String::from(match self { - PackageManager::Homebrew => "Homebrew", - PackageManager::MacPorts => "MacPorts", - PackageManager::Pacman => "pacman", - PackageManager::Portage => "portage", - PackageManager::Dpkg => "dpkg", - PackageManager::Opkg => "opkg", - PackageManager::Xbps => "xbps", - PackageManager::Pkgsrc => "pkgsrc", - PackageManager::Apk => "apk", - PackageManager::Eopkg => "eopkg", - PackageManager::Rpm => "rpm", - PackageManager::Cargo => "cargo", - PackageManager::Flatpak => "flatpak", - PackageManager::Snap => "snap", - PackageManager::Android => "Android", - PackageManager::Pkg => "pkg", - PackageManager::Scoop => "Scoop", - }) - } -} diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 7ebf69a1..1702df83 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -332,9 +332,7 @@ impl GeneralReadout for WindowsGeneralReadout { )) } - fn disk_space( - &self, - ) -> Result<(u128, u128), ReadoutError> { + fn disk_space(&self) -> Result<(u128, u128), ReadoutError> { Err(ReadoutError::NotImplemented) } } diff --git a/src/winman.rs b/src/winman.rs index 89b66096..03127502 100644 --- a/src/winman.rs +++ b/src/winman.rs @@ -1,8 +1,8 @@ //! This module provides a set of functions that detect the name of the window manager the host is //! running. +use crate::enums::ReadoutError; use crate::extra; -use crate::traits::ReadoutError; use std::process::{Command, Stdio};