use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use sysinfo::Disks;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskInfo {
pub entries: Vec<DiskEntry>,
pub total: Option<DiskTotal>,
pub collected_at: chrono::DateTime<chrono::Local>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskEntry {
pub filesystem: String,
pub disk_type: String,
pub total: u64,
pub used: u64,
pub available: u64,
pub percent: f64,
pub mount_point: PathBuf,
pub is_removable: bool,
pub block_size: u64,
pub inodes_total: Option<u64>,
pub inodes_used: Option<u64>,
pub inodes_free: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskTotal {
pub total: u64,
pub used: u64,
pub available: u64,
}
impl DiskInfo {
pub fn collect(
_human_readable: bool,
_local_only: bool,
_all: bool,
_fs_types: &[String],
_exclude_types: &[String],
block_size: u64,
) -> Result<Self> {
let disks = Disks::new_with_refreshed_list();
let mut entries = Vec::new();
for disk in &disks {
let mount_point = disk.mount_point().to_path_buf();
let fs_type = disk.file_system().to_string_lossy().to_string();
if !_all && fs_type.is_empty() {
continue;
}
if !_fs_types.is_empty() && !_fs_types.contains(&fs_type) {
continue;
}
if !_exclude_types.is_empty() && _exclude_types.contains(&fs_type) {
continue;
}
let total = disk.total_space();
let available = disk.available_space();
let used = total.saturating_sub(available);
let percent = if total > 0 {
(used as f64 / total as f64) * 100.0
} else {
0.0
};
entries.push(DiskEntry {
filesystem: disk.name().to_string_lossy().to_string(),
disk_type: fs_type,
total,
used,
available,
percent,
mount_point,
is_removable: disk.is_removable(),
block_size,
inodes_total: None,
inodes_used: None,
inodes_free: None,
});
}
let total = if entries.len() > 1 {
Some(DiskTotal {
total: entries.iter().map(|e| e.total).sum(),
used: entries.iter().map(|e| e.used).sum(),
available: entries.iter().map(|e| e.available).sum(),
})
} else {
None
};
Ok(DiskInfo {
entries,
total,
collected_at: chrono::Local::now(),
})
}
pub fn filter_by_mounts(&mut self, mounts: &[String]) {
if mounts.is_empty() {
return;
}
self.entries.retain(|entry| {
let mount_str = entry.mount_point.display().to_string().to_lowercase();
mounts.iter().any(|m| {
let m = m.to_lowercase();
mount_str == m || m.starts_with(&mount_str) || mount_str.starts_with(&m)
})
});
self.total = if self.entries.len() > 1 {
Some(DiskTotal {
total: self.entries.iter().map(|e| e.total).sum(),
used: self.entries.iter().map(|e| e.used).sum(),
available: self.entries.iter().map(|e| e.available).sum(),
})
} else {
None
};
}
}