use crate::cli::{Cli, SortField};
use crate::config::Config;
use crate::disk::DiskInfo;
use anyhow::Result;
use colored::*;
use std::io;
pub use crate::cli::OutputFormat;
pub fn display_table(disk_info: &DiskInfo, cli: &Cli, config: &Config) {
let mut entries = disk_info.entries.clone();
if let Some(sort_field) = &cli.sort {
entries.sort_by(|a, b| {
let cmp = match sort_field {
SortField::Filesystem => a.filesystem.cmp(&b.filesystem),
SortField::Size => a.total.cmp(&b.total),
SortField::Used => a.used.cmp(&b.used),
SortField::Avail => a.available.cmp(&b.available),
SortField::Percent => a.percent.partial_cmp(&b.percent).unwrap(),
SortField::Mounted => a.mount_point.cmp(&b.mount_point),
};
if cli.reverse {
cmp.reverse()
} else {
cmp
}
});
}
let fs_width = entries
.iter()
.map(|e| e.filesystem.len())
.max()
.unwrap_or(10)
.max(10)
.min(30)
+ 2;
let mount_width = entries
.iter()
.map(|e| e.mount_point.display().to_string().len())
.max()
.unwrap_or(10)
.max(10)
.min(40)
+ 2;
let size_width = entries
.iter()
.map(|e| format_size(e.total, cli.human_readable).len())
.max()
.unwrap_or(10)
.max(10)
+ 2;
let used_width = entries
.iter()
.map(|e| format_size(e.used, cli.human_readable).len())
.max()
.unwrap_or(10)
.max(10)
+ 2;
let avail_width = entries
.iter()
.map(|e| format_size(e.available, cli.human_readable).len())
.max()
.unwrap_or(10)
.max(10)
+ 2;
let type_width = 6;
let percent_width = 5;
let _total_width = 1
+ 1
+ fs_width
+ 1
+ 1
+ type_width
+ 1
+ 1
+ size_width
+ 1
+ 1
+ used_width
+ 1
+ 1
+ avail_width
+ 1
+ 1
+ percent_width
+ 1
+ 1
+ mount_width
+ 1
+ 1;
let total_width = 2
+ fs_width
+ 3
+ type_width
+ 3
+ size_width
+ 3
+ used_width
+ 3
+ avail_width
+ 3
+ percent_width
+ 3
+ mount_width
+ 2;
let border_color = hex_to_rgb(&config.colors.border_color);
let top_border = format!("┌{}┐", "─".repeat(total_width - 2));
println!(
"{}",
top_border.truecolor(border_color.0, border_color.1, border_color.2)
);
let header_color = hex_to_rgb(&config.colors.header_color);
let header_line = format!(
"│ {:<fs$} │ {:<tp$} │ {:>sz$} │ {:>us$} │ {:>av$} │ {:>pc$} │ {:<mt$} │",
"Filesystem",
"Type",
"Size",
"Used",
"Avail",
"Use%",
"Mounted on",
fs = fs_width,
tp = type_width,
sz = size_width,
us = used_width,
av = avail_width,
pc = percent_width,
mt = mount_width
);
println!(
"{}",
header_line.truecolor(header_color.0, header_color.1, header_color.2)
);
let sep_line = format!("├{}┤", "─".repeat(total_width - 2));
println!(
"{}",
sep_line.truecolor(border_color.0, border_color.1, border_color.2)
);
for entry in &entries {
let fs_color = hex_to_rgb(&config.colors.filesystem_color);
let mount_color = hex_to_rgb(&config.colors.mount_color);
let avail_color = hex_to_rgb(&config.colors.avail_color);
let usage_color = if entry.percent >= 90.0 {
hex_to_rgb(&config.colors.high_usage_color)
} else if entry.percent >= 75.0 {
hex_to_rgb(&config.colors.warning_usage_color)
} else {
hex_to_rgb(&config.colors.normal_usage_color)
};
let percent_str = format!("{:.0}%", entry.percent);
let fs_display = entry.filesystem.clone();
let mount_display = entry.mount_point.display().to_string();
let size_display = format_size(entry.total, cli.human_readable);
let used_display = format_size(entry.used, cli.human_readable);
let avail_display = format_size(entry.available, cli.human_readable);
print!("│ ");
print!(
"{}",
format!("{:<fs$}", fs_display, fs = fs_width)
.truecolor(fs_color.0, fs_color.1, fs_color.2)
);
print!(" │ ");
print!("{:<tp$}", entry.disk_type, tp = type_width);
print!(" │ ");
print!("{:>sz$}", size_display, sz = size_width);
print!(" │ ");
print!("{:>us$}", used_display, us = used_width);
print!(" │ ");
print!(
"{}",
format!("{:>av$}", avail_display, av = avail_width).truecolor(
avail_color.0,
avail_color.1,
avail_color.2
)
);
print!(" │ ");
print!(
"{}",
format!("{:>pc$}", percent_str, pc = percent_width).truecolor(
usage_color.0,
usage_color.1,
usage_color.2
)
);
print!(" │ ");
print!(
"{}",
format!("{:<mt$}", mount_display, mt = mount_width).truecolor(
mount_color.0,
mount_color.1,
mount_color.2
)
);
println!(" │");
}
let bottom_border = format!("└{}┘", "─".repeat(total_width - 2));
println!(
"{}",
bottom_border.truecolor(border_color.0, border_color.1, border_color.2)
);
if cli.total {
if let Some(total) = &disk_info.total {
let total_size = format_size(total.total, cli.human_readable);
let total_used = format_size(total.used, cli.human_readable);
let total_avail = format_size(total.available, cli.human_readable);
println!();
let total_sep = "─".repeat(total_width - 2).to_string();
println!(
"{}",
total_sep.truecolor(border_color.0, border_color.1, border_color.2)
);
println!("{}", "Totals:".bold().truecolor(0, 255, 255));
println!(" Total Size: {}", total_size);
println!(" Total Used: {}", total_used);
println!(" Total Avail: {}", total_avail);
}
}
let timestamp = format!(
"Last updated: {}",
disk_info.collected_at.format("%Y-%m-%d %H:%M:%S")
);
println!();
println!("{}", timestamp.truecolor(128, 128, 128));
}
fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
let hex = hex.trim_start_matches('#');
if hex.len() != 6 {
return (255, 255, 255);
}
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255);
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(255);
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255);
(r, g, b)
}
pub fn display_json(disk_info: &DiskInfo) -> Result<()> {
let json = serde_json::to_string_pretty(&disk_info.entries)?;
println!("{}", json);
Ok(())
}
pub fn display_csv(disk_info: &DiskInfo) -> Result<()> {
let mut wtr = csv::Writer::from_writer(io::stdout());
for entry in &disk_info.entries {
wtr.serialize(entry)?;
}
wtr.flush()?;
Ok(())
}
pub fn display_inodes(disk_info: &DiskInfo, cli: &Cli, _config: &Config) {
for entry in &disk_info.entries {
println!(
"{:<20} {:>10} {:>10} {:>10} {:>6.0}% {:<20}",
entry.filesystem,
format_size(entry.total, cli.human_readable),
format_size(entry.used, cli.human_readable),
format_size(entry.available, cli.human_readable),
entry.percent,
entry.mount_point.display()
);
}
}
pub async fn watch_mode(
interval: u64,
human_readable: bool,
local_only: bool,
all: bool,
fs_type: Vec<String>,
exclude_type: Vec<String>,
block_size: u64,
config: Config,
) -> Result<()> {
let mut tick = tokio::time::interval(tokio::time::Duration::from_secs(interval));
loop {
tick.tick().await;
print!("\x1B[2J\x1B[1;1H");
let disk_info = crate::disk::DiskInfo::collect(
human_readable,
local_only,
all,
&fs_type,
&exclude_type,
block_size,
)?;
display_table(
&disk_info,
&Cli {
human_readable,
local_only,
all,
inodes: false,
output: OutputFormat::Table,
block_size,
fs_type: fs_type.clone(),
exclude_type: exclude_type.clone(),
sort: None,
reverse: false,
total: true,
sync: false,
watch: 0,
version: false,
mounts: vec![],
config: None,
},
&config,
);
}
}
fn format_size(size: u64, human_readable: bool) -> String {
if human_readable {
humansize::format_size(size, humansize::BINARY)
} else {
size.to_string()
}
}