style: reorder imports and reformat code for consistency

This commit is contained in:
Andrew Phillips
2025-05-10 10:06:33 -03:00
committed by Andrew Phillips (aider)
parent c936326ac3
commit 9feec61759
13 changed files with 492 additions and 356 deletions

View File

@@ -1,22 +1,21 @@
use std::io;
use std::io::{Read, Write, BufWriter};
use std::fs;
use std::str::FromStr;
use std::path::PathBuf;
use std::collections::HashMap;
use std::os::fd::FromRawFd;
use std::process::Stdio; // For Stdio::null, Stdio::piped
use nix::fcntl::{FdFlag};
use nix::fcntl::FdFlag;
use nix::unistd::{close, pipe};
use nix::Error as NixError;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::io::{BufWriter, Read, Write};
use std::os::fd::FromRawFd;
use std::path::PathBuf;
use std::process::Stdio; // For Stdio::null, Stdio::piped
use std::str::FromStr;
use anyhow::{Context, Result, Error, anyhow};
use rusqlite::Connection;
use gethostname::gethostname;
use anyhow::{anyhow, Context, Error, Result};
use clap::error::ErrorKind;
use clap::*;
use gethostname::gethostname;
use log::*;
use rusqlite::Connection;
mod modes;
use crate::modes::common::*;
@@ -24,12 +23,12 @@ extern crate directories;
use directories::ProjectDirs;
extern crate prettytable;
use prettytable::{Table, Row, Cell, Attr};
use prettytable::format;
use prettytable::format::{TableFormat, Alignment};
use prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR;
use prettytable::row;
use prettytable::color;
use prettytable::format;
use prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR;
use prettytable::format::{Alignment, TableFormat};
use prettytable::row;
use prettytable::{Attr, Cell, Row, Table};
use chrono::prelude::*;
@@ -42,36 +41,29 @@ pub mod db;
use compression::CompressionType;
use is_terminal::IsTerminal;
extern crate term;
lazy_static! {
static ref FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR: TableFormat = format::FormatBuilder::new()
.column_separator('│')
.borders('│')
.separators(&[format::LinePosition::Top],
format::LineSeparator::new(
'─',
'┬',
'┌',
'┐'))
.separators(&[format::LinePosition::Title],
format::LineSeparator::new(
'─',
'┼',
'├',
''))
.separators(&[format::LinePosition::Bottom],
format::LineSeparator::new(
'─',
'┴',
'└',
'┘'))
.padding(1, 1)
.build();
static ref FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR: TableFormat =
format::FormatBuilder::new()
.column_separator('│')
.borders('│')
.separators(
&[format::LinePosition::Top],
format::LineSeparator::new('─', '┬', '┌', '┐')
)
.separators(
&[format::LinePosition::Title],
format::LineSeparator::new('─', '┼', '├', '┤')
)
.separators(
&[format::LinePosition::Bottom],
format::LineSeparator::new('', '┴', '└', '┘')
)
.padding(1, 1)
.build();
}
#[derive(Parser, Debug)]
@@ -85,10 +77,9 @@ pub struct Args {
options: OptionsArgs,
#[arg(help("A list of either item IDs or tags"))]
ids_or_tags: Vec<NumberOrString>
ids_or_tags: Vec<NumberOrString>,
}
#[derive(Parser, Debug)]
struct ModeArgs {
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["get", "diff", "list", "update", "delete", "info", "status"]))]
@@ -96,7 +87,9 @@ struct ModeArgs {
save: bool,
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "diff", "list", "update", "delete", "info", "status"]))]
#[arg(help("Get an item either by it's ID or by a combination of matching tags and metatdata"))]
#[arg(help(
"Get an item either by it's ID or by a combination of matching tags and metatdata"
))]
get: bool,
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "list", "update", "delete", "info", "status"]))]
@@ -116,34 +109,38 @@ struct ModeArgs {
delete: bool,
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "get", "diff", "list", "update", "delete", "status"]), requires("ids_or_tags"))]
#[arg(help("Get an item either by it's ID or by a combination of matching tags and metatdata"))]
#[arg(help(
"Get an item either by it's ID or by a combination of matching tags and metatdata"
))]
info: bool,
#[arg(group("mode"), help_heading("Mode Options"), short('S'), long, conflicts_with_all(["save", "get", "diff", "list", "update", "delete", "info"]))]
#[arg(help("Show status of directories and supported compression algorithms"))]
status: bool
status: bool,
}
#[derive(Parser, Debug)]
struct ItemArgs {
#[arg(help_heading("Item Options"), short, long, conflicts_with_all(["get", "delete", "status"]))]
#[arg(help("Set metadata for the item using the format KEY=[VALUE], the metadata will be removed if VALUE is not provided"))]
meta: Vec<KeyValue>,
#[arg(help_heading("Item Options"), short, long, env("KEEP_COMPRESSION"), )]
#[arg(help_heading("Item Options"), short, long, env("KEEP_COMPRESSION"))]
#[arg(help("Compression algorithm to use when saving items"))]
compression: Option<String>,
}
#[derive(Parser, Debug)]
struct OptionsArgs {
#[arg(long, env("KEEP_DIR"))]
#[arg(help("Specify the directory to use for storage"))]
dir: Option<PathBuf>,
#[arg(long, env("KEEP_LIST_FORMAT"), default_value("id,time,size,tags,meta:hostname"))]
#[arg(
long,
env("KEEP_LIST_FORMAT"),
default_value("id,time,size,tags,meta:hostname")
)]
#[arg(help("A comma separated list of columns to display with --list"))]
list_format: String,
@@ -160,8 +157,7 @@ struct OptionsArgs {
quiet: bool,
}
#[derive(Debug,PartialEq)]
#[derive(Debug, PartialEq)]
enum KeepModes {
Unknown,
Save,
@@ -171,13 +167,13 @@ enum KeepModes {
Update,
Delete,
Info,
Status
Status,
}
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
struct KeyValue {
key: String,
value: String
value: String,
}
impl FromStr for KeyValue {
@@ -186,15 +182,14 @@ impl FromStr for KeyValue {
match s.split_once('=') {
Some(kv) => Ok(KeyValue {
key: kv.0.to_string(),
value: kv.1.to_string()
value: kv.1.to_string(),
}),
None => Err(anyhow!("Unable to parse key=value pair"))
None => Err(anyhow!("Unable to parse key=value pair")),
}
}
}
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
enum NumberOrString {
Number(i64),
Str(String),
@@ -219,7 +214,7 @@ pub enum ColumnType {
FileSize,
FilePath,
Tags,
Meta
Meta,
}
fn main() -> Result<(), Error> {
@@ -272,7 +267,7 @@ fn main() -> Result<(), Error> {
}
if mode == KeepModes::Unknown {
if ! ids.is_empty() {
if !ids.is_empty() {
mode = KeepModes::Get;
} else {
mode = KeepModes::Save;
@@ -287,7 +282,7 @@ fn main() -> Result<(), Error> {
if args.options.dir.is_none() {
match proj_dirs {
Some(proj_dirs) => args.options.dir = Some(proj_dirs.data_dir().to_path_buf()),
None => return Err(anyhow!("Unable to determine data directory"))
None => return Err(anyhow!("Unable to determine data directory")),
}
}
@@ -302,45 +297,65 @@ fn main() -> Result<(), Error> {
debug!("MAIN: Data directory: {:?}", data_path);
debug!("MAIN: DB file: {:?}", db_path);
fs::create_dir_all(data_path.clone())
.context("Problem creating data directory")?;
fs::create_dir_all(data_path.clone()).context("Problem creating data directory")?;
debug!("MAIN: Data directory created or already exists");
let mut conn = db::open(db_path.clone())
.context("Problem opening database")?;
let mut conn = db::open(db_path.clone()).context("Problem opening database")?;
debug!("MAIN: DB opened successfully");
match mode {
KeepModes::Save => mode_save(&mut cmd, args, ids, tags, conn, data_path)?,
KeepModes::Get => crate::modes::get::mode_get(&mut cmd, args, ids, tags, &mut conn, data_path)?,
KeepModes::Get => {
crate::modes::get::mode_get(&mut cmd, args, ids, tags, &mut conn, data_path)?
}
KeepModes::Diff => mode_diff(&mut cmd, args, ids, tags, &mut conn, data_path)?,
KeepModes::List => mode_list(&mut cmd, args, ids, tags, &mut conn, data_path)?,
KeepModes::Update => crate::modes::update::mode_update(&mut cmd, args, ids, tags, &mut conn, data_path)?,
KeepModes::Update => {
crate::modes::update::mode_update(&mut cmd, args, ids, tags, &mut conn, data_path)?
}
KeepModes::Info => mode_info(&mut cmd, args, ids, tags, &mut conn, data_path)?,
KeepModes::Delete => crate::modes::delete::mode_delete(&mut cmd, args, ids, tags, &mut conn, data_path)?,
KeepModes::Delete => {
crate::modes::delete::mode_delete(&mut cmd, args, ids, tags, &mut conn, data_path)?
}
KeepModes::Status => crate::modes::status::mode_status(&mut cmd, args, data_path, db_path)?,
_ => todo!()
_ => todo!(),
}
Ok(())
}
fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<String>, conn: Connection, data_path: PathBuf) -> Result<()> {
if ! ids.is_empty() {
cmd.error(ErrorKind::InvalidValue, "ID given, you cannot supply IDs when using --save").exit();
fn mode_save(
cmd: &mut Command,
args: Args,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: Connection,
data_path: PathBuf,
) -> Result<()> {
if !ids.is_empty() {
cmd.error(
ErrorKind::InvalidValue,
"ID given, you cannot supply IDs when using --save",
)
.exit();
}
if tags.is_empty() {
tags.push("none".to_string());
}
let compression_name = args.item.compression.unwrap_or(compression::default_type().to_string());
let compression_name = args
.item
.compression
.unwrap_or(compression::default_type().to_string());
let compression_type_opt = CompressionType::from_str(&compression_name);
if compression_type_opt.is_err() {
cmd.error(ErrorKind::InvalidValue, format!("Unknown compression type: {}", compression_name)).exit();
cmd.error(
ErrorKind::InvalidValue,
format!("Unknown compression type: {}", compression_name),
)
.exit();
}
let compression_type = compression_type_opt.unwrap();
@@ -350,14 +365,14 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
id: None,
ts: Utc::now(),
size: None,
compression: compression_type.to_string()
compression: compression_type.to_string(),
};
let id = db::insert_item(&conn, item.clone())?;
item.id = Some(id);
debug!("MAIN: Added item {:?}", item.clone());
if ! args.options.quiet {
if !args.options.quiet {
if std::io::stderr().is_terminal() {
let mut t = term::stderr().unwrap();
t.reset().unwrap_or(());
@@ -385,7 +400,7 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
let mut item_meta: HashMap<String, String> = get_meta_from_env();
if let Ok(hostname) = gethostname().into_string() {
if ! item_meta.contains_key("hostname") {
if !item_meta.contains_key("hostname") {
item_meta.insert("hostname".to_string(), hostname);
}
}
@@ -399,7 +414,7 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
let meta = db::Meta {
id: item.id.unwrap(),
name: kv.0.to_string(),
value: kv.1.to_string()
value: kv.1.to_string(),
};
db::store_meta(&conn, meta)?;
}
@@ -411,9 +426,16 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
let mut stdout = io::stdout().lock();
let mut buffer = [0; libc::BUFSIZ as usize];
let compression_engine = compression::get_engine(compression_type.clone()).expect("Unable to get compression engine");
let mut item_out: Box<dyn Write> = compression_engine.create(item_path.clone())
.context(anyhow!("Unable to write file {:?} using compression {:?}", item_path, compression_type))?;
let compression_engine = compression::get_engine(compression_type.clone())
.expect("Unable to get compression engine");
let mut item_out: Box<dyn Write> =
compression_engine
.create(item_path.clone())
.context(anyhow!(
"Unable to write file {:?} using compression {:?}",
item_path,
compression_type
))?;
debug!("MAIN: Starting IO loop");
loop {
@@ -428,7 +450,7 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
item_out.write_all(&buffer[..n])?;
item.size = match item.size {
None => Some(n as i64),
Some(prev_n) => Some(prev_n + n as i64)
Some(prev_n) => Some(prev_n + n as i64),
};
}
debug!("MAIN: Ending IO loop");
@@ -441,7 +463,6 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
Ok(())
}
fn mode_diff(
cmd: &mut clap::Command,
_args: Args, // Mark as unused if not needed directly
@@ -455,14 +476,14 @@ fn mode_diff(
ErrorKind::InvalidValue,
"Tags are not supported with --diff. Please provide exactly two IDs.",
)
.exit();
.exit();
}
if ids.len() != 2 {
cmd.error(
ErrorKind::InvalidValue,
"You must supply exactly two IDs when using --diff.",
)
.exit();
.exit();
}
// Fetch items, ensuring they exist.
@@ -476,15 +497,14 @@ fn mode_diff(
let item_a_tags: Vec<String> = db::get_item_tags(conn, &item_a)?
.into_iter()
.map(|x| {x.name})
.map(|x| x.name)
.collect();
let item_b_tags: Vec<String> = db::get_item_tags(conn, &item_b)?
.into_iter()
.map(|x| {x.name})
.map(|x| x.name)
.collect();
let mut item_path_a = data_path.clone();
item_path_a.push(item_a.id.unwrap().to_string()); // id.unwrap() is safe due to ok_or_else
let compression_type_a = CompressionType::from_str(&item_a.compression)?;
@@ -505,21 +525,34 @@ fn mode_diff(
// it's good practice if the raw FDs were to be handled further before that.
// For this specific code, since from_raw_fd takes ownership immediately, this is less critical
// but doesn't hurt.
nix::fcntl::fcntl(fd_a_write, nix::fcntl::FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC))
.map_err(|e| anyhow!("Failed to set FD_CLOEXEC on fd_a_write: {}", e))?;
nix::fcntl::fcntl(fd_b_write, nix::fcntl::FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC))
.map_err(|e| anyhow!("Failed to set FD_CLOEXEC on fd_b_write: {}", e))?;
nix::fcntl::fcntl(
fd_a_write,
nix::fcntl::FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC),
)
.map_err(|e| anyhow!("Failed to set FD_CLOEXEC on fd_a_write: {}", e))?;
nix::fcntl::fcntl(
fd_b_write,
nix::fcntl::FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC),
)
.map_err(|e| anyhow!("Failed to set FD_CLOEXEC on fd_b_write: {}", e))?;
debug!("MAIN: Creating child process for diff");
let mut diff_command = std::process::Command::new("diff");
diff_command
.arg("-u")
.arg("--label")
.arg(format!("Keep item A: {} {}", item_a.id.unwrap(), item_a_tags.join(" ")))
.arg(format!(
"Keep item A: {} {}",
item_a.id.unwrap(),
item_a_tags.join(" ")
))
.arg(format!("/dev/fd/{}", fd_a_read))
.arg("--label")
.arg(format!("Keep item B: {} {}", item_b.id.unwrap(), item_b_tags.join(" ")))
.arg(format!(
"Keep item B: {} {}",
item_b.id.unwrap(),
item_b_tags.join(" ")
))
.arg(format!("/dev/fd/{}", fd_b_read))
.stdin(Stdio::null())
.stdout(Stdio::piped())
@@ -632,21 +665,35 @@ fn mode_diff(
let diff_status = child_process
.wait()
.map_err(|e| anyhow!("Failed to wait on diff command: {}", e))?;
debug!("MAIN: Diff child process finished with status: {}", diff_status);
debug!(
"MAIN: Diff child process finished with status: {}",
diff_status
);
// Retrieve the captured output from the reader threads.
// .join().unwrap() here will panic if the reader thread itself panicked.
// The inner Result is from the read_to_end operation within the thread.
let stdout_capture_result = stdout_reader_thread.join().unwrap_or_else(|panic_payload| {
Err(anyhow!("Stdout reader thread panicked: {:?}", panic_payload))
})?;
let stderr_capture_result = stderr_reader_thread.join().unwrap_or_else(|panic_payload| {
Err(anyhow!("Stderr reader thread panicked: {:?}", panic_payload))
})?;
let stdout_capture_result = stdout_reader_thread
.join()
.unwrap_or_else(|panic_payload| {
Err(anyhow!(
"Stdout reader thread panicked: {:?}",
panic_payload
))
})?;
let stderr_capture_result = stderr_reader_thread
.join()
.unwrap_or_else(|panic_payload| {
Err(anyhow!(
"Stderr reader thread panicked: {:?}",
panic_payload
))
})?;
// Handle diff's exit status and output
match diff_status.code() {
Some(0) => { // Exit code 0: No differences
Some(0) => {
// Exit code 0: No differences
debug!("MAIN: Diff successful, no differences found.");
// Typically, diff -u doesn't print to stdout if no differences.
// But if it did, it would be shown here.
@@ -654,11 +701,13 @@ fn mode_diff(
println!("{}", String::from_utf8_lossy(&stdout_capture_result));
}
}
Some(1) => { // Exit code 1: Differences found
Some(1) => {
// Exit code 1: Differences found
debug!("MAIN: Diff successful, differences found.");
println!("{}", String::from_utf8_lossy(&stdout_capture_result));
}
Some(error_code) => { // Exit code > 1: Error in diff utility
Some(error_code) => {
// Exit code > 1: Error in diff utility
eprintln!("Diff command failed with exit code: {}", error_code);
if !stdout_capture_result.is_empty() {
eprintln!(
@@ -677,7 +726,8 @@ fn mode_diff(
error_code
));
}
None => { // Process terminated by a signal
None => {
// Process terminated by a signal
eprintln!("Diff command terminated by signal.");
if !stderr_capture_result.is_empty() {
eprintln!(
@@ -692,10 +742,20 @@ fn mode_diff(
Ok(())
}
fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<String>, conn: &mut Connection, data_path: PathBuf) -> Result<()> {
if ! ids.is_empty() {
cmd.error(ErrorKind::InvalidValue, "ID given, you can only supply tags when using --list").exit();
fn mode_list(
cmd: &mut Command,
args: Args,
ids: &mut Vec<i64>,
tags: &Vec<String>,
conn: &mut Connection,
data_path: PathBuf,
) -> Result<()> {
if !ids.is_empty() {
cmd.error(
ErrorKind::InvalidValue,
"ID given, you can only supply tags when using --list",
)
.exit();
}
let mut meta: HashMap<String, String> = HashMap::new();
@@ -706,7 +766,7 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
let items = match tags.is_empty() && meta.is_empty() {
true => db::get_items(conn)?,
false => db::get_items_matching(conn, tags, &meta)?
false => db::get_items_matching(conn, tags, &meta)?,
};
debug!("MAIN: Items: {:?}", items);
@@ -719,7 +779,7 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
let item_tags: Vec<String> = db::get_item_tags(conn, item)?
.into_iter()
.map(|x| {x.name})
.map(|x| x.name)
.collect();
tags_by_item.insert(item_id, item_tags);
@@ -729,7 +789,7 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
item_meta.insert(meta.name.clone(), meta.value);
}
meta_by_item.insert(item_id, item_meta);
};
}
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_CLEAN);
@@ -741,10 +801,13 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
for column in list_format.clone() {
let mut column_format = column.split(":").into_iter();
let column_name = column_format.next().expect("Unable to parse column name");
let column_type = ColumnType::from_str(column_name).expect(format!("Unknown column {:?}", column_name).as_str());
let column_type = ColumnType::from_str(column_name)
.expect(format!("Unknown column {:?}", column_name).as_str());
if column_type == ColumnType::Meta {
let meta_name = column_format.next().expect("Unable to parse metadata name for meta column");
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));
@@ -765,7 +828,8 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
for column in list_format.clone() {
let mut column_format = column.split(":").into_iter();
let column_name = column_format.next().expect("Unable to parse column name");
let column_type = ColumnType::from_str(column_name).expect(format!("Unknown column {:?}", column_name).as_str());
let column_type = ColumnType::from_str(column_name)
.expect(format!("Unknown column {:?}", column_name).as_str());
let mut meta_name: Option<&str> = None;
if column_type == ColumnType::Meta {
@@ -774,45 +838,62 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
let column_width: usize = match column_format.next() {
Some(len) => len.parse().unwrap_or(0),
None => 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(&Local).format("%F %T").to_string(), column_width)),
Alignment::RIGHT,
),
ColumnType::Time => Cell::new(&string_column(
item.ts.with_timezone(&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),
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)
}
.with_style(Attr::Bold),
},
},
ColumnType::Compression => Cell::new(&string_column(item.compression.to_string(), column_width)),
ColumnType::Compression => {
Cell::new(&string_column(item.compression.to_string(), column_width))
}
ColumnType::FileSize => match item_path.metadata() {
Ok(metadata) => Cell::new_align(
&size_column(metadata.len() as u64, args.options.human_readable, column_width),
Alignment::RIGHT),
&size_column(
metadata.len() as u64,
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)
.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::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("")
Some(meta_value) => {
Cell::new(&string_column(meta_value.to_string(), column_width))
}
None => Cell::new(""),
},
None => Cell::new("")
}
None => Cell::new(""),
},
};
table_row.add_cell(cell);
}
@@ -824,9 +905,15 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
Ok(())
}
fn mode_info(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<String>, conn: &mut Connection, data_path: PathBuf) -> Result<()> {
if ! ids.is_empty() && ! tags.is_empty() {
fn mode_info(
cmd: &mut Command,
args: Args,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: &mut Connection,
data_path: PathBuf,
) -> Result<()> {
if !ids.is_empty() && !tags.is_empty() {
cmd.error(ErrorKind::InvalidValue, "Both ID and tags given, you must supply exactly one ID or atleast one tag when using --info").exit();
} else if ids.len() > 1 {
cmd.error(ErrorKind::InvalidValue, "More than one ID given, you must supply exactly one ID or atleast one tag when using --info").exit();
@@ -841,22 +928,20 @@ fn mode_info(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
let item_maybe = match tags.is_empty() && meta.is_empty() {
true => match ids.iter().next() {
Some(item_id) => db::get_item(conn, *item_id)?,
None => db::get_item_last(conn)?
None => db::get_item_last(conn)?,
},
false => db::get_item_matching(conn, tags, &meta)?
false => db::get_item_matching(conn, tags, &meta)?,
};
if let Some(item) = item_maybe {
debug!("MAIN: Found item {:?}", item);
let item_id = item.id.unwrap();
let item_tags: Vec<String> = db::get_item_tags(conn, &item)?
.into_iter()
.map(|x| {x.name})
.map(|x| x.name)
.collect();
let mut table = Table::new();
if std::io::stdout().is_terminal() {
table.set_format(*FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR);
@@ -866,75 +951,82 @@ fn mode_info(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
table.add_row(Row::new(vec![
Cell::new("ID").with_style(Attr::Bold),
Cell::new(&item_id.to_string())
Cell::new(&item_id.to_string()),
]));
let ts_cell = Cell::new(&item.ts.with_timezone(&Local).format("%F %T %Z").to_string());
table.add_row(Row::new(vec![
Cell::new("Timestamp").with_style(Attr::Bold),
ts_cell
ts_cell,
]));
let mut item_path = data_path.clone();
item_path.push(item.id.unwrap().to_string());
table.add_row(Row::new(vec![
Cell::new("Path").with_style(Attr::Bold),
Cell::new(item_path.to_str().expect("Unable to get item path"))
Cell::new(item_path.to_str().expect("Unable to get item path")),
]));
let size_cell = match item.size {
Some(size) => Cell::new(format_size(size as u64, args.options.human_readable).as_str()),
None => Cell::new("Missing").with_style(Attr::ForegroundColor(color::RED)).with_style(Attr::Bold)
None => Cell::new("Missing")
.with_style(Attr::ForegroundColor(color::RED))
.with_style(Attr::Bold),
};
table.add_row(Row::new(vec![
Cell::new("Stream Size").with_style(Attr::Bold),
size_cell
size_cell,
]));
let compression_type = CompressionType::from_str(&item.compression)?;
table.add_row(Row::new(vec![
Cell::new("Compression").with_style(Attr::Bold),
Cell::new(&compression_type.to_string())
Cell::new(&compression_type.to_string()),
]));
let file_size_cell = match item_path.metadata() {
Ok(metadata) => Cell::new(format_size(metadata.len(), args.options.human_readable).as_str()),
Err(_) => Cell::new("Missing").with_style(Attr::ForegroundColor(color::RED)).with_style(Attr::Bold)
Ok(metadata) => {
Cell::new(format_size(metadata.len(), args.options.human_readable).as_str())
}
Err(_) => Cell::new("Missing")
.with_style(Attr::ForegroundColor(color::RED))
.with_style(Attr::Bold),
};
table.add_row(Row::new(vec![
Cell::new("File Size").with_style(Attr::Bold),
file_size_cell
file_size_cell,
]));
let compression_engine = compression::get_engine(compression_type).expect("Unable to get compression engine");
let compression_engine =
compression::get_engine(compression_type).expect("Unable to get compression engine");
let magic = compression_engine.magic(item_path.clone());
let file_magic_cell = match magic {
Ok(magic) => Cell::new(magic.as_str()),
Err(e) => Cell::new(&e.to_string()).with_style(Attr::ForegroundColor(color::RED)).with_style(Attr::Bold)
Err(e) => Cell::new(&e.to_string())
.with_style(Attr::ForegroundColor(color::RED))
.with_style(Attr::Bold),
};
table.add_row(Row::new(vec![
Cell::new("File Magic").with_style(Attr::Bold),
file_magic_cell
file_magic_cell,
]));
table.add_row(Row::new(vec![
Cell::new("Tags").with_style(Attr::Bold),
Cell::new(&item_tags.join(" "))
Cell::new(&item_tags.join(" ")),
]));
for meta in db::get_item_meta(conn, &item)? {
let meta_name = format!("Meta: {}", &meta.name);
table.add_row(Row::new(vec![
Cell::new(meta_name.as_str()).with_style(Attr::Bold),
Cell::new(&meta.value)
Cell::new(&meta.value),
]));
}