style: reorder imports and reformat code for consistency
This commit is contained in:
committed by
Andrew Phillips (aider)
parent
c936326ac3
commit
9feec61759
@@ -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() {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
157
src/db.rs
@@ -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)?,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
376
src/main.rs
376
src/main.rs
@@ -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),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(" ")),
|
||||||
|
|||||||
@@ -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)?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user