use std::{borrow::Cow, cmp::max, num::NonZeroU16}; use crate::{ app::AppConfigFields, canvas::{ components::data_table::{ ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortColumn, SortDataTable, SortDataTableProps, SortOrder, SortsRow, }, styling::CanvasStyling, }, utils::{data_prefixes::get_decimal_bytes, general::sort_partial_fn}, }; #[derive(Clone, Debug)] pub struct DiskWidgetData { pub name: Cow<'static, str>, pub mount_point: Cow<'static, str>, pub free_bytes: Option, pub used_bytes: Option, pub total_bytes: Option, pub summed_total_bytes: Option, pub io_read: Cow<'static, str>, pub io_write: Cow<'static, str>, } impl DiskWidgetData { fn total_space(&self) -> Cow<'static, str> { if let Some(total_bytes) = self.total_bytes { let converted_total_space = get_decimal_bytes(total_bytes); format!( "{:.*}{}", 0, converted_total_space.0, converted_total_space.1 ) .into() } else { "N/A".into() } } fn free_space(&self) -> Cow<'static, str> { if let Some(free_bytes) = self.free_bytes { let converted_free_space = get_decimal_bytes(free_bytes); format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into() } else { "N/A".into() } } fn used_space(&self) -> Cow<'static, str> { if let Some(used_bytes) = self.used_bytes { let converted_free_space = get_decimal_bytes(used_bytes); format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into() } else { "N/A".into() } } fn free_percent(&self) -> Option { if let (Some(free_bytes), Some(summed_total_bytes)) = (self.free_bytes, self.summed_total_bytes) { Some(free_bytes as f64 / summed_total_bytes as f64 * 100_f64) } else { None } } fn free_percent_string(&self) -> Cow<'static, str> { match self.free_percent() { Some(val) => format!("{val:.1}%").into(), None => "N/A".into(), } } fn used_percent(&self) -> Option { if let (Some(used_bytes), Some(summed_total_bytes)) = (self.used_bytes, self.summed_total_bytes) { if summed_total_bytes > 0 { Some(used_bytes as f64 / summed_total_bytes as f64 * 100_f64) } else { None } } else { None } } fn used_percent_string(&self) -> Cow<'static, str> { match self.used_percent() { Some(val) => format!("{val:.1}%").into(), None => "N/A".into(), } } } pub enum DiskWidgetColumn { Disk, Mount, Used, Free, Total, UsedPercent, FreePercent, IoRead, IoWrite, } impl ColumnHeader for DiskWidgetColumn { fn text(&self) -> Cow<'static, str> { match self { DiskWidgetColumn::Disk => "Disk(d)", DiskWidgetColumn::Mount => "Mount(m)", DiskWidgetColumn::Used => "Used(u)", DiskWidgetColumn::Free => "Free(n)", DiskWidgetColumn::UsedPercent => "Used%(p)", DiskWidgetColumn::FreePercent => "Free%", DiskWidgetColumn::Total => "Total(t)", DiskWidgetColumn::IoRead => "R/s(r)", DiskWidgetColumn::IoWrite => "W/s(w)", } .into() } } impl DataToCell for DiskWidgetData { fn to_cell( &self, column: &DiskWidgetColumn, _calculated_width: NonZeroU16, ) -> Option> { let text = match column { DiskWidgetColumn::Disk => self.name.clone(), DiskWidgetColumn::Mount => self.mount_point.clone(), DiskWidgetColumn::Used => self.used_space(), DiskWidgetColumn::Free => self.free_space(), DiskWidgetColumn::UsedPercent => self.used_percent_string(), DiskWidgetColumn::FreePercent => self.free_percent_string(), DiskWidgetColumn::Total => self.total_space(), DiskWidgetColumn::IoRead => self.io_read.clone(), DiskWidgetColumn::IoWrite => self.io_write.clone(), }; Some(text) } fn column_widths>( data: &[Self], _columns: &[C], ) -> Vec where Self: Sized, { let mut widths = vec![0; 7]; data.iter().for_each(|row| { widths[0] = max(widths[0], row.name.len() as u16); widths[1] = max(widths[1], row.mount_point.len() as u16); }); widths } } pub struct DiskTableWidget { pub table: SortDataTable, pub force_update_data: bool, } impl SortsRow for DiskWidgetColumn { type DataType = DiskWidgetData; fn sort_data(&self, data: &mut [Self::DataType], descending: bool) { match self { DiskWidgetColumn::Disk => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.name, &b.name)); } DiskWidgetColumn::Mount => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.mount_point, &b.mount_point)); } DiskWidgetColumn::Used => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.used_bytes, &b.used_bytes)); } DiskWidgetColumn::UsedPercent => { data.sort_by(|a, b| { sort_partial_fn(descending)(&a.used_percent(), &b.used_percent()) }); } DiskWidgetColumn::Free => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.free_bytes, &b.free_bytes)); } DiskWidgetColumn::FreePercent => { data.sort_by(|a, b| { sort_partial_fn(descending)(&a.free_percent(), &b.free_percent()) }); } DiskWidgetColumn::Total => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.total_bytes, &b.total_bytes)); } DiskWidgetColumn::IoRead => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_read, &b.io_read)); } DiskWidgetColumn::IoWrite => { data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_write, &b.io_write)); } } } } impl DiskTableWidget { pub fn new(config: &AppConfigFields, colours: &CanvasStyling) -> Self { let columns = [ SortColumn::soft(DiskWidgetColumn::Disk, Some(0.2)), SortColumn::soft(DiskWidgetColumn::Mount, Some(0.2)), SortColumn::hard(DiskWidgetColumn::Used, 8).default_descending(), SortColumn::hard(DiskWidgetColumn::Free, 8).default_descending(), SortColumn::hard(DiskWidgetColumn::Total, 9).default_descending(), SortColumn::hard(DiskWidgetColumn::UsedPercent, 9).default_descending(), SortColumn::hard(DiskWidgetColumn::IoRead, 10).default_descending(), SortColumn::hard(DiskWidgetColumn::IoWrite, 11).default_descending(), ]; let props = SortDataTableProps { inner: DataTableProps { title: Some(" Disks ".into()), table_gap: config.table_gap, left_to_right: true, is_basic: config.use_basic_mode, show_table_scroll_position: config.show_table_scroll_position, show_current_entry_when_unfocused: false, }, sort_index: 0, order: SortOrder::Ascending, }; let styling = DataTableStyling::from_colours(colours); Self { table: SortDataTable::new_sortable(columns, props, styling), force_update_data: false, } } /// Forces an update of the data stored. #[inline] pub fn force_data_update(&mut self) { self.force_update_data = true; } /// Update the current table data. pub fn set_table_data(&mut self, data: &[DiskWidgetData]) { let mut data = data.to_vec(); if let Some(column) = self.table.columns.get(self.table.sort_index()) { column.sort_by(&mut data, self.table.order()); } self.table.set_data(data); } pub fn set_index(&mut self, index: usize) { self.table.set_sort_index(index); self.force_data_update(); } }