diff --git a/Cargo.lock b/Cargo.lock index e946152..8bc954b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1186,6 +1186,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "path-dedot" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" +dependencies = [ + "once_cell", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -1344,6 +1353,7 @@ dependencies = [ "num_cpus", "nvml-wrapper", "paste", + "path-dedot", "plotters", "plotters-cairo", "pretty_assertions", diff --git a/Cargo.toml b/Cargo.toml index 1f037cb..6fd5340 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ nix = { version = "0.29.0", default-features = false, features = [ num_cpus = "1.16.0" nvml-wrapper = "0.10.0" paste = "1.0.15" +path-dedot = "3.1.1" plotters = { version = "0.3.7", default-features = false, features = [ "area_series", ] } diff --git a/src/ui/window.rs b/src/ui/window.rs index 91dd198..6a93570 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -354,12 +354,9 @@ impl MainWindow { #[strong(rename_to = this)] self, move |_, key, _, _| { - match key { - gdk::Key::Control_L => { - debug!("Ctrl is being held, halting apps and processes updates"); - this.imp().pause_updates.set(true); - } - _ => (), + if key == gdk::Key::Control_L { + debug!("Ctrl is being held, halting apps and processes updates"); + this.imp().pause_updates.set(true); }; glib::Propagation::Proceed } @@ -368,12 +365,9 @@ impl MainWindow { #[weak] imp, move |_, key, _, _| { - match key { - gdk::Key::Control_L => { - debug!("Ctrl has been released, continuing apps and processes updates"); - imp.pause_updates.set(false); - } - _ => (), + if key == gdk::Key::Control_L { + debug!("Ctrl has been released, continuing apps and processes updates"); + imp.pause_updates.set(false); }; } )); diff --git a/src/utils/battery.rs b/src/utils/battery.rs index 5142209..eecb4aa 100644 --- a/src/utils/battery.rs +++ b/src/utils/battery.rs @@ -176,19 +176,21 @@ impl Battery { Ok(list) } - pub fn is_valid_power_supply(path: &PathBuf) -> bool { + pub fn is_valid_power_supply>(path: P) -> bool { + let path = path.as_ref(); + //type == "Battery" //scope != "Device" (HID device batteries) - let power_supply_type = std::fs::read_to_string(path.join("type")); - let power_supply_scope = std::fs::read_to_string(path.join("scope")); - if let Ok(power_supply_type) = power_supply_type { - if power_supply_type.trim() == "Battery" { - if power_supply_scope.is_err() || power_supply_scope.unwrap().trim() != "Device" { - return true; - } - } - } - false + + let power_supply_type_is_battery = std::fs::read_to_string(path.join("type")) + .map(|ps_type| ps_type.trim() == "Battery") + .unwrap_or_default(); + + let power_supply_scope_is_not_device = std::fs::read_to_string(path.join("scope")) + .map(|ps_scope| ps_scope.trim() != "Device") + .unwrap_or(true); + + power_supply_type_is_battery && power_supply_scope_is_not_device } pub fn from_sysfs>(sysfs_path: P) -> Battery { diff --git a/src/utils/cpu.rs b/src/utils/cpu.rs index 2dcc467..60a438a 100644 --- a/src/utils/cpu.rs +++ b/src/utils/cpu.rs @@ -295,7 +295,7 @@ fn parse_proc_stat>(stat: S) -> Vec> { .lines() .skip(1) .filter(|line| line.starts_with("cpu")) - .map(|line| parse_proc_stat_line(line)) + .map(parse_proc_stat_line) .collect() } diff --git a/src/utils/drive.rs b/src/utils/drive.rs index ea2739a..b491fff 100644 --- a/src/utils/drive.rs +++ b/src/utils/drive.rs @@ -1,7 +1,11 @@ +use super::units::convert_storage; +use crate::i18n::{i18n, i18n_f}; +use crate::utils::link::{Link, LinkData}; use anyhow::{bail, Context, Result}; use gtk::gio::{Icon, ThemedIcon}; use lazy_regex::{lazy_regex, Lazy, Regex}; use log::trace; +use path_dedot::ParseDot; use process_data::pci_slot::PciSlot; use std::{ collections::HashMap, @@ -10,16 +14,16 @@ use std::{ str::FromStr, }; -use super::units::convert_storage; -use crate::i18n::{i18n, i18n_f}; -use crate::utils::link::{Link, PcieLink}; - const PATH_SYSFS: &str = "/sys/block"; static RE_DRIVE: Lazy = lazy_regex!( r" *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*) *(?P[0-9]*)" ); +static RE_ATA_LINK: Lazy = lazy_regex!(r"(^link(\d+))$"); + +static RE_ATA_SLOT: Lazy = lazy_regex!(r"(^.+?/ata(\d+))/"); + #[derive(Debug)] pub struct DriveData { pub inner: Drive, @@ -83,11 +87,26 @@ pub enum DriveType { Unknown, } +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] +pub enum DriveSlot { + Pci(PciSlot), + Ata(AtaSlot), + #[default] + Unknown, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct AtaSlot { + pub ata_device: u8, + pub ata_link: u8, +} + #[derive(Debug, Clone, Default, Eq)] pub struct Drive { pub model: Option, pub drive_type: DriveType, pub block_device: String, + pub slot: DriveSlot, pub sysfs_path: PathBuf, } @@ -145,7 +164,7 @@ impl Drive { drive.block_device = block_device; drive.model = drive.model().ok().map(|model| model.trim().to_string()); drive.drive_type = drive.drive_type().unwrap_or_default(); - + drive.slot = drive.slot().unwrap_or_default(); trace!("Created Drive object of {path:?}: {drive:?}"); drive @@ -315,21 +334,73 @@ impl Drive { /// Will return `Err` if there are errors during /// reading or parsing, or if the drive link type is not supported pub fn link(&self) -> Result { - match self.drive_type { - DriveType::Nvme => self.link_for_nvme(), - _ => bail!("unsupported drive type"), + match self.slot { + DriveSlot::Pci(slot) => Ok(Link::Pcie(LinkData::from_pci_slot(&slot)?)), + DriveSlot::Ata(slot) => Ok(Link::Sata(LinkData::from_ata_slot(&slot)?)), + _ => bail!("unsupported drive connection type"), } } - fn link_for_nvme(&self) -> Result { - let pcie_address_path = self.sysfs_path.join("device").join("address"); - let pci_slot = PciSlot::from_str( - &std::fs::read_to_string(pcie_address_path) - .map(|x| x.trim().to_string()) - .context("Could not find PCIe address in sysfs for nvme")?, - )?; - let pcie_link = PcieLink::from_pci_slot(pci_slot)?; - Ok(Link::Pcie(pcie_link)) + pub fn slot(&self) -> Result { + if let Ok(pci_slot) = self.pci_slot() { + Ok(DriveSlot::Pci(pci_slot)) + } else if let Ok(ata_slot) = self.ata_slot() { + Ok(DriveSlot::Ata(ata_slot)) + } else { + bail!("unsupported drive slot type") + } + } + + fn pci_slot(&self) -> Result { + let pci_address_path = self.sysfs_path.join("device").join("address"); + let pci_address = + std::fs::read_to_string(pci_address_path).map(|x| x.trim().to_string())?; + + Ok(PciSlot::from_str(&pci_address)?) + } + + fn ata_slot(&self) -> Result { + let symlink = std::fs::read_link(&self.sysfs_path) + .context("Could not read sysfs_path as symlink")? + .to_string_lossy() + .to_string(); + // ../../devices/pci0000:40/0000:40:08.3/0000:47:00.0/ata25/host24/target24:0:0/24:0:0:0/block/sda + + let ata_sub_path_match = RE_ATA_SLOT + .captures(&symlink) + .context("No ata match found, probably no ata device")?; + + let ata_sub_path = ata_sub_path_match + .get(1) + .context("No ata match found, probably no ata device")? + .as_str(); + + let ata_device = ata_sub_path_match + .get(2) + .context("could not match digits in ata")? + .as_str() + .parse::()?; + + let ata_path = Path::new(&self.sysfs_path).join("..").join(ata_sub_path); + let dot_parsed_path = ata_path.parse_dot()?.clone(); + let sub_dirs = std::fs::read_dir(dot_parsed_path).context("Could not read ata path")?; + + let ata_link = sub_dirs + .filter_map(|x| { + x.ok().and_then(|x| { + RE_ATA_LINK + .captures(&x.file_name().to_string_lossy()) + .and_then(|captures| captures.get(2)) + .and_then(|capture| capture.as_str().parse::().ok()) + }) + }) + .next() + .context("No ata link number found")?; + + Ok(AtaSlot { + ata_device, + ata_link, + }) } /// Returns the World-Wide Identification of the drive diff --git a/src/utils/gpu/mod.rs b/src/utils/gpu/mod.rs index 2aaba96..3f7e393 100644 --- a/src/utils/gpu/mod.rs +++ b/src/utils/gpu/mod.rs @@ -16,7 +16,7 @@ use std::{ }; use self::{amd::AmdGpu, intel::IntelGpu, nvidia::NvidiaGpu, other::OtherGpu}; -use crate::utils::link::{Link, PcieLink}; +use crate::utils::link::{Link, LinkData}; use crate::{ i18n::i18n, utils::{pci::Device, read_uevent}, @@ -29,7 +29,7 @@ pub const VID_AMD: u16 = 0x1002; pub const VID_INTEL: u16 = 0x8086; pub const VID_NVIDIA: u16 = 0x10DE; -const RE_CARD_ENUMARATOR: Lazy = lazy_regex!(r"(\d+)\/?$"); +static RE_CARD_ENUMARATOR: Lazy = lazy_regex!(r"(\d+)\/?$"); #[derive(Debug)] pub struct GpuData { @@ -532,7 +532,7 @@ impl Gpu { pub fn link(&self) -> Result { if let GpuIdentifier::PciSlot(pci_slot) = self.gpu_identifier() { - let pcie_link = PcieLink::from_pci_slot(pci_slot)?; + let pcie_link = LinkData::from_pci_slot(&pci_slot)?; Ok(Link::Pcie(pcie_link)) } else { bail!("Could not retrieve PciSlot from Gpu"); diff --git a/src/utils/link.rs b/src/utils/link.rs index 0f4b490..2983162 100644 --- a/src/utils/link.rs +++ b/src/utils/link.rs @@ -1,23 +1,25 @@ use crate::i18n::i18n; +use crate::utils::drive::AtaSlot; +use crate::utils::link::SataSpeed::{Sata150, Sata300, Sata600}; use anyhow::{anyhow, bail, Context, Error, Result}; use process_data::pci_slot::PciSlot; use std::fmt::{Display, Formatter}; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::str::FromStr; #[derive(Debug, Default)] pub enum Link { - Pcie(PcieLink), + Pcie(LinkData), + Sata(LinkData), #[default] Unknown, } #[derive(Debug)] -pub struct PcieLink { - pub current: Result, - pub max: Result, +pub struct LinkData { + pub current: Result, + pub max: Result, } - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct PcieLinkData { pub speed: PcieSpeed, @@ -33,9 +35,15 @@ pub enum PcieSpeed { Pcie50, Pcie60, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SataSpeed { + Sata150, + Sata300, + Sata600, +} -impl PcieLink { - pub fn from_pci_slot(pci_slot: PciSlot) -> Result { +impl LinkData { + pub fn from_pci_slot(pci_slot: &PciSlot) -> Result { let pcie_dir = format!("/sys/bus/pci/devices/{pci_slot}/"); let pcie_folder = Path::new(pcie_dir.as_str()); if pcie_folder.exists() { @@ -44,7 +52,9 @@ impl PcieLink { bail!("Could not find PCIe address entry for {pci_slot}"); } - fn read_pcie_link_data(path: &PathBuf) -> Result { + fn read_pcie_link_data>(path: P) -> Result { + let path = path.as_ref(); + let current_pcie_speed_raw = std::fs::read_to_string(path.join("current_link_speed")) .map(|x| x.trim().to_string()) .context("Could not read current link speed")?; @@ -66,21 +76,27 @@ impl PcieLink { } else { Err(anyhow!("Could not parse max PCIe link")) }; - Ok(PcieLink { current, max }) + Ok(Self { current, max }) } } impl PcieLinkData { - pub fn parse(speed_raw: &str, width_raw: &str) -> Result { - let speed = PcieSpeed::from_str(speed_raw)?; + pub fn parse>(speed_raw: S, width_raw: S) -> Result { + let speed = PcieSpeed::from_str(speed_raw.as_ref())?; let width = width_raw + .as_ref() .parse::() .context("Could not parse PCIe width")?; Ok(PcieLinkData { speed, width }) } } -impl Display for PcieLink { +impl Display for LinkData +where + T: Display, + T: Copy, + T: PartialEq, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if let Ok(current) = self.current { let has_different_max = { @@ -101,6 +117,25 @@ impl Display for PcieLink { } } +impl LinkData { + pub fn from_ata_slot(ata_slot: &AtaSlot) -> Result { + let ata_link_path = + Path::new("/sys/class/ata_link").join(format!("link{}", ata_slot.ata_link)); + + let current_sata_speed_raw = std::fs::read_to_string(ata_link_path.join("sata_spd")) + .map(|x| x.trim().to_string()) + .context("Could not read sata_spd")?; + let max_sata_speed_raw = std::fs::read_to_string(ata_link_path.join("sata_spd_max")) + .map(|x| x.trim().to_string()) + .context("Could not read sata_spd_max"); + + let current = SataSpeed::from_str(¤t_sata_speed_raw); + let max = max_sata_speed_raw.and_then(|raw| SataSpeed::from_str(&raw)); + + Ok(Self { current, max }) + } +} + impl Display for PcieLinkData { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{} ×{}", self.speed, self.width) @@ -135,11 +170,38 @@ impl FromStr for PcieSpeed { "16.0 GT/s PCIe" => Ok(PcieSpeed::Pcie40), "32.0 GT/s PCIe" => Ok(PcieSpeed::Pcie50), "64.0 GT/s PCIe" => Ok(PcieSpeed::Pcie60), - _ => Err(anyhow!("Could not parse PCIe speed")), + _ => Err(anyhow!("Could not parse PCIe speed: '{s}'")), + } + } +} + +impl FromStr for SataSpeed { + type Err = Error; + + fn from_str(s: &str) -> std::result::Result { + match s { + // https://en.wikipedia.org/wiki/SATA + "1.5 Gbps" => Ok(Sata150), + "3.0 Gbps" => Ok(Sata300), + "6.0 Gbps" => Ok(Sata600), + _ => Err(anyhow!("Could not parse SATA speed: '{s}'")), } } } +impl Display for SataSpeed { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Sata150 => "SATA-150", + Sata300 => "SATA-300", + Sata600 => "SATA-600", + } + ) + } +} impl Display for Link { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( @@ -147,6 +209,7 @@ impl Display for Link { "{}", match self { Link::Pcie(data) => data.to_string(), + Link::Sata(data) => data.to_string(), Link::Unknown => i18n("N/A"), } ) @@ -155,7 +218,7 @@ impl Display for Link { #[cfg(test)] mod test { - use crate::utils::link::{PcieLink, PcieLinkData, PcieSpeed}; + use crate::utils::link::{LinkData, PcieLinkData, PcieSpeed, SataSpeed}; use anyhow::anyhow; use std::collections::HashMap; use std::str::FromStr; @@ -233,7 +296,7 @@ mod test { let map = get_test_pcie_link_data(); for link_data in map.keys() { - let input = PcieLink { + let input = LinkData { current: Ok(link_data.clone()), max: Ok(link_data.clone()), }; @@ -248,7 +311,7 @@ mod test { let map = get_test_pcie_link_data(); for link_data in map.keys() { - let input = PcieLink { + let input = LinkData { current: Ok(link_data.clone()), max: Err(anyhow!("No max")), }; @@ -265,7 +328,7 @@ mod test { for current_data in map.keys() { for max_data in map.keys() { if current_data != max_data { - let input = PcieLink { + let input = LinkData { current: Ok(current_data.clone()), max: Ok(max_data.clone()), }; @@ -280,7 +343,7 @@ mod test { #[test] fn display_pcie_link_different_max_2() { - let input = PcieLink { + let input = LinkData { current: Ok(PcieLinkData { speed: PcieSpeed::Pcie40, width: 8, @@ -341,4 +404,49 @@ mod test { ), ]) } + + #[test] + fn parse_sata_link_speeds() { + let map = HashMap::from([ + ("1.5 Gbps", SataSpeed::Sata150), + ("3.0 Gbps", SataSpeed::Sata300), + ("6.0 Gbps", SataSpeed::Sata600), + ]); + + for input in map.keys() { + let result = SataSpeed::from_str(input); + assert!(result.is_ok(), "Could not parse SATA speed for '{}'", input); + let expected = map[input]; + pretty_assertions::assert_eq!(expected, result.unwrap()); + } + } + + #[test] + fn parse_sata_link_speeds_failure() { + let invalid = vec!["4.0 Gbps", "SOMETHING_ELSE", ""]; + + for input in invalid { + let result = SataSpeed::from_str(input); + assert!( + result.is_err(), + "Could parse SATA speed for '{}' while we don't expect that", + input + ); + } + } + + #[test] + fn display_sata_link_speeds() { + let map = HashMap::from([ + (SataSpeed::Sata150, "SATA-150"), + (SataSpeed::Sata300, "SATA-300"), + (SataSpeed::Sata600, "SATA-600"), + ]); + + for input in map.keys() { + let result = input.to_string(); + let expected = map[input]; + pretty_assertions::assert_str_eq!(expected, result); + } + } } diff --git a/src/utils/memory.rs b/src/utils/memory.rs index 130d11f..fa2e11b 100644 --- a/src/utils/memory.rs +++ b/src/utils/memory.rs @@ -241,7 +241,7 @@ impl MemoryDevice { .and_then(|regex| regex.captures(dmi)) .and_then(|captures| captures.get(1)) .and_then(|capture| capture.as_str().parse::().ok()) - .map_or(true, |int| int != 0); + != Some(0); let speed = if installed { Regex::new(&TEMPLATE_RE_CONFIGURED_SPEED_MTS.replace('%', &i))