dg_sysfs 1.1.2

Utils for working with SysFS in Garden OS
Documentation
pub mod disk;

use crate::classes::block::disk::{Disk, DiskStatsMap, DiskUsage, Partition};
use crate::{SYSFS_DIR, SysFsNode, read_value};
use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind};
use std::path::Path;

const BLOCK_CLASS: &str = "block";

#[derive(Debug, Serialize, Deserialize)]
pub enum BlockDevice {
    Disk(Disk),
    Partition(Partition),
    Loopback,
    Ram,
    ZRam,
    DeviceMapper,
}
impl SysFsNode for BlockDevice {
    async fn read<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
        let path = path.as_ref();
        match path.file_name() {
            Some(name) => {
                let name = name.to_string_lossy().to_string();
                Ok(if name.starts_with("loop") {
                    BlockDevice::Loopback
                } else if name.starts_with("ram") {
                    BlockDevice::Ram
                } else if name.starts_with("zram") {
                    BlockDevice::ZRam
                } else if name.starts_with("dm") {
                    BlockDevice::DeviceMapper
                } else {
                    match read_value(BLOCK_CLASS, &name, "partition").await.ok() {
                        Some(_) => BlockDevice::Partition(Partition::read(path).await?),
                        None => BlockDevice::Disk(Disk::read(path).await?),
                    }
                })
            }
            None => Err(Error::new(
                ErrorKind::InvalidInput,
                "Failed to read device name from path",
            )),
        }
    }
}

#[derive(Debug, Default)]
pub struct BlockEnumerator {
    disk_cache: Vec<Disk>,
    device_stats_cache: DiskStatsMap,
}
impl BlockEnumerator {
    pub fn new() -> BlockEnumerator {
        BlockEnumerator {
            disk_cache: Vec::new(),
            device_stats_cache: DiskStatsMap::default(),
        }
    }
    pub async fn get_devices(&self) -> Result<Vec<BlockDevice>, Error> {
        let path = Path::new(SYSFS_DIR).join(BLOCK_CLASS);
        let mut block_devices = vec![];
        let mut entries = tokio::fs::read_dir(path).await?;
        while let Some(entry) = entries.next_entry().await? {
            block_devices.push(BlockDevice::read(entry.path()).await?);
        }
        Ok(block_devices)
    }
    pub async fn reload_disks(&mut self) -> Result<(), Error> {
        let path = Path::new(SYSFS_DIR).join(BLOCK_CLASS);
        let mut disks = vec![];
        let mut entries = tokio::fs::read_dir(path).await?;
        while let Some(entry) = entries.next_entry().await? {
            if let Some(v) = Self::read_disk(entry.path()).await? {
                disks.push(v);
            }
        }
        self.disk_cache = disks;
        self.device_stats_cache = DiskStatsMap::parse().await?;
        Ok(())
    }
    pub fn get_all_disks(&self) -> &[Disk] {
        &self.disk_cache
    }
    pub fn get_disk_usage(&self, name: &str) -> Option<DiskUsage> {
        self.device_stats_cache.get_disk_usage(name)
    }

    async fn read_disk<P: AsRef<Path>>(path: P) -> Result<Option<Disk>, Error> {
        let path = path.as_ref();
        match path.file_name() {
            Some(name) => {
                let name = name.to_string_lossy().to_string();
                if name.starts_with("loop")
                    || name.starts_with("ram")
                    || name.starts_with("zram")
                    || name.starts_with("dm")
                {
                    Ok(None)
                } else {
                    match read_value(BLOCK_CLASS, &name, "partition").await.ok() {
                        Some(_) => Ok(None),
                        None => Disk::read(path).await.map(Some),
                    }
                }
            }
            None => Err(Error::new(
                ErrorKind::InvalidInput,
                "Failed to read device name from path",
            )),
        }
    }
}

#[tokio::test]
pub async fn test_block_enumerator() {
    use dg_logger::DruidGardenLogger;
    use log::Level;
    use log::info;
    let _logger = DruidGardenLogger::build()
        .current_level(Level::Info)
        .init()
        .unwrap();
    let devices = BlockEnumerator::new().get_devices().await.unwrap();
    for device in devices {
        info!("{device:?}")
    }
}