130 lines
4.1 KiB
Rust
130 lines
4.1 KiB
Rust
//! Filesystem usage renderer. Args: `[mountpoint]` (default `/`).
|
|
|
|
use crate::config::Config;
|
|
use crate::error::{FluxoError, Result};
|
|
use crate::modules::WaybarModule;
|
|
use crate::output::WaybarOutput;
|
|
use crate::state::AppReceivers;
|
|
use crate::utils::{TokenValue, classify_usage, format_template};
|
|
|
|
/// Renders used/total for a given mount point. Returns [`FluxoError::Module`]
|
|
/// if the mount point isn't present in the current disk snapshot.
|
|
pub struct DiskModule;
|
|
|
|
impl WaybarModule for DiskModule {
|
|
async fn run(
|
|
&self,
|
|
config: &Config,
|
|
state: &AppReceivers,
|
|
args: &[&str],
|
|
) -> Result<WaybarOutput> {
|
|
let mountpoint = args.first().unwrap_or(&"/");
|
|
|
|
let disks = state.disks.borrow().clone();
|
|
|
|
if disks.is_empty() {
|
|
return Ok(WaybarOutput {
|
|
text: "Disk Loading...".to_string(),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
for disk in &disks {
|
|
if disk.mount_point == *mountpoint {
|
|
let total = disk.total_bytes as f64;
|
|
let available = disk.available_bytes as f64;
|
|
let used = total - available;
|
|
|
|
let used_gb = used / 1024.0 / 1024.0 / 1024.0;
|
|
let total_gb = total / 1024.0 / 1024.0 / 1024.0;
|
|
let free_gb = available / 1024.0 / 1024.0 / 1024.0;
|
|
|
|
let percentage = if total > 0.0 {
|
|
(used / total) * 100.0
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let class = classify_usage(percentage, 80.0, 95.0);
|
|
|
|
let text = format_template(
|
|
&config.disk.format,
|
|
&[
|
|
("mount", TokenValue::String(mountpoint.to_string())),
|
|
("used", TokenValue::Float(used_gb)),
|
|
("total", TokenValue::Float(total_gb)),
|
|
],
|
|
);
|
|
|
|
return Ok(WaybarOutput {
|
|
text,
|
|
tooltip: Some(format!(
|
|
"Used: {:.1}G\nTotal: {:.1}G\nFree: {:.1}G",
|
|
used_gb, total_gb, free_gb
|
|
)),
|
|
class: Some(class.to_string()),
|
|
percentage: Some(percentage as u8),
|
|
});
|
|
}
|
|
}
|
|
|
|
Err(FluxoError::Module {
|
|
module: "disk",
|
|
message: format!("Mountpoint {} not found", mountpoint),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::state::{AppState, DiskInfo, MockState, mock_state};
|
|
|
|
fn state_with_disk(mount: &str, total: u64, available: u64) -> MockState {
|
|
mock_state(AppState {
|
|
disks: vec![DiskInfo {
|
|
mount_point: mount.to_string(),
|
|
filesystem: "ext4".to_string(),
|
|
total_bytes: total,
|
|
available_bytes: available,
|
|
}],
|
|
..Default::default()
|
|
})
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_disk_found() {
|
|
let gb = 1024 * 1024 * 1024;
|
|
let state = state_with_disk("/", 100 * gb, 60 * gb);
|
|
let config = Config::default();
|
|
let output = DiskModule
|
|
.run(&config, &state.receivers, &["/"])
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(output.class.as_deref(), Some("normal"));
|
|
assert_eq!(output.percentage, Some(40)); // 40% used
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_disk_high() {
|
|
let gb = 1024 * 1024 * 1024;
|
|
let state = state_with_disk("/", 100 * gb, 15 * gb);
|
|
let config = Config::default();
|
|
let output = DiskModule
|
|
.run(&config, &state.receivers, &["/"])
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(output.class.as_deref(), Some("high")); // 85% used
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_disk_not_found() {
|
|
let state = state_with_disk("/", 100, 50);
|
|
let config = Config::default();
|
|
let result = DiskModule
|
|
.run(&config, &state.receivers, &["/nonexistent"])
|
|
.await;
|
|
assert!(result.is_err());
|
|
}
|
|
}
|