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,9 +1,9 @@
use anyhow::{anyhow,Result,Context}; use anyhow::{anyhow, Context, Result};
use strum::IntoEnumIterator;
use std::path::PathBuf;
use std::io::{Read,Write};
use std::io; use std::io;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use strum::IntoEnumIterator;
use log::*; use log::*;
@@ -11,17 +11,16 @@ use lazy_static::lazy_static;
extern crate enum_map; extern crate enum_map;
use enum_map::enum_map; use enum_map::enum_map;
use enum_map::{EnumMap,Enum}; use enum_map::{Enum, EnumMap};
pub mod none;
pub mod lz4;
pub mod gzip; pub mod gzip;
pub mod lz4;
pub mod none;
pub mod program; pub mod program;
use none::CompressionEngineNone;
use lz4::CompressionEngineLZ4;
use gzip::CompressionEngineGZip; use gzip::CompressionEngineGZip;
use lz4::CompressionEngineLZ4;
use none::CompressionEngineNone;
use program::CompressionEngineProgram; use program::CompressionEngineProgram;
#[derive(Debug, Eq, PartialEq, Clone, strum::EnumIter, strum::Display, strum::EnumString, Enum)] #[derive(Debug, Eq, PartialEq, Clone, strum::EnumIter, strum::Display, strum::EnumString, Enum)]
@@ -32,10 +31,9 @@ pub enum CompressionType {
BZip2, BZip2,
XZ, XZ,
ZStd, ZStd,
None None,
} }
pub trait CompressionEngine { pub trait CompressionEngine {
fn open(&self, file_path: PathBuf) -> Result<Box<dyn Read>>; fn open(&self, file_path: PathBuf) -> Result<Box<dyn Read>>;
fn create(&self, file_path: PathBuf) -> Result<Box<dyn Write>>; fn create(&self, file_path: PathBuf) -> Result<Box<dyn Write>>;
@@ -76,7 +74,11 @@ pub trait CompressionEngine {
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.context(anyhow!("Unable to spawn child process: {:?} {:?}", program, args))?; .context(anyhow!(
"Unable to spawn child process: {:?} {:?}",
program,
args
))?;
let mut stdin = process.stdin.unwrap(); let mut stdin = process.stdin.unwrap();
stdin.write_all(&buffer[0..buffer_size])?; stdin.write_all(&buffer[0..buffer_size])?;
@@ -106,7 +108,6 @@ pub trait CompressionEngine {
} }
} }
lazy_static! { lazy_static! {
pub static ref COMPRESSION_PROGRAMS: EnumMap<CompressionType, Option<CompressionEngineProgram>> = enum_map! { pub static ref COMPRESSION_PROGRAMS: EnumMap<CompressionType, Option<CompressionEngineProgram>> = enum_map! {
CompressionType::LZ4 => None, CompressionType::LZ4 => None,
@@ -127,17 +128,19 @@ lazy_static! {
}; };
} }
pub fn get_engine(compression_type: CompressionType) -> Result<Box<dyn CompressionEngine>> { pub fn get_engine(compression_type: CompressionType) -> Result<Box<dyn CompressionEngine>> {
match compression_type { match compression_type {
CompressionType::LZ4 => Ok(Box::new(CompressionEngineLZ4::new())), CompressionType::LZ4 => Ok(Box::new(CompressionEngineLZ4::new())),
CompressionType::GZip => Ok(Box::new(CompressionEngineGZip::new())), CompressionType::GZip => Ok(Box::new(CompressionEngineGZip::new())),
CompressionType::None => Ok(Box::new(CompressionEngineNone::new())), CompressionType::None => Ok(Box::new(CompressionEngineNone::new())),
compression_type => Ok(Box::new(COMPRESSION_PROGRAMS[compression_type.clone()].clone().unwrap())) compression_type => Ok(Box::new(
COMPRESSION_PROGRAMS[compression_type.clone()]
.clone()
.unwrap(),
)),
} }
} }
pub fn default_type() -> CompressionType { pub fn default_type() -> CompressionType {
let mut default = CompressionType::None; let mut default = CompressionType::None;
for compression_type in CompressionType::iter() { for compression_type in CompressionType::iter() {

View File

@@ -1,13 +1,13 @@
use anyhow::Result; use anyhow::Result;
use log::*;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use log::*;
use flate2::Compression;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;
use flate2::Compression;
use crate::compression::CompressionEngine; use crate::compression::CompressionEngine;
@@ -40,7 +40,6 @@ impl CompressionEngine for CompressionEngineGZip {
Ok(Box::new(AutoFinishGzEncoder::new(gzip_write))) Ok(Box::new(AutoFinishGzEncoder::new(gzip_write)))
} }
} }
pub struct AutoFinishGzEncoder<W: Write> { pub struct AutoFinishGzEncoder<W: Write> {

View File

@@ -1,16 +1,15 @@
use anyhow::Result; use anyhow::Result;
use std::fs::File;
use std::path::PathBuf;
use std::io::{Read, Write};
use log::*; use log::*;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use lz4_flex::frame::{FrameDecoder, FrameEncoder}; use lz4_flex::frame::{FrameDecoder, FrameEncoder};
use crate::compression::CompressionEngine; use crate::compression::CompressionEngine;
#[derive(Debug, Eq, PartialEq, Clone, Default)] #[derive(Debug, Eq, PartialEq, Clone, Default)]
pub struct CompressionEngineLZ4 { pub struct CompressionEngineLZ4 {}
}
impl CompressionEngineLZ4 { impl CompressionEngineLZ4 {
pub fn new() -> CompressionEngineLZ4 { pub fn new() -> CompressionEngineLZ4 {
@@ -38,6 +37,4 @@ impl CompressionEngine for CompressionEngineLZ4 {
Ok(Box::new(lz4_write)) Ok(Box::new(lz4_write))
} }
} }

View File

@@ -1,8 +1,8 @@
use anyhow::Result; use anyhow::Result;
use std::fs::File;
use std::path::PathBuf;
use std::io::{Read, Write};
use log::*; use log::*;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use crate::compression::CompressionEngine; use crate::compression::CompressionEngine;
@@ -30,5 +30,4 @@ impl CompressionEngine for CompressionEngineNone {
debug!("COMPRESSION: Writting to {:?} using {:?}", file_path, *self); debug!("COMPRESSION: Writting to {:?} using {:?}", file_path, *self);
Ok(Box::new(File::create(file_path)?)) Ok(Box::new(File::create(file_path)?))
} }
} }

View File

@@ -1,35 +1,37 @@
use anyhow::{Context, Result, anyhow}; use anyhow::{anyhow, Context, Result};
use std::fs::File; use log::*;
use std::process::{Command,Stdio};
use std::path::PathBuf;
use std::env; use std::env;
use std::fs; use std::fs;
use std::os::unix::fs::PermissionsExt; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use log::*; use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::compression::CompressionEngine; use crate::compression::CompressionEngine;
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub struct CompressionEngineProgram { pub struct CompressionEngineProgram {
pub program: String, pub program: String,
pub compress: Vec<String>, pub compress: Vec<String>,
pub decompress: Vec<String>, pub decompress: Vec<String>,
pub supported: bool pub supported: bool,
} }
impl CompressionEngineProgram { impl CompressionEngineProgram {
pub fn new(program: &str, compress: Vec<&str>, decompress: Vec<&str>) -> CompressionEngineProgram { pub fn new(
program: &str,
compress: Vec<&str>,
decompress: Vec<&str>,
) -> CompressionEngineProgram {
let program_path = get_program_path(program); let program_path = get_program_path(program);
let supported = program_path.is_ok(); let supported = program_path.is_ok();
CompressionEngineProgram { CompressionEngineProgram {
program: program_path.unwrap_or(program.to_string()), program: program_path.unwrap_or(program.to_string()),
compress: compress.iter().map(|s| {s.to_string()}).collect(), compress: compress.iter().map(|s| s.to_string()).collect(),
decompress: decompress.iter().map(|s| {s.to_string()}).collect(), decompress: decompress.iter().map(|s| s.to_string()).collect(),
supported supported,
} }
} }
} }
@@ -45,7 +47,10 @@ impl CompressionEngine for CompressionEngineProgram {
let program = self.program.clone(); let program = self.program.clone();
let args = self.decompress.clone(); let args = self.decompress.clone();
debug!("COMPRESSION: Executing command: {:?} {:?} reading from {:?}", program, args, file_path); debug!(
"COMPRESSION: Executing command: {:?} {:?} reading from {:?}",
program, args, file_path
);
let file = File::open(file_path).context("Unable to open file for reading")?; let file = File::open(file_path).context("Unable to open file for reading")?;
@@ -54,7 +59,11 @@ impl CompressionEngine for CompressionEngineProgram {
.stdin(file) .stdin(file)
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.context(anyhow!("Unable to spawn child process: {:?} {:?}", program, args))?; .context(anyhow!(
"Unable to spawn child process: {:?} {:?}",
program,
args
))?;
Ok(Box::new(process.stdout.unwrap())) Ok(Box::new(process.stdout.unwrap()))
} }
@@ -64,7 +73,10 @@ impl CompressionEngine for CompressionEngineProgram {
let program = self.program.clone(); let program = self.program.clone();
let args = self.compress.clone(); let args = self.compress.clone();
debug!("COMPRESSION: Executing command: {:?} {:?} writing to {:?}", program, args, file_path); debug!(
"COMPRESSION: Executing command: {:?} {:?} writing to {:?}",
program, args, file_path
);
let file = File::create(file_path).context("Unable to open file for writing")?; let file = File::create(file_path).context("Unable to open file for writing")?;
@@ -73,11 +85,14 @@ impl CompressionEngine for CompressionEngineProgram {
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(file) .stdout(file)
.spawn() .spawn()
.context(anyhow!("Problem spawning child process: {:?} {:?}", program, args))?; .context(anyhow!(
"Problem spawning child process: {:?} {:?}",
program,
args
))?;
Ok(Box::new(process.stdin.unwrap())) Ok(Box::new(process.stdin.unwrap()))
} }
} }
fn get_program_path(program: &str) -> Result<String> { fn get_program_path(program: &str) -> Result<String> {

157
src/db.rs
View File

@@ -1,31 +1,37 @@
use std::path::PathBuf; use anyhow::{Context, Error, Result};
use std::collections::HashMap;
use std::rc::Rc;
use anyhow::{Context, Result, Error};
use rusqlite::{Connection, OpenFlags};
use rusqlite_migration::{Migrations, M};
use chrono::prelude::*; use chrono::prelude::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::*; use log::*;
use rusqlite::{Connection, OpenFlags};
use rusqlite_migration::{Migrations, M};
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
lazy_static! { lazy_static! {
static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![ static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![
M::up("CREATE TABLE items( M::up(
"CREATE TABLE items(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
ts TEXT NOT NULL, ts TEXT NOT NULL,
size INTEGER NULL, size INTEGER NULL,
compression TEXT NOT NULL)"), compression TEXT NOT NULL)"
M::up("CREATE TABLE tags ( ),
M::up(
"CREATE TABLE tags (
id INTEGER NOT NULL, id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE,
PRIMARY KEY(id, name));"), PRIMARY KEY(id, name));"
M::up("CREATE TABLE metas ( ),
M::up(
"CREATE TABLE metas (
id INTEGER NOT NULL, id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
value TEXT NOT NULL, value TEXT NOT NULL,
FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE,
PRIMARY KEY(id, name));") PRIMARY KEY(id, name));"
)
]); ]);
} }
@@ -34,71 +40,72 @@ pub struct Item {
pub id: Option<i64>, pub id: Option<i64>,
pub ts: DateTime<Utc>, pub ts: DateTime<Utc>,
pub size: Option<i64>, pub size: Option<i64>,
pub compression: String pub compression: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Tag { pub struct Tag {
pub id: i64, pub id: i64,
pub name: String pub name: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Meta { pub struct Meta {
pub id: i64, pub id: i64,
pub name: String, pub name: String,
pub value: String pub value: String,
} }
pub fn open(path: PathBuf) -> Result<Connection, Error> { pub fn open(path: PathBuf) -> Result<Connection, Error> {
debug!("DB: Opening file: {:?}", path); debug!("DB: Opening file: {:?}", path);
let mut conn = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE) let mut conn = Connection::open_with_flags(
path,
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
)
.context("Problem opening file")?; .context("Problem opening file")?;
conn.pragma_update(None, "foreign_keys", "ON") conn.pragma_update(None, "foreign_keys", "ON")
.context("Problem enabling SQLite foreign_keys pragma")?; .context("Problem enabling SQLite foreign_keys pragma")?;
MIGRATIONS.to_latest(&mut conn) MIGRATIONS
.to_latest(&mut conn)
.context("Problem performing database migrations")?; .context("Problem performing database migrations")?;
rusqlite::vtab::array::load_module(&conn) rusqlite::vtab::array::load_module(&conn).context("Problem enabling array module")?;
.context("Problem enabling array module")?;
Ok(conn) Ok(conn)
} }
pub fn insert_item(conn: &Connection, item: Item) -> Result<i64> { pub fn insert_item(conn: &Connection, item: Item) -> Result<i64> {
debug!("DB: Inserting item: {:?}", item); debug!("DB: Inserting item: {:?}", item);
conn.execute( conn.execute(
"INSERT INTO items (ts, size, compression) VALUES (?1, ?2, ?3)", "INSERT INTO items (ts, size, compression) VALUES (?1, ?2, ?3)",
(item.ts, item.size, item.compression))?; (item.ts, item.size, item.compression),
)?;
Ok(conn.last_insert_rowid()) Ok(conn.last_insert_rowid())
} }
pub fn update_item(conn: &Connection, item: Item) -> Result<()> { pub fn update_item(conn: &Connection, item: Item) -> Result<()> {
debug!("DB: Updating item: {:?}", item); debug!("DB: Updating item: {:?}", item);
conn.execute( conn.execute(
"UPDATE items SET size=?2, compression=?3 WHERE id=?1", "UPDATE items SET size=?2, compression=?3 WHERE id=?1",
(item.id, item.size, item.compression))?; (item.id, item.size, item.compression),
)?;
Ok(()) Ok(())
} }
pub fn delete_item(conn: &Connection, item: Item) -> Result<()> { pub fn delete_item(conn: &Connection, item: Item) -> Result<()> {
debug!("DB: Deleting item: {:?}", item); debug!("DB: Deleting item: {:?}", item);
conn.execute("DELETE FROM items WHERE id=?1", [item.id])?; conn.execute("DELETE FROM items WHERE id=?1", [item.id])?;
Ok(()) Ok(())
} }
pub fn query_delete_meta(conn: &Connection, meta: Meta) -> Result<()> { pub fn query_delete_meta(conn: &Connection, meta: Meta) -> Result<()> {
debug!("DB: Deleting meta: {:?}", meta); debug!("DB: Deleting meta: {:?}", meta);
conn.execute( conn.execute(
"DELETE FROM metas WHERE id=?1 AND name=?2", "DELETE FROM metas WHERE id=?1 AND name=?2",
(meta.id, meta.name))?; (meta.id, meta.name),
)?;
Ok(()) Ok(())
} }
@@ -107,7 +114,8 @@ pub fn query_upsert_meta(conn: &Connection, meta: Meta) -> Result<()> {
conn.execute( conn.execute(
"INSERT INTO metas (id, name, value) VALUES (?1, ?2, ?3) "INSERT INTO metas (id, name, value) VALUES (?1, ?2, ?3)
ON CONFLICT(id, name) DO UPDATE SET value=?3", ON CONFLICT(id, name) DO UPDATE SET value=?3",
(meta.id, meta.name, meta.value))?; (meta.id, meta.name, meta.value),
)?;
Ok(()) Ok(())
} }
@@ -121,41 +129,38 @@ pub fn store_meta(conn: &Connection, meta: Meta) -> Result<()> {
Ok(()) Ok(())
} }
pub fn insert_tag(conn: &Connection, tag: Tag) -> Result<()> { pub fn insert_tag(conn: &Connection, tag: Tag) -> Result<()> {
debug!("DB: Inserting tag: {:?}", tag); debug!("DB: Inserting tag: {:?}", tag);
conn.execute( conn.execute(
"INSERT INTO tags (id, name) VALUES (?1, ?2)", "INSERT INTO tags (id, name) VALUES (?1, ?2)",
(tag.id, tag.name))?; (tag.id, tag.name),
)?;
Ok(()) Ok(())
} }
pub fn delete_item_tags(conn: &Connection, item: Item) -> Result<()> { pub fn delete_item_tags(conn: &Connection, item: Item) -> Result<()> {
debug!("DB: Deleting all item tags: {:?}", item); debug!("DB: Deleting all item tags: {:?}", item);
conn.execute( conn.execute("DELETE FROM tags WHERE id=?1", [item.id])?;
"DELETE FROM tags WHERE id=?1",
[item.id])?;
Ok(()) Ok(())
} }
pub fn set_item_tags(conn: &Connection, item: Item, tags: &Vec<String>) -> Result<()> { pub fn set_item_tags(conn: &Connection, item: Item, tags: &Vec<String>) -> Result<()> {
debug!("DB: Setting tags for item: {:?} ?{:?}", item, tags); debug!("DB: Setting tags for item: {:?} ?{:?}", item, tags);
delete_item_tags(conn, item.clone())?; delete_item_tags(conn, item.clone())?;
let item_id = item.id.unwrap(); let item_id = item.id.unwrap();
for tag_name in tags { for tag_name in tags {
insert_tag(conn, insert_tag(
conn,
Tag { Tag {
id: item_id, id: item_id,
name: tag_name.to_string() name: tag_name.to_string(),
})?; },
)?;
} }
Ok(()) Ok(())
} }
pub fn query_all_items(conn: &Connection) -> Result<Vec<Item>> { pub fn query_all_items(conn: &Connection) -> Result<Vec<Item>> {
debug!("DB: Querying all items"); debug!("DB: Querying all items");
let mut statement = conn let mut statement = conn
@@ -169,7 +174,7 @@ pub fn query_all_items(conn: &Connection) -> Result<Vec<Item>> {
id: row.get(0)?, id: row.get(0)?,
ts: row.get(1)?, ts: row.get(1)?,
size: row.get(2)?, size: row.get(2)?,
compression: row.get(3)? compression: row.get(3)?,
}; };
items.push(item); items.push(item);
} }
@@ -180,7 +185,8 @@ pub fn query_all_items(conn: &Connection) -> Result<Vec<Item>> {
pub fn query_tagged_items<'a>(conn: &'a Connection, tags: &'a Vec<String>) -> Result<Vec<Item>> { pub fn query_tagged_items<'a>(conn: &'a Connection, tags: &'a Vec<String>) -> Result<Vec<Item>> {
debug!("DB: Querying tagged items: {:?}", tags); debug!("DB: Querying tagged items: {:?}", tags);
let mut statement = conn let mut statement = conn
.prepare_cached(" .prepare_cached(
"
SELECT items.id, SELECT items.id,
items.ts, items.ts,
items.size, items.size,
@@ -191,12 +197,13 @@ pub fn query_tagged_items<'a>(conn: &'a Connection, tags: &'a Vec<String>) -> Re
WHERE items.id = tags_match.id WHERE items.id = tags_match.id
GROUP BY items.id GROUP BY items.id
HAVING tags_score = ?2 HAVING tags_score = ?2
ORDER BY items.id ASC") ORDER BY items.id ASC",
)
.context("Problem preparing SQL statement")?; .context("Problem preparing SQL statement")?;
let tags_values: Vec<rusqlite::types::Value> = tags let tags_values: Vec<rusqlite::types::Value> = tags
.iter() .iter()
.map(|s| {rusqlite::types::Value::from(s.clone())}) .map(|s| rusqlite::types::Value::from(s.clone()))
.collect(); .collect();
let tags_ptr = Rc::new(tags_values); let tags_ptr = Rc::new(tags_values);
@@ -209,7 +216,7 @@ pub fn query_tagged_items<'a>(conn: &'a Connection, tags: &'a Vec<String>) -> Re
id: row.get(0)?, id: row.get(0)?,
ts: row.get(1)?, ts: row.get(1)?,
size: row.get(2)?, size: row.get(2)?,
compression: row.get(3)? compression: row.get(3)?,
}; };
items.push(item); items.push(item);
} }
@@ -217,20 +224,24 @@ pub fn query_tagged_items<'a>(conn: &'a Connection, tags: &'a Vec<String>) -> Re
Ok(items) Ok(items)
} }
pub fn get_items(conn: &Connection) -> Result<Vec<Item>> { pub fn get_items(conn: &Connection) -> Result<Vec<Item>> {
debug!("DB: Getting all items"); debug!("DB: Getting all items");
query_all_items(conn) query_all_items(conn)
} }
pub fn get_items_matching(
pub fn get_items_matching(conn: &Connection, tags: &Vec<String>, meta: &HashMap<String,String>) -> Result<Vec<Item>> { conn: &Connection,
debug!("DB: Getting items matching: tags={:?} meta={:?}", tags, meta); tags: &Vec<String>,
meta: &HashMap<String, String>,
) -> Result<Vec<Item>> {
debug!(
"DB: Getting items matching: tags={:?} meta={:?}",
tags, meta
);
let items = match tags.is_empty() { let items = match tags.is_empty() {
true => query_all_items(conn)?, true => query_all_items(conn)?,
false => query_tagged_items(conn, tags)? false => query_tagged_items(conn, tags)?,
}; };
if meta.is_empty() { if meta.is_empty() {
@@ -251,7 +262,7 @@ pub fn get_items_matching(conn: &Connection, tags: &Vec<String>, meta: &HashMap<
for (k, v) in meta.iter() { for (k, v) in meta.iter() {
match item_meta.get(k) { match item_meta.get(k) {
Some(value) => item_ok = v.eq(value), Some(value) => item_ok = v.eq(value),
None => item_ok = false None => item_ok = false,
} }
if item_ok { if item_ok {
@@ -265,14 +276,17 @@ pub fn get_items_matching(conn: &Connection, tags: &Vec<String>, meta: &HashMap<
} }
Ok(filtered_items) Ok(filtered_items)
} }
} }
pub fn get_item_matching(
pub fn get_item_matching(conn: &Connection, tags: &Vec<String>, _meta: &HashMap<String, String>) -> Result<Option<Item>> { conn: &Connection,
tags: &Vec<String>,
_meta: &HashMap<String, String>,
) -> Result<Option<Item>> {
debug!("DB: Get item matching tags: {:?}", tags); debug!("DB: Get item matching tags: {:?}", tags);
let mut statement = conn let mut statement = conn
.prepare_cached(" .prepare_cached(
"
SELECT items.id, SELECT items.id,
items.ts, items.ts,
items.size, items.size,
@@ -284,12 +298,13 @@ pub fn get_item_matching(conn: &Connection, tags: &Vec<String>, _meta: &HashMap<
GROUP BY items.id GROUP BY items.id
HAVING score = ?2 HAVING score = ?2
ORDER BY items.id DESC ORDER BY items.id DESC
LIMIT 1") LIMIT 1",
)
.context("Problem preparing SQL statement")?; .context("Problem preparing SQL statement")?;
let tags_values: Vec<rusqlite::types::Value> = tags let tags_values: Vec<rusqlite::types::Value> = tags
.iter() .iter()
.map(|s| {rusqlite::types::Value::from(s.clone()) }) .map(|s| rusqlite::types::Value::from(s.clone()))
.collect(); .collect();
let tags_ptr = Rc::new(tags_values); let tags_ptr = Rc::new(tags_values);
@@ -301,20 +316,21 @@ pub fn get_item_matching(conn: &Connection, tags: &Vec<String>, _meta: &HashMap<
id: row.get(0)?, id: row.get(0)?,
ts: row.get(1)?, ts: row.get(1)?,
size: row.get(2)?, size: row.get(2)?,
compression: row.get(3)? compression: row.get(3)?,
})), })),
None => Ok(None) None => Ok(None),
} }
} }
pub fn get_item(conn: &Connection, item_id: i64) -> Result<Option<Item>> { pub fn get_item(conn: &Connection, item_id: i64) -> Result<Option<Item>> {
debug!("DB: Getting item {:?}", item_id); debug!("DB: Getting item {:?}", item_id);
let mut statement = conn let mut statement = conn
.prepare_cached(" .prepare_cached(
"
SELECT id, ts, size, compression SELECT id, ts, size, compression
FROM items FROM items
WHERE items.id = ?1") WHERE items.id = ?1",
)
.context("Problem preparing SQL statement")?; .context("Problem preparing SQL statement")?;
let mut rows = statement.query([item_id])?; let mut rows = statement.query([item_id])?;
@@ -324,20 +340,22 @@ pub fn get_item(conn: &Connection, item_id: i64) -> Result<Option<Item>> {
id: row.get(0)?, id: row.get(0)?,
ts: row.get(1)?, ts: row.get(1)?,
size: row.get(2)?, size: row.get(2)?,
compression: row.get(3)? compression: row.get(3)?,
})), })),
None => Ok(None) None => Ok(None),
} }
} }
pub fn get_item_last(conn: &Connection) -> Result<Option<Item>> { pub fn get_item_last(conn: &Connection) -> Result<Option<Item>> {
debug!("DB: Getting last item"); debug!("DB: Getting last item");
let mut statement = conn let mut statement = conn
.prepare_cached(" .prepare_cached(
"
SELECT id, ts, size, compression SELECT id, ts, size, compression
FROM items FROM items
ORDER BY id DESC ORDER BY id DESC
LIMIT 1") LIMIT 1",
)
.context("Problem preparing SQL statement")?; .context("Problem preparing SQL statement")?;
let mut rows = statement.query([])?; let mut rows = statement.query([])?;
@@ -347,9 +365,9 @@ pub fn get_item_last(conn: &Connection) -> Result<Option<Item>> {
id: row.get(0)?, id: row.get(0)?,
ts: row.get(1)?, ts: row.get(1)?,
size: row.get(2)?, size: row.get(2)?,
compression: row.get(3)? compression: row.get(3)?,
})), })),
None => Ok(None) None => Ok(None),
} }
} }
@@ -372,7 +390,6 @@ pub fn get_item_tags(conn: &Connection, item: &Item) -> Result<Vec<Tag>> {
Ok(tags) Ok(tags)
} }
pub fn get_item_meta(conn: &Connection, item: &Item) -> Result<Vec<Meta>> { pub fn get_item_meta(conn: &Connection, item: &Item) -> Result<Vec<Meta>> {
debug!("DB: Getting item meta: {:?}", item); debug!("DB: Getting item meta: {:?}", item);
let mut statement = conn let mut statement = conn
@@ -386,7 +403,7 @@ pub fn get_item_meta(conn: &Connection, item: &Item) -> Result<Vec<Meta>> {
metas.push(Meta { metas.push(Meta {
id: row.get(0)?, id: row.get(0)?,
name: row.get(1)?, name: row.get(1)?,
value: row.get(2)? value: row.get(2)?,
}); });
} }

View File

@@ -1,22 +1,21 @@
use std::io; use nix::fcntl::FdFlag;
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::unistd::{close, pipe}; use nix::unistd::{close, pipe};
use nix::Error as NixError; 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::{anyhow, Context, Error, Result};
use anyhow::{Context, Result, Error, anyhow};
use rusqlite::Connection;
use gethostname::gethostname;
use clap::error::ErrorKind; use clap::error::ErrorKind;
use clap::*; use clap::*;
use gethostname::gethostname;
use log::*; use log::*;
use rusqlite::Connection;
mod modes; mod modes;
use crate::modes::common::*; use crate::modes::common::*;
@@ -24,12 +23,12 @@ extern crate directories;
use directories::ProjectDirs; use directories::ProjectDirs;
extern crate prettytable; 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::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::*; use chrono::prelude::*;
@@ -42,34 +41,27 @@ pub mod db;
use compression::CompressionType; use compression::CompressionType;
use is_terminal::IsTerminal; use is_terminal::IsTerminal;
extern crate term; extern crate term;
lazy_static! { lazy_static! {
static ref FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR: TableFormat = format::FormatBuilder::new() static ref FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR: TableFormat =
format::FormatBuilder::new()
.column_separator('│') .column_separator('│')
.borders('│') .borders('│')
.separators(&[format::LinePosition::Top], .separators(
format::LineSeparator::new( &[format::LinePosition::Top],
'─', format::LineSeparator::new('─', '┬', '┌', '┐')
'┬', )
'┌', .separators(
'┐')) &[format::LinePosition::Title],
.separators(&[format::LinePosition::Title], format::LineSeparator::new('─', '┼', '├', '┤')
format::LineSeparator::new( )
'─', .separators(
'┼', &[format::LinePosition::Bottom],
'├', format::LineSeparator::new('─', '┴', '└', '┘')
'┤')) )
.separators(&[format::LinePosition::Bottom],
format::LineSeparator::new(
'─',
'┴',
'└',
'┘'))
.padding(1, 1) .padding(1, 1)
.build(); .build();
} }
@@ -85,10 +77,9 @@ pub struct Args {
options: OptionsArgs, options: OptionsArgs,
#[arg(help("A list of either item IDs or tags"))] #[arg(help("A list of either item IDs or tags"))]
ids_or_tags: Vec<NumberOrString> ids_or_tags: Vec<NumberOrString>,
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct ModeArgs { struct ModeArgs {
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["get", "diff", "list", "update", "delete", "info", "status"]))] #[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, save: bool,
#[arg(group("mode"), help_heading("Mode Options"), short, long, conflicts_with_all(["save", "diff", "list", "update", "delete", "info", "status"]))] #[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, get: bool,
#[arg(group("mode"), help_heading("Mode Options"), long, conflicts_with_all(["save", "get", "list", "update", "delete", "info", "status"]))] #[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, 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(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, info: bool,
#[arg(group("mode"), help_heading("Mode Options"), short('S'), long, conflicts_with_all(["save", "get", "diff", "list", "update", "delete", "info"]))] #[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"))] #[arg(help("Show status of directories and supported compression algorithms"))]
status: bool status: bool,
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct ItemArgs { struct ItemArgs {
#[arg(help_heading("Item Options"), short, long, conflicts_with_all(["get", "delete", "status"]))] #[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"))] #[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>, 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"))] #[arg(help("Compression algorithm to use when saving items"))]
compression: Option<String>, compression: Option<String>,
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct OptionsArgs { struct OptionsArgs {
#[arg(long, env("KEEP_DIR"))] #[arg(long, env("KEEP_DIR"))]
#[arg(help("Specify the directory to use for storage"))] #[arg(help("Specify the directory to use for storage"))]
dir: Option<PathBuf>, 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"))] #[arg(help("A comma separated list of columns to display with --list"))]
list_format: String, list_format: String,
@@ -160,7 +157,6 @@ struct OptionsArgs {
quiet: bool, quiet: bool,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum KeepModes { enum KeepModes {
Unknown, Unknown,
@@ -171,13 +167,13 @@ enum KeepModes {
Update, Update,
Delete, Delete,
Info, Info,
Status Status,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct KeyValue { struct KeyValue {
key: String, key: String,
value: String value: String,
} }
impl FromStr for KeyValue { impl FromStr for KeyValue {
@@ -186,14 +182,13 @@ impl FromStr for KeyValue {
match s.split_once('=') { match s.split_once('=') {
Some(kv) => Ok(KeyValue { Some(kv) => Ok(KeyValue {
key: kv.0.to_string(), 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 { enum NumberOrString {
Number(i64), Number(i64),
@@ -219,7 +214,7 @@ pub enum ColumnType {
FileSize, FileSize,
FilePath, FilePath,
Tags, Tags,
Meta Meta,
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
@@ -287,7 +282,7 @@ fn main() -> Result<(), Error> {
if args.options.dir.is_none() { if args.options.dir.is_none() {
match proj_dirs { match proj_dirs {
Some(proj_dirs) => args.options.dir = Some(proj_dirs.data_dir().to_path_buf()), 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: Data directory: {:?}", data_path);
debug!("MAIN: DB file: {:?}", db_path); debug!("MAIN: DB file: {:?}", db_path);
fs::create_dir_all(data_path.clone()) fs::create_dir_all(data_path.clone()).context("Problem creating data directory")?;
.context("Problem creating data directory")?;
debug!("MAIN: Data directory created or already exists"); debug!("MAIN: Data directory created or already exists");
let mut conn = db::open(db_path.clone()) let mut conn = db::open(db_path.clone()).context("Problem opening database")?;
.context("Problem opening database")?;
debug!("MAIN: DB opened successfully"); debug!("MAIN: DB opened successfully");
match mode { match mode {
KeepModes::Save => mode_save(&mut cmd, args, ids, tags, conn, data_path)?, 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::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::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::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)?, KeepModes::Status => crate::modes::status::mode_status(&mut cmd, args, data_path, db_path)?,
_ => todo!() _ => todo!(),
} }
Ok(()) Ok(())
} }
fn mode_save(
cmd: &mut Command,
fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<String>, conn: Connection, data_path: PathBuf) -> Result<()> { args: Args,
ids: &mut Vec<i64>,
tags: &mut Vec<String>,
conn: Connection,
data_path: PathBuf,
) -> Result<()> {
if !ids.is_empty() { if !ids.is_empty() {
cmd.error(ErrorKind::InvalidValue, "ID given, you cannot supply IDs when using --save").exit(); cmd.error(
ErrorKind::InvalidValue,
"ID given, you cannot supply IDs when using --save",
)
.exit();
} }
if tags.is_empty() { if tags.is_empty() {
tags.push("none".to_string()); 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); let compression_type_opt = CompressionType::from_str(&compression_name);
if compression_type_opt.is_err() { 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(); let compression_type = compression_type_opt.unwrap();
@@ -350,7 +365,7 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
id: None, id: None,
ts: Utc::now(), ts: Utc::now(),
size: None, size: None,
compression: compression_type.to_string() compression: compression_type.to_string(),
}; };
let id = db::insert_item(&conn, item.clone())?; let id = db::insert_item(&conn, item.clone())?;
@@ -399,7 +414,7 @@ fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<S
let meta = db::Meta { let meta = db::Meta {
id: item.id.unwrap(), id: item.id.unwrap(),
name: kv.0.to_string(), name: kv.0.to_string(),
value: kv.1.to_string() value: kv.1.to_string(),
}; };
db::store_meta(&conn, meta)?; 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 stdout = io::stdout().lock();
let mut buffer = [0; libc::BUFSIZ as usize]; let mut buffer = [0; libc::BUFSIZ as usize];
let compression_engine = compression::get_engine(compression_type.clone()).expect("Unable to get compression engine"); let compression_engine = compression::get_engine(compression_type.clone())
let mut item_out: Box<dyn Write> = compression_engine.create(item_path.clone()) .expect("Unable to get compression engine");
.context(anyhow!("Unable to write file {:?} using compression {:?}", item_path, compression_type))?; 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"); debug!("MAIN: Starting IO loop");
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_out.write_all(&buffer[..n])?;
item.size = match item.size { item.size = match item.size {
None => Some(n as i64), 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"); 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(()) Ok(())
} }
fn mode_diff( fn mode_diff(
cmd: &mut clap::Command, cmd: &mut clap::Command,
_args: Args, // Mark as unused if not needed directly _args: Args, // Mark as unused if not needed directly
@@ -476,15 +497,14 @@ fn mode_diff(
let item_a_tags: Vec<String> = db::get_item_tags(conn, &item_a)? let item_a_tags: Vec<String> = db::get_item_tags(conn, &item_a)?
.into_iter() .into_iter()
.map(|x| {x.name}) .map(|x| x.name)
.collect(); .collect();
let item_b_tags: Vec<String> = db::get_item_tags(conn, &item_b)? let item_b_tags: Vec<String> = db::get_item_tags(conn, &item_b)?
.into_iter() .into_iter()
.map(|x| {x.name}) .map(|x| x.name)
.collect(); .collect();
let mut item_path_a = data_path.clone(); 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 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)?; 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. // 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 // For this specific code, since from_raw_fd takes ownership immediately, this is less critical
// but doesn't hurt. // but doesn't hurt.
nix::fcntl::fcntl(fd_a_write, nix::fcntl::FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)) 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))?; .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)) 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))?; .map_err(|e| anyhow!("Failed to set FD_CLOEXEC on fd_b_write: {}", e))?;
debug!("MAIN: Creating child process for diff"); debug!("MAIN: Creating child process for diff");
let mut diff_command = std::process::Command::new("diff"); let mut diff_command = std::process::Command::new("diff");
diff_command diff_command
.arg("-u") .arg("-u")
.arg("--label") .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(format!("/dev/fd/{}", fd_a_read))
.arg("--label") .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)) .arg(format!("/dev/fd/{}", fd_b_read))
.stdin(Stdio::null()) .stdin(Stdio::null())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
@@ -632,21 +665,35 @@ fn mode_diff(
let diff_status = child_process let diff_status = child_process
.wait() .wait()
.map_err(|e| anyhow!("Failed to wait on diff command: {}", e))?; .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. // Retrieve the captured output from the reader threads.
// .join().unwrap() here will panic if the reader thread itself panicked. // .join().unwrap() here will panic if the reader thread itself panicked.
// The inner Result is from the read_to_end operation within the thread. // 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| { let stdout_capture_result = stdout_reader_thread
Err(anyhow!("Stdout reader thread panicked: {:?}", panic_payload)) .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| { let stderr_capture_result = stderr_reader_thread
Err(anyhow!("Stderr reader thread panicked: {:?}", panic_payload)) .join()
.unwrap_or_else(|panic_payload| {
Err(anyhow!(
"Stderr reader thread panicked: {:?}",
panic_payload
))
})?; })?;
// Handle diff's exit status and output // Handle diff's exit status and output
match diff_status.code() { 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."); debug!("MAIN: Diff successful, no differences found.");
// Typically, diff -u doesn't print to stdout if no differences. // Typically, diff -u doesn't print to stdout if no differences.
// But if it did, it would be shown here. // But if it did, it would be shown here.
@@ -654,11 +701,13 @@ fn mode_diff(
println!("{}", String::from_utf8_lossy(&stdout_capture_result)); 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."); debug!("MAIN: Diff successful, differences found.");
println!("{}", String::from_utf8_lossy(&stdout_capture_result)); 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); eprintln!("Diff command failed with exit code: {}", error_code);
if !stdout_capture_result.is_empty() { if !stdout_capture_result.is_empty() {
eprintln!( eprintln!(
@@ -677,7 +726,8 @@ fn mode_diff(
error_code error_code
)); ));
} }
None => { // Process terminated by a signal None => {
// Process terminated by a signal
eprintln!("Diff command terminated by signal."); eprintln!("Diff command terminated by signal.");
if !stderr_capture_result.is_empty() { if !stderr_capture_result.is_empty() {
eprintln!( eprintln!(
@@ -692,10 +742,20 @@ fn mode_diff(
Ok(()) Ok(())
} }
fn mode_list(
fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<String>, conn: &mut Connection, data_path: PathBuf) -> Result<()> { cmd: &mut Command,
args: Args,
ids: &mut Vec<i64>,
tags: &Vec<String>,
conn: &mut Connection,
data_path: PathBuf,
) -> Result<()> {
if !ids.is_empty() { if !ids.is_empty() {
cmd.error(ErrorKind::InvalidValue, "ID given, you can only supply tags when using --list").exit(); cmd.error(
ErrorKind::InvalidValue,
"ID given, you can only supply tags when using --list",
)
.exit();
} }
let mut meta: HashMap<String, String> = HashMap::new(); 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() { let items = match tags.is_empty() && meta.is_empty() {
true => db::get_items(conn)?, 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); 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)? let item_tags: Vec<String> = db::get_item_tags(conn, item)?
.into_iter() .into_iter()
.map(|x| {x.name}) .map(|x| x.name)
.collect(); .collect();
tags_by_item.insert(item_id, item_tags); 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); item_meta.insert(meta.name.clone(), meta.value);
} }
meta_by_item.insert(item_id, item_meta); meta_by_item.insert(item_id, item_meta);
}; }
let mut table = Table::new(); let mut table = Table::new();
table.set_format(*format::consts::FORMAT_CLEAN); 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() { for column in list_format.clone() {
let mut column_format = column.split(":").into_iter(); let mut column_format = column.split(":").into_iter();
let column_name = column_format.next().expect("Unable to parse column name"); 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 { 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)); title_row.add_cell(Cell::new(meta_name).with_style(Attr::Bold));
} else { } else {
title_row.add_cell(Cell::new(&column_type.to_string()).with_style(Attr::Bold)); 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() { for column in list_format.clone() {
let mut column_format = column.split(":").into_iter(); let mut column_format = column.split(":").into_iter();
let column_name = column_format.next().expect("Unable to parse column name"); 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; let mut meta_name: Option<&str> = None;
if column_type == ColumnType::Meta { 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() { let column_width: usize = match column_format.next() {
Some(len) => len.parse().unwrap_or(0), Some(len) => len.parse().unwrap_or(0),
None => 0 None => 0,
}; };
let cell = match column_type { let cell = match column_type {
ColumnType::Id => Cell::new_align( ColumnType::Id => Cell::new_align(
&string_column(item.id.unwrap_or(0).to_string(), column_width), &string_column(item.id.unwrap_or(0).to_string(), column_width),
Alignment::RIGHT), Alignment::RIGHT,
ColumnType::Time => Cell::new( ),
&string_column(item.ts.with_timezone(&Local).format("%F %T").to_string(), column_width)), ColumnType::Time => Cell::new(&string_column(
item.ts.with_timezone(&Local).format("%F %T").to_string(),
column_width,
)),
ColumnType::Size => match item.size { ColumnType::Size => match item.size {
Some(size) => Cell::new_align( Some(size) => Cell::new_align(
&size_column(size as u64, args.options.human_readable, column_width), &size_column(size as u64, args.options.human_readable, column_width),
Alignment::RIGHT), Alignment::RIGHT,
),
None => match item_path.metadata() { None => match item_path.metadata() {
Ok(_) => Cell::new_align("Unknown", Alignment::RIGHT) Ok(_) => Cell::new_align("Unknown", Alignment::RIGHT)
.with_style(Attr::ForegroundColor(color::YELLOW)) .with_style(Attr::ForegroundColor(color::YELLOW))
.with_style(Attr::Bold), .with_style(Attr::Bold),
Err(_) => Cell::new_align("Missing", Alignment::RIGHT) Err(_) => Cell::new_align("Missing", Alignment::RIGHT)
.with_style(Attr::ForegroundColor(color::RED)) .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() { ColumnType::FileSize => match item_path.metadata() {
Ok(metadata) => Cell::new_align( Ok(metadata) => Cell::new_align(
&size_column(metadata.len() as u64, args.options.human_readable, column_width), &size_column(
Alignment::RIGHT), metadata.len() as u64,
args.options.human_readable,
column_width,
),
Alignment::RIGHT,
),
Err(_) => Cell::new_align("Missing", 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::Tags => Cell::new(&string_column(tags.join(" "), column_width)),
ColumnType::Meta => match meta_name { ColumnType::Meta => match meta_name {
Some(meta_name) => match meta.get(meta_name) { Some(meta_name) => match meta.get(meta_name) {
Some(meta_value) => Cell::new(&string_column(meta_value.to_string(), column_width)), Some(meta_value) => {
None => Cell::new("") Cell::new(&string_column(meta_value.to_string(), column_width))
},
None => Cell::new("")
} }
None => Cell::new(""),
},
None => Cell::new(""),
},
}; };
table_row.add_cell(cell); table_row.add_cell(cell);
} }
@@ -824,8 +905,14 @@ fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &Vec<Strin
Ok(()) Ok(())
} }
fn mode_info(
fn mode_info(cmd: &mut Command, args: Args, ids: &mut Vec<i64>, tags: &mut Vec<String>, conn: &mut Connection, data_path: PathBuf) -> Result<()> { 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() { 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(); 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 { } else if ids.len() > 1 {
@@ -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() { let item_maybe = match tags.is_empty() && meta.is_empty() {
true => match ids.iter().next() { true => match ids.iter().next() {
Some(item_id) => db::get_item(conn, *item_id)?, 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 { if let Some(item) = item_maybe {
debug!("MAIN: Found item {:?}", item); debug!("MAIN: Found item {:?}", item);
let item_id = item.id.unwrap(); let item_id = item.id.unwrap();
let item_tags: Vec<String> = db::get_item_tags(conn, &item)? let item_tags: Vec<String> = db::get_item_tags(conn, &item)?
.into_iter() .into_iter()
.map(|x| {x.name}) .map(|x| x.name)
.collect(); .collect();
let mut table = Table::new(); let mut table = Table::new();
if std::io::stdout().is_terminal() { if std::io::stdout().is_terminal() {
table.set_format(*FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR); 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![ table.add_row(Row::new(vec![
Cell::new("ID").with_style(Attr::Bold), 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()); let ts_cell = Cell::new(&item.ts.with_timezone(&Local).format("%F %T %Z").to_string());
table.add_row(Row::new(vec![ table.add_row(Row::new(vec![
Cell::new("Timestamp").with_style(Attr::Bold), Cell::new("Timestamp").with_style(Attr::Bold),
ts_cell ts_cell,
])); ]));
let mut item_path = data_path.clone(); let mut item_path = data_path.clone();
item_path.push(item.id.unwrap().to_string()); item_path.push(item.id.unwrap().to_string());
table.add_row(Row::new(vec![ table.add_row(Row::new(vec![
Cell::new("Path").with_style(Attr::Bold), 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 { let size_cell = match item.size {
Some(size) => Cell::new(format_size(size as u64, args.options.human_readable).as_str()), 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![ table.add_row(Row::new(vec![
Cell::new("Stream Size").with_style(Attr::Bold), Cell::new("Stream Size").with_style(Attr::Bold),
size_cell size_cell,
])); ]));
let compression_type = CompressionType::from_str(&item.compression)?; let compression_type = CompressionType::from_str(&item.compression)?;
table.add_row(Row::new(vec![ table.add_row(Row::new(vec![
Cell::new("Compression").with_style(Attr::Bold), 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() { let file_size_cell = match item_path.metadata() {
Ok(metadata) => Cell::new(format_size(metadata.len(), args.options.human_readable).as_str()), Ok(metadata) => {
Err(_) => Cell::new("Missing").with_style(Attr::ForegroundColor(color::RED)).with_style(Attr::Bold) 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![ table.add_row(Row::new(vec![
Cell::new("File Size").with_style(Attr::Bold), 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 magic = compression_engine.magic(item_path.clone());
let file_magic_cell = match magic { let file_magic_cell = match magic {
Ok(magic) => Cell::new(magic.as_str()), 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![ table.add_row(Row::new(vec![
Cell::new("File Magic").with_style(Attr::Bold), Cell::new("File Magic").with_style(Attr::Bold),
file_magic_cell file_magic_cell,
])); ]));
table.add_row(Row::new(vec![ table.add_row(Row::new(vec![
Cell::new("Tags").with_style(Attr::Bold), 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)? { for meta in db::get_item_meta(conn, &item)? {
let meta_name = format!("Meta: {}", &meta.name); let meta_name = format!("Meta: {}", &meta.name);
table.add_row(Row::new(vec![ table.add_row(Row::new(vec![
Cell::new(meta_name.as_str()).with_style(Attr::Bold), Cell::new(meta_name.as_str()).with_style(Attr::Bold),
Cell::new(&meta.value) Cell::new(&meta.value),
])); ]));
} }

View File

@@ -1,8 +1,8 @@
use std::env;
use std::collections::HashMap;
use regex::Regex;
use humansize::{FormatSizeOptions, BINARY}; use humansize::{FormatSizeOptions, BINARY};
use log::debug; use log::debug;
use regex::Regex;
use std::collections::HashMap;
use std::env;
pub fn get_meta_from_env() -> HashMap<String, String> { pub fn get_meta_from_env() -> HashMap<String, String> {
debug!("MAIN: Getting meta from KEEP_META_*"); debug!("MAIN: Getting meta from KEEP_META_*");
@@ -19,15 +19,14 @@ pub fn get_meta_from_env() -> HashMap<String, String> {
} }
pub fn format_size_human_readable(size: u64) -> String { pub fn format_size_human_readable(size: u64) -> String {
let options = FormatSizeOptions::from(BINARY) let options = FormatSizeOptions::from(BINARY).decimal_places(1);
.decimal_places(1);
humansize::format_size(size, options) humansize::format_size(size, options)
} }
pub fn format_size(size: u64, human_readable: bool) -> String { pub fn format_size(size: u64, human_readable: bool) -> String {
match human_readable { match human_readable {
true => format_size_human_readable(size), true => format_size_human_readable(size),
false => size.to_string() false => size.to_string(),
} }
} }

View File

@@ -1,11 +1,11 @@
use anyhow::{Context, Result, anyhow}; use anyhow::{anyhow, Context, Result};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use crate::db; use crate::db;
use clap::error::ErrorKind;
use clap::Command; use clap::Command;
use log::{debug, warn}; use log::{debug, warn};
use clap::error::ErrorKind;
use rusqlite::Connection; use rusqlite::Connection;
pub fn mode_delete( pub fn mode_delete(
@@ -17,9 +17,17 @@ pub fn mode_delete(
data_path: PathBuf, data_path: PathBuf,
) -> Result<()> { ) -> Result<()> {
if ids.is_empty() { if ids.is_empty() {
cmd.error(ErrorKind::InvalidValue, "No ID given, you must supply atleast one ID when using --delete").exit(); cmd.error(
ErrorKind::InvalidValue,
"No ID given, you must supply atleast one ID when using --delete",
)
.exit();
} else if !tags.is_empty() { } else if !tags.is_empty() {
cmd.error(ErrorKind::InvalidValue, "Tags given but not supported, you must supply atleast one ID when using --delete").exit(); cmd.error(
ErrorKind::InvalidValue,
"Tags given but not supported, you must supply atleast one ID when using --delete",
)
.exit();
} }
for item_id in ids.iter() { for item_id in ids.iter() {
@@ -30,7 +38,8 @@ pub fn mode_delete(
let mut item_path = data_path.clone(); let mut item_path = data_path.clone();
item_path.push(item_id.to_string()); item_path.push(item_id.to_string());
fs::remove_file(&item_path).context(anyhow!("Unable to remove item file {:?}", item_path))?; fs::remove_file(&item_path)
.context(anyhow!("Unable to remove item file {:?}", item_path))?;
} else { } else {
warn!("Unable to find item {item_id} in database"); warn!("Unable to find item {item_id} in database");
} }

View File

@@ -1,10 +1,9 @@
use anyhow::anyhow; use anyhow::anyhow;
use clap::Command;
use crate::compression::CompressionType; use crate::compression::CompressionType;
use std::str::FromStr; use clap::Command;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
pub fn mode_get( pub fn mode_get(
cmd: &mut Command, cmd: &mut Command,
@@ -29,9 +28,9 @@ pub fn mode_get(
let item_maybe = match tags.is_empty() && meta.is_empty() { let item_maybe = match tags.is_empty() && meta.is_empty() {
true => match ids.iter().next() { true => match ids.iter().next() {
Some(item_id) => crate::db::get_item(conn, *item_id)?, Some(item_id) => crate::db::get_item(conn, *item_id)?,
None => crate::db::get_item_last(conn)? None => crate::db::get_item_last(conn)?,
}, },
false => crate::db::get_item_matching(conn, tags, &meta)? false => crate::db::get_item_matching(conn, tags, &meta)?,
}; };
if let Some(item) = item_maybe { if let Some(item) = item_maybe {

View File

@@ -1,5 +1,5 @@
pub mod common; pub mod common;
pub mod get;
pub mod delete; pub mod delete;
pub mod update; pub mod get;
pub mod status; pub mod status;
pub mod update;

View File

@@ -1,39 +1,27 @@
use std::io; use anyhow::{anyhow, Context, Result};
use std::path::PathBuf;
use anyhow::{Context, Result, anyhow};
use rusqlite::Connection;
use gethostname::gethostname;
use strum::IntoEnumIterator;
use clap::error::ErrorKind;
use clap::*; use clap::*;
use log::*;
use is_terminal::IsTerminal; use is_terminal::IsTerminal;
use chrono::prelude::*; use std::path::PathBuf;
use std::os::fd::FromRawFd; use strum::IntoEnumIterator;
use std::process::Stdio;
use nix::fcntl::{FdFlag};
use nix::unistd::{close, pipe};
use nix::Error as NixError;
use crate::compression::CompressionType;
use strum::EnumString;
use crate::compression::program::CompressionEngineProgram;
use crate::compression::COMPRESSION_PROGRAMS;
use crate::compression::default_type; use crate::compression::default_type;
use crate::db; use crate::compression::program::CompressionEngineProgram;
use crate::modes::common::{format_size, string_column}; use crate::compression::CompressionType;
use crate::compression::COMPRESSION_PROGRAMS;
use std::str::FromStr; use std::str::FromStr;
use crate::compression::CompressionEngine;
use prettytable::{Table, Row, Cell, Attr};
use prettytable::format;
use prettytable::format::{TableFormat, Alignment};
use prettytable::row;
use prettytable::color;
use crate::FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR; use crate::FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR;
use crate::FORMAT_NO_BORDER_LINE_SEPARATOR; use crate::FORMAT_NO_BORDER_LINE_SEPARATOR;
use prettytable::color;
use prettytable::row;
use prettytable::{Attr, Cell, Row, Table};
pub fn mode_status(_cmd: &mut Command, args: crate::Args, data_path: PathBuf, db_path: PathBuf) -> Result<()> { pub fn mode_status(
_cmd: &mut Command,
args: crate::Args,
data_path: PathBuf,
db_path: PathBuf,
) -> Result<()> {
let mut path_table = Table::new(); let mut path_table = Table::new();
if std::io::stdout().is_terminal() { if std::io::stdout().is_terminal() {
@@ -49,15 +37,24 @@ pub fn mode_status(_cmd: &mut Command, args: crate::Args, data_path: PathBuf, db
path_table.add_row(Row::new(vec![ path_table.add_row(Row::new(vec![
Cell::new("Data"), Cell::new("Data"),
Cell::new(&data_path.into_os_string().into_string().expect("Unable to convert data path to string")) Cell::new(
&data_path
.into_os_string()
.into_string()
.expect("Unable to convert data path to string"),
),
])); ]));
path_table.add_row(Row::new(vec![ path_table.add_row(Row::new(vec![
Cell::new("Database"), Cell::new("Database"),
Cell::new(&db_path.into_os_string().into_string().expect("Unable to convert DB path to string")) Cell::new(
&db_path
.into_os_string()
.into_string()
.expect("Unable to convert DB path to string"),
),
])); ]));
let mut compression_table = Table::new(); let mut compression_table = Table::new();
if std::io::stdout().is_terminal() { if std::io::stdout().is_terminal() {
compression_table.set_format(*FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR); compression_table.set_format(*FORMAT_BOX_CHARS_NO_BORDER_LINE_SEPARATOR);
@@ -73,22 +70,22 @@ pub fn mode_status(_cmd: &mut Command, args: crate::Args, data_path: PathBuf, db
b->"Compress", b->"Compress",
b->"Decompress")); b->"Decompress"));
let default_type = match args.item.compression { let default_type = match args.item.compression {
Some(compression_name) => FromStr::from_str(&compression_name) Some(compression_name) => FromStr::from_str(&compression_name)
.context(anyhow!("Invalid compression type {}", compression_name))?, .context(anyhow!("Invalid compression type {}", compression_name))?,
None => default_type() None => default_type(),
}; };
for compression_type in CompressionType::iter() { for compression_type in CompressionType::iter() {
let compression_program: CompressionEngineProgram = match &COMPRESSION_PROGRAMS[compression_type.clone()] { let compression_program: CompressionEngineProgram =
match &COMPRESSION_PROGRAMS[compression_type.clone()] {
Some(compression_program) => compression_program.clone(), Some(compression_program) => compression_program.clone(),
None => CompressionEngineProgram { None => CompressionEngineProgram {
program: "".to_string(), program: "".to_string(),
compress: Vec::new(), compress: Vec::new(),
decompress: Vec::new(), decompress: Vec::new(),
supported: true supported: true,
} },
}; };
let is_default = compression_type == default_type; let is_default = compression_type == default_type;
@@ -97,14 +94,16 @@ pub fn mode_status(_cmd: &mut Command, args: crate::Args, data_path: PathBuf, db
Cell::new(&compression_type.to_string()), Cell::new(&compression_type.to_string()),
match compression_program.supported { match compression_program.supported {
true => Cell::new("Yes").with_style(Attr::ForegroundColor(color::GREEN)), true => Cell::new("Yes").with_style(Attr::ForegroundColor(color::GREEN)),
false => Cell::new("No").with_style(Attr::ForegroundColor(color::RED)) false => Cell::new("No").with_style(Attr::ForegroundColor(color::RED)),
}, },
match is_default { match is_default {
true => Cell::new("Yes").with_style(Attr::ForegroundColor(color::GREEN)), true => Cell::new("Yes").with_style(Attr::ForegroundColor(color::GREEN)),
false => Cell::new("No") false => Cell::new("No"),
}, },
match compression_program.program.eq("") { match compression_program.program.eq("") {
true => Cell::new("<INTERNAL>").with_style(Attr::ForegroundColor(color::BRIGHT_BLACK)), true => {
Cell::new("<INTERNAL>").with_style(Attr::ForegroundColor(color::BRIGHT_BLACK))
}
false => Cell::new(&compression_program.program), false => Cell::new(&compression_program.program),
}, },
Cell::new(&compression_program.compress.join(" ")), Cell::new(&compression_program.compress.join(" ")),

View File

@@ -3,10 +3,10 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use crate::db; use crate::db;
use clap::Command;
use clap::error::ErrorKind;
use log::{debug, info};
use crate::CompressionType; use crate::CompressionType;
use clap::error::ErrorKind;
use clap::Command;
use log::{debug, info};
use rusqlite::Connection; use rusqlite::Connection;
pub fn mode_update( pub fn mode_update(
@@ -18,7 +18,11 @@ pub fn mode_update(
data_path: PathBuf, data_path: PathBuf,
) -> Result<()> { ) -> Result<()> {
if ids.is_empty() { if ids.is_empty() {
cmd.error(ErrorKind::InvalidValue, "No ID given, you must supply exactly one ID when using --update").exit(); cmd.error(
ErrorKind::InvalidValue,
"No ID given, you must supply exactly one ID when using --update",
)
.exit();
} else if ids.len() > 1 { } 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 --update").exit(); cmd.error(ErrorKind::InvalidValue, "More than one ID given, you must supply exactly one ID or atleast one tag when using --update").exit();
} }
@@ -43,12 +47,16 @@ pub fn mode_update(
if item_file_metadata.is_ok() { if item_file_metadata.is_ok() {
debug!("MAIN: Updating stream size of {:?}", item_path); debug!("MAIN: Updating stream size of {:?}", item_path);
let compression_type = CompressionType::from_str(&item.compression)?; let compression_type = CompressionType::from_str(&item.compression)?;
let compression_engine = crate::compression::get_engine(compression_type).expect("Unable to get compression engine"); let compression_engine = crate::compression::get_engine(compression_type)
.expect("Unable to get compression engine");
let size = compression_engine.size(item_path)? as i64; let size = compression_engine.size(item_path)? as i64;
item.size = Some(size); item.size = Some(size);
db::update_item(&conn, item.clone())?; db::update_item(&conn, item.clone())?;
} else { } else {
debug!("MAIN: Unable to update size of item due to missing file {:?}", item_path); debug!(
"MAIN: Unable to update size of item due to missing file {:?}",
item_path
);
} }
} }
@@ -58,7 +66,7 @@ pub fn mode_update(
let meta = db::Meta { let meta = db::Meta {
id: item.id.unwrap(), id: item.id.unwrap(),
name: kv.key.to_string(), name: kv.key.to_string(),
value: kv.value.to_string() value: kv.value.to_string(),
}; };
db::store_meta(conn, meta)?; db::store_meta(conn, meta)?;
} }