use crate::config; use crate::services::item_service::ItemService; use crate::services::types::ItemWithMeta; use crate::modes::common::ColumnType; use crate::modes::common::{size_column, string_column, OutputFormat}; use anyhow::{Result}; use log::debug; use comfy_table::{Table, ContentArrangement, Cell, Row, Color, Attribute}; use comfy_table::presets::NOTHING; use comfy_table::CellAlignment; use serde::{Deserialize, Serialize}; use serde_json; use serde_yaml; use std::io::{stdout, IsTerminal}; #[derive(Serialize, Deserialize)] struct ListItem { id: Option, time: String, size: Option, size_formatted: String, compression: String, file_size: Option, file_size_formatted: String, file_path: String, tags: Vec, meta: std::collections::HashMap, } pub fn mode_list( cmd: &mut clap::Command, settings: &config::Settings, ids: &mut Vec, tags: &Vec, conn: &mut rusqlite::Connection, data_path: std::path::PathBuf, ) -> Result<()> { if !ids.is_empty() { cmd.error( clap::error::ErrorKind::InvalidValue, "ID given, you can only supply tags when using --list", ) .exit(); } let item_service = ItemService::new(data_path.clone()); let items_with_meta = item_service.list_items(conn, tags, &std::collections::HashMap::new())?; let output_format = crate::modes::common::settings_output_format(settings); if output_format != OutputFormat::Table { return show_list_structured(items_with_meta, data_path, settings, output_format); } let mut table = Table::new(); table .load_preset(NOTHING) .set_content_arrangement(ContentArrangement::Dynamic); if !stdout().is_terminal() { table.force_no_tty(); } // Create header row let mut header_cells = Vec::new(); for column in &settings.list_format { header_cells.push(Cell::new(&column.label).add_attribute(Attribute::Bold)); } table.set_header(header_cells); for item_with_meta in items_with_meta { let tags: Vec = item_with_meta.tags.iter().map(|t| t.name.clone()).collect(); let meta = item_with_meta.meta_as_map(); let item = item_with_meta.item; let mut item_path = data_path.clone(); item_path.push(item.id.unwrap().to_string()); let mut table_row = Row::new(); for column in &settings.list_format { let column_type = ColumnType::from_str(&column.name) .unwrap_or_else(|_| panic!("Unknown column {:?}", column.name)); let mut meta_name: Option<&str> = None; if let ColumnType::Meta = column_type { let parts: Vec<&str> = column.name.split(':').collect(); if parts.len() > 1 { meta_name = Some(parts[1]); } } let mut cell = match column_type { ColumnType::Id => Cell::new(item.id.unwrap_or(0).to_string()), ColumnType::Time => Cell::new( item.ts .with_timezone(&chrono::Local) .format("%F %T") .to_string(), ), ColumnType::Size => match item.size { Some(size) => Cell::new(format_size( size as u64, settings.human_readable, )), None => match item_path.metadata() { Ok(_) => Cell::new("Unknown") .fg(Color::Yellow) .add_attribute(Attribute::Bold), Err(_) => Cell::new("Missing") .fg(Color::Red) .add_attribute(Attribute::Bold), }, }, ColumnType::Compression => Cell::new(item.compression.to_string()), ColumnType::FileSize => match item_path.metadata() { Ok(metadata) => Cell::new(format_size( metadata.len(), settings.human_readable, )), Err(_) => Cell::new("Missing") .fg(Color::Red) .add_attribute(Attribute::Bold), }, ColumnType::FilePath => Cell::new( item_path.clone().into_os_string().into_string().unwrap(), ), ColumnType::Tags => Cell::new(tags.join(" ")), ColumnType::Meta => match meta_name { Some(meta_name) => match meta.get(meta_name) { Some(meta_value) => Cell::new(meta_value.to_string()), None => Cell::new(""), }, None => Cell::new(""), }, }; // Apply alignment in one place cell = match column.align { crate::config::ColumnAlignment::Right => cell.set_alignment(CellAlignment::Right), crate::config::ColumnAlignment::Left => cell.set_alignment(CellAlignment::Left), }; table_row.add_cell(cell); } table.add_row(table_row); } println!("{}", table); Ok(()) } fn show_list_structured( items_with_meta: Vec, data_path: std::path::PathBuf, settings: &config::Settings, output_format: OutputFormat, ) -> Result<()> { let mut list_items = Vec::new(); for item_with_meta in items_with_meta { let tags: Vec = item_with_meta.tags.iter().map(|t| t.name.clone()).collect(); let meta = item_with_meta.meta_as_map(); let item = item_with_meta.item; let item_id = item.id.unwrap(); let mut item_path = data_path.clone(); item_path.push(item_id.to_string()); let file_size = item_path.metadata().map(|m| m.len()).ok(); let file_size_formatted = match file_size { Some(size) => crate::modes::common::format_size(size, settings.human_readable), None => "Missing".to_string(), }; let size_formatted = match item.size { Some(size) => crate::modes::common::format_size(size as u64, settings.human_readable), None => "Unknown".to_string(), }; let list_item = ListItem { id: item.id, time: item .ts .with_timezone(&chrono::Local) .format("%F %T") .to_string(), size: item.size.map(|s| s as u64), size_formatted, compression: item.compression, file_size, file_size_formatted, file_path: item_path.into_os_string().into_string().unwrap_or_default(), tags, meta, }; list_items.push(list_item); } match output_format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&list_items)?); } OutputFormat::Yaml => { println!("{}", serde_yaml::to_string(&list_items)?); } OutputFormat::Table => unreachable!(), } Ok(()) }