use core::{fmt, panic};
use std::collections::HashMap;
use std::sync::Arc;
use crate::formatters::{bytes_human_readable, time_human_readable};
use crate::table::{
OptionalUint64Column, StringColumn, TableRow, Uint64Column,
};
use crate::{FileObject, Table, TableCallback, TableColumn, TableColumnValue};
pub struct FileObjectTable {
columns: Vec<(String, Box<dyn TableColumn>)>, column_index: HashMap<String, usize>, callback: Option<Arc<dyn TableCallback>>,
}
impl FileObjectTable {
pub fn new(selected_columns: &Option<Vec<&str>>) -> Self {
let mut table = Self {
columns: Vec::new(),
column_index: HashMap::new(),
callback: None,
};
let valid_columns = vec!["name", "size", "modified"];
if let Some(columns) = selected_columns {
for &column in columns {
match column {
"name" => table
.add_column("name", Box::new(StringColumn(Vec::new()))),
"size" => table
.add_column("size", Box::new(Uint64Column(Vec::new()))),
"modified" => table.add_column(
"modified",
Box::new(OptionalUint64Column(Vec::new())),
),
_ => panic!("Invalid column name: {}", column),
}
}
} else {
for &column in &valid_columns {
match column {
"name" => table
.add_column("name", Box::new(StringColumn(Vec::new()))),
"size" => table
.add_column("size", Box::new(Uint64Column(Vec::new()))),
"modified" => table.add_column(
"modified",
Box::new(OptionalUint64Column(Vec::new())),
),
_ => panic!("Invalid column name: {}", column),
}
}
}
table
}
}
impl Table for FileObjectTable {
fn len(&self) -> usize {
if self.columns.is_empty() {
0
} else {
self.columns[0].1.len()
}
}
fn add_column(&mut self, name: &str, column_type: Box<dyn TableColumn>) {
let index = self.columns.len();
self.columns.push((name.to_string(), column_type));
self.column_index.insert(name.to_string(), index);
}
fn set_callback(&mut self, callback: Arc<dyn TableCallback>) {
self.callback = Some(callback);
}
fn add_row(
&mut self,
row_data: Vec<(String, TableColumnValue)>,
) -> Result<(), String> {
if let Some(callback) = &self.callback {
let mut row = TableRow::new(row_data.clone(), Some(&print_row));
callback.on_row_add(&mut row);
}
for (column_name, value) in row_data {
if let Some(&index) = self.column_index.get(&column_name) {
let (_, column) = &mut self.columns[index];
column.append(value)?;
} else {
return Err(format!("Column '{}' not found", column_name));
}
}
Ok(())
}
fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Table")
.field("callback", &"Callback Omitted")
.finish()?;
f.write_str("columns: {\n")?;
for (name, column) in &self.columns {
write!(f, " {}: ", name)?;
write!(f, "{:?}", column)?;
f.write_str(",\n")?;
}
f.write_str("}\n")
}
}
fn print_row(row: &TableRow) {
let full_path = true;
let row_data = row.data();
let name = row_data
.iter()
.find(|(key, _)| key == "name")
.map(|(_, value)| match value {
TableColumnValue::StringColumn(val)
| TableColumnValue::OptionalStringColumn(Some(val)) => val,
_ => panic!("name column not found or not a string"),
})
.expect("name not found")
.to_string();
let fsize = row_data
.iter()
.find(|(key, _)| key == "size")
.map(|(_, value)| extract_u64_value(value))
.flatten()
.unwrap_or(0);
let modified = row_data
.iter()
.find(|(key, _)| key == "modified")
.map(|(_, value)| extract_u64_value(value))
.flatten();
let name_without_trailing_slash = name.trim_end_matches('/');
let mut name_to_print = if full_path {
name_without_trailing_slash.to_string()
} else {
name_without_trailing_slash
.split('/')
.last()
.unwrap_or(name_without_trailing_slash)
.to_string()
};
if name.ends_with('/') {
name_to_print.push('/');
}
println!(
"{}",
format!(
"{:8} {} {}",
bytes_human_readable(fsize),
if let Some(mtime) = modified {
time_human_readable(mtime)
} else {
"PRE".to_string()
},
name_to_print
)
);
}
impl FileObjectTable {
pub async fn add_file_objects(
&mut self,
file_objects: Vec<FileObject>,
) -> Result<(), String> {
for file_object in file_objects {
let mut row_data: Vec<(String, TableColumnValue)> = Vec::new();
for (column_name, _) in &self.columns {
if let Some(value) =
file_object.get_value_by_column_name(column_name)
{
row_data.push((column_name.clone(), value));
} else {
panic!("Column not found: {}", column_name);
}
}
self.add_row(row_data)?;
}
Ok(())
}
pub async fn add_rows(
&mut self,
rows: Vec<HashMap<String, TableColumnValue>>,
) -> Result<(), String> {
for row_data in rows {
let mut row_vec: Vec<(String, TableColumnValue)> = Vec::new();
for (column_name, _) in &self.columns {
if let Some(value) = row_data.get(column_name) {
row_vec.push((column_name.clone(), value.clone()));
} else {
panic!("Column not found: {}", column_name);
}
}
self.add_row(row_vec)?;
}
Ok(())
}
}
impl fmt::Debug for FileObjectTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_debug(f)
}
}
fn extract_u64_value(modified: &TableColumnValue) -> Option<u64> {
match modified {
TableColumnValue::Uint64Column(value) => Some(*value),
TableColumnValue::OptionalUint64Column(Some(value)) => Some(*value),
TableColumnValue::OptionalUint64Column(None) => None,
_ => panic!(
"Unexpected column type; expected Uint64Column or \
OptionalUint64Column"
),
}
}