use std::fs;
use crate::modules::{
enums::{DiskDisplay, DiskSubtitle},
utils::get_bar,
};
pub fn get_disks(
subtitle_mode: DiskSubtitle,
display_mode: DiskDisplay,
paths: Option<Vec<&str>>,
) -> Option<Vec<(String, String)>> {
let mount_points = if let Some(ref user_paths) = paths {
user_paths.iter().map(|s| s.to_string()).collect()
} else {
get_default_mount_points()
};
let mut results = Vec::new();
for mount_point in mount_points {
if let Some(disk_info) = get_disk_info_for_path(&mount_point, subtitle_mode, &display_mode)
{
results.push(disk_info);
}
}
if results.is_empty() {
return None;
}
Some(results)
}
fn get_default_mount_points() -> Vec<String> {
let mut points = vec!["/".to_string()];
let mut seen_devices: std::collections::HashSet<String> = std::collections::HashSet::new();
if let Ok(content) = fs::read_to_string("/proc/mounts") {
let common_prefixes = ["/home", "/boot", "/var", "/usr", "/opt", "/data"];
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let mount = parts[0]; let mount_point = parts[1];
if seen_devices.contains(mount) {
continue;
}
if !mount_point.starts_with("/dev")
&& !mount_point.starts_with("/sys")
&& !mount_point.starts_with("/proc")
&& !mount_point.starts_with("/run")
&& !mount_point.starts_with("/snap")
{
for prefix in &common_prefixes {
if mount_point.starts_with(prefix) && mount_point != "/" {
seen_devices.insert(mount.to_string());
points.push(mount_point.to_string());
break;
}
}
}
}
}
}
points.truncate(5);
points
}
fn get_disk_info_for_path(
path: &str,
subtitle_mode: DiskSubtitle,
display_mode: &DiskDisplay,
) -> Option<(String, String)> {
let path_cstr = std::ffi::CString::new(path).ok()?;
let mut statfs: libc::statvfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statvfs(path_cstr.as_ptr(), &mut statfs) } != 0 {
return None;
}
let total = statfs.f_blocks as u64 * statfs.f_frsize as u64;
let available = statfs.f_bavail as u64 * statfs.f_frsize as u64;
let used = total.saturating_sub(available);
if total == 0 {
return None;
}
let percent = ((used as f64 / total as f64) * 100.0).round().clamp(0.0, 100.0) as u8;
let total_h = format_size(total);
let used_h = format_size(used);
let usage_display = format!("{} / {}", used_h, total_h);
let perc_val = percent.min(100);
let final_str = match display_mode {
DiskDisplay::Info => usage_display,
DiskDisplay::Percentage => format!("{}% {}", percent, get_bar(perc_val)),
DiskDisplay::InfoBar => format!("{} {}", usage_display, get_bar(perc_val)),
DiskDisplay::BarInfo => format!("{} {}", get_bar(perc_val), usage_display),
DiskDisplay::Bar => get_bar(perc_val),
};
let subtitle = match subtitle_mode {
DiskSubtitle::Name => get_device_name(path),
DiskSubtitle::Dir => path
.trim_start_matches('/')
.split('/')
.next()
.unwrap_or("")
.to_string(),
DiskSubtitle::None => "".to_string(),
DiskSubtitle::Mount => path.to_string(),
};
let full_subtitle = if subtitle.is_empty() {
"Disk".to_string()
} else {
format!("Disk ({})", subtitle)
};
Some((full_subtitle, final_str))
}
fn get_device_name(path: &str) -> String {
if let Ok(content) = fs::read_to_string("/proc/mounts") {
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 && parts[1] == path {
return parts[0].to_string();
}
}
}
"".to_string()
}
fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
const TB: u64 = GB * 1024;
if bytes >= TB {
format!("{:.1}T", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.1}G", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1}M", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1}K", bytes as f64 / KB as f64)
} else {
format!("{}B", bytes)
}
}