use crate::Alignment; use crate::db::{get_items, get_items_matching}; use crate::modes::common::ColumnType; use crate::modes::common::{size_column, string_column}; use anyhow::anyhow; use log::debug; use prettytable::color; use prettytable::row; use prettytable::{Attr, Cell, Row, Table}; pub fn mode_list( cmd: &mut clap::Command, args: crate::Args, ids: &mut Vec, tags: &Vec, conn: &mut rusqlite::Connection, data_path: std::path::PathBuf, ) -> anyhow::Result<()> { if !ids.is_empty() { cmd.error( clap::error::ErrorKind::InvalidValue, "ID given, you can only supply tags when using --list", ) .exit(); } let mut meta: std::collections::HashMap = std::collections::HashMap::new(); for item in args.item.meta.iter() { let item = item.clone(); meta.insert(item.key, item.value); } let items = match tags.is_empty() && meta.is_empty() { true => get_items(conn)?, false => get_items_matching(conn, tags, &meta)?, }; debug!("MAIN: Items: {:?}", items); let mut tags_by_item: std::collections::HashMap> = std::collections::HashMap::new(); let mut meta_by_item: std::collections::HashMap< i64, std::collections::HashMap, > = std::collections::HashMap::new(); for item in items.iter() { let item_id = item.id.unwrap(); let item_tags: Vec = crate::db::get_item_tags(conn, item)? .into_iter() .map(|x| x.name) .collect(); tags_by_item.insert(item_id, item_tags); let mut item_meta: std::collections::HashMap = std::collections::HashMap::new(); for meta in crate::db::get_item_meta(conn, item)? { item_meta.insert(meta.name.clone(), meta.value); } meta_by_item.insert(item_id, item_meta); } let mut table = Table::new(); table.set_format(*prettytable::format::consts::FORMAT_CLEAN); let list_format = args.options.list_format.split(","); let mut title_row = row!(); for column in list_format.clone() { let mut column_format = column.split(":"); let column_name = column_format.next().expect("Unable to parse column name"); let column_type = ColumnType::from_str(column_name) .map_err(|_| anyhow!("Unknown column {:?}", column_name))?; if column_type == ColumnType::Meta { let meta_name = column_format .next() .expect("Unable to parse metadata name for meta column"); title_row.add_cell(Cell::new(meta_name).with_style(Attr::Bold)); } else { title_row.add_cell(Cell::new(&column_type.to_string()).with_style(Attr::Bold)); } } table.set_titles(title_row); for item in items { let item_id = item.id.unwrap(); let tags = tags_by_item.get(&item_id).unwrap(); let meta = meta_by_item.get(&item_id).unwrap(); let mut item_path = data_path.clone(); item_path.push(item.id.unwrap().to_string()); let mut table_row = Row::new(vec![]); for column in list_format.clone() { let mut column_format = column.split(":"); let column_name = column_format.next().expect("Unable to parse column name"); let column_type = ColumnType::from_str(column_name) .unwrap_or_else(|_| panic!("Unknown column {:?}", column_name)); let mut meta_name: Option<&str> = None; if column_type == ColumnType::Meta { meta_name = column_format.next(); } let column_width: usize = match column_format.next() { Some(len) => len.parse().unwrap_or(0), None => 0, }; let cell = match column_type { ColumnType::Id => Cell::new_align( &string_column(item.id.unwrap_or(0).to_string(), column_width), Alignment::RIGHT, ), ColumnType::Time => Cell::new(&string_column( item.ts .with_timezone(&chrono::Local) .format("%F %T") .to_string(), column_width, )), ColumnType::Size => match item.size { Some(size) => Cell::new_align( &size_column(size as u64, args.options.human_readable, column_width), Alignment::RIGHT, ), None => match item_path.metadata() { Ok(_) => Cell::new_align("Unknown", Alignment::RIGHT) .with_style(Attr::ForegroundColor(color::YELLOW)) .with_style(Attr::Bold), Err(_) => Cell::new_align("Missing", Alignment::RIGHT) .with_style(Attr::ForegroundColor(color::RED)) .with_style(Attr::Bold), }, }, ColumnType::Compression => { Cell::new(&string_column(item.compression.to_string(), column_width)) }, ColumnType::DigestType => { Cell::new(&string_column(item.digest_type.to_string(), column_width)) }, ColumnType::DigestValue => { match item.digest_value { Some(ref value) => Cell::new(&string_column(value.to_string(), column_width)), None => Cell::new("Missing") .with_style(Attr::ForegroundColor(color::RED)) .with_style(Attr::Bold), } }, ColumnType::FileSize => match item_path.metadata() { Ok(metadata) => Cell::new_align( &size_column( metadata.len(), args.options.human_readable, column_width, ), Alignment::RIGHT, ), Err(_) => Cell::new_align("Missing", Alignment::RIGHT) .with_style(Attr::ForegroundColor(color::RED)) .with_style(Attr::Bold), }, ColumnType::FilePath => Cell::new(&string_column( item_path.clone().into_os_string().into_string().unwrap(), column_width, )), ColumnType::Tags => Cell::new(&string_column(tags.join(" "), column_width)), ColumnType::Meta => match meta_name { Some(meta_name) => match meta.get(meta_name) { Some(meta_value) => { Cell::new(&string_column(meta_value.to_string(), column_width)) } None => Cell::new(""), }, None => Cell::new(""), }, }; table_row.add_cell(cell); } table.add_row(table_row); } table.printstd(); Ok(()) } // These helper functions are needed for the mode_list function