Initial commit
This commit is contained in:
98
src/compression.rs
Normal file
98
src/compression.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::fmt;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use log::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompressionType {
|
||||
name: String,
|
||||
binary: String,
|
||||
compress: String,
|
||||
decompress: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for CompressionType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "[name='{}', binary='{}', compress='{}', decompress='{}']", self.name, self.binary, self.compress, self.decompress)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn add_compression_type(compression_types: &mut Vec<CompressionType>, name: String, binary: String, compress: String, decompress: String) {
|
||||
let path = is_program_in_path(binary);
|
||||
|
||||
if let Ok(path) = path {
|
||||
compression_types.push(
|
||||
CompressionType {
|
||||
name,
|
||||
binary: path,
|
||||
compress,
|
||||
decompress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_compression_types() -> Vec<CompressionType> {
|
||||
let mut compression_types = Vec::new();
|
||||
|
||||
add_compression_type(&mut compression_types, "lz4".to_string(), "lz4".to_string(), "-qc".to_string() ,"-dc".to_string());
|
||||
add_compression_type(&mut compression_types, "gzip".to_string(), "gzip".to_string(), "-qc".to_string() ,"-dc".to_string());
|
||||
add_compression_type(&mut compression_types, "bzip2".to_string(), "bzip2".to_string(), "-qc".to_string() ,"-dc".to_string());
|
||||
add_compression_type(&mut compression_types, "xz".to_string(), "xz".to_string(), "-qc".to_string() ,"-dc".to_string());
|
||||
|
||||
compression_types.push(
|
||||
CompressionType {
|
||||
name: "none".to_string(),
|
||||
binary: "".to_string(),
|
||||
compress: "".to_string(),
|
||||
decompress: "".to_string(),
|
||||
});
|
||||
|
||||
return compression_types;
|
||||
}
|
||||
|
||||
|
||||
pub fn get_compression_default(compression_types: Vec<CompressionType>) -> Result<CompressionType, String> {
|
||||
debug!("Compression type: default");
|
||||
|
||||
match compression_types.first() {
|
||||
None => Err(String::from("Unable to find default compression type")),
|
||||
Some(compression_type) => Ok(compression_type.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_compression_named(compression_types: Vec<CompressionType>, compression_name: String) -> Result<CompressionType, String> {
|
||||
debug!("Compression type: {}", compression_name);
|
||||
match compression_types.iter().find(|&c| c.name == *compression_name) {
|
||||
None => Err(format!("Unable to find compression type: {}", compression_name)),
|
||||
Some(compression_type) => Ok(compression_type.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_compression(compression_name: Option<String>) -> Result<CompressionType, String> {
|
||||
let compression_types = supported_compression_types();
|
||||
|
||||
match compression_name {
|
||||
None => get_compression_default(compression_types),
|
||||
Some(compression_name) => get_compression_named(compression_types, compression_name),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_program_in_path(program: String) -> Result<String, ()> {
|
||||
debug!("Looking for executable: {}", program);
|
||||
if let Ok(path) = env::var("PATH") {
|
||||
for p in path.split(':') {
|
||||
let p_str = format!("{}/{}", p, program);
|
||||
let stat = fs::metadata(p_str.clone());
|
||||
if let Ok(stat) = stat {
|
||||
let md = stat;
|
||||
let permissions = md.permissions();
|
||||
if md.is_file() && permissions.mode() & 0o111 != 0 {
|
||||
return Ok(p_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
36
src/db.rs
Normal file
36
src/db.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use rusqlite::{params, Connection, Error};
|
||||
use rusqlite_migration::{Migrations, M};
|
||||
use log::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![
|
||||
M::up("CREATE TABLE keep(
|
||||
id INTEGER AUTOINCREMENT NOT NULL,
|
||||
ts TEXT NOT NULL,
|
||||
compress TEXT NOT NULL,
|
||||
hostname TEXT NOT NULL,
|
||||
comment TEXT NOT NULL)
|
||||
PRIMARY KEY(id);"),
|
||||
M::up("CREATE TABLE tags (
|
||||
id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES keep(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY(id, name));")
|
||||
]);
|
||||
}
|
||||
|
||||
fn open(path: String) -> Result<Connection, String> {
|
||||
debug!("Opening DB {}", path);
|
||||
|
||||
match Connection::open(path) {
|
||||
Ok(mut conn) => match conn.pragma_update(None, "foreign_keys", "ON") {
|
||||
Ok(()) => match MIGRATIONS.to_latest(&mut conn) {
|
||||
Ok(()) => Ok(conn),
|
||||
Err(e) => Err(format!("Error migrating sqlite schema: {}", e))
|
||||
},
|
||||
Err(e) => Err(format!("Error setting sqlite pragma: {}", e))
|
||||
}
|
||||
Err(e) => Err(format!("Error connecting to database: {}", e))
|
||||
}
|
||||
}
|
||||
224
src/main.rs
Normal file
224
src/main.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
use std::str::FromStr;
|
||||
use std::path::PathBuf;
|
||||
use clap::error::ErrorKind;
|
||||
use clap::*;
|
||||
use log::*;
|
||||
|
||||
pub mod compression;
|
||||
pub mod db;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[command(flatten)]
|
||||
mode: ModeArgs,
|
||||
#[command(flatten)]
|
||||
item: ItemArgs,
|
||||
#[command(flatten)]
|
||||
options: OptionsArgs,
|
||||
|
||||
#[arg()]
|
||||
ids_or_tags: Vec<NumberOrString>
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct ModeArgs {
|
||||
#[arg(group("mode"), help_heading("Mode"), short, long, conflicts_with_all(["get", "list", "update", "delete", "status"]))]
|
||||
save: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode"), short, long, conflicts_with_all(["save", "list", "update", "delete", "status"]))]
|
||||
get: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode"), short, long, conflicts_with_all(["save", "get", "update", "delete", "status"]))]
|
||||
list: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode"), short, long, conflicts_with_all(["save", "get", "list", "delete", "status"]), requires("ids_or_tags"))]
|
||||
update: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode"), short, long, conflicts_with_all(["save", "get", "list", "update", "status"]), requires("ids_or_tags"))]
|
||||
delete: bool,
|
||||
|
||||
#[arg(group("mode"), help_heading("Mode"), short('S'), long, conflicts_with_all(["save", "get", "list", "update", "delete"]))]
|
||||
status: bool
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct ItemArgs {
|
||||
#[arg(help_heading("Item"), short, long, conflicts_with("get"), conflicts_with("list"))]
|
||||
comment: Option<String>,
|
||||
|
||||
#[arg(help_heading("Item"), short('C'), long, conflicts_with("get"), conflicts_with("list"), env("KEEP_COMPRESS"))]
|
||||
compress: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct OptionsArgs {
|
||||
#[arg(help_heading("Options"), long, env("KEEP_DIR"))]
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
#[arg(help_heading("Options"), short, long)]
|
||||
force: bool,
|
||||
|
||||
#[arg(help_heading("Options"), short, long, action = clap::ArgAction::Count, conflicts_with("quiet"))]
|
||||
verbose: u8,
|
||||
|
||||
#[arg(help_heading("Options"), short, long)]
|
||||
quiet: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
enum NumberOrString {
|
||||
Number(u32),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
#[derive(Debug,PartialEq)]
|
||||
enum KeepModes {
|
||||
Unknown,
|
||||
Save,
|
||||
Get,
|
||||
List,
|
||||
Update,
|
||||
Delete,
|
||||
Status
|
||||
}
|
||||
|
||||
|
||||
impl FromStr for NumberOrString {
|
||||
type Err = &'static str; // The actual type doesn't matter since we never error, but it must implement `Display`
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err>
|
||||
{
|
||||
Ok (s.parse::<u32>()
|
||||
.map(NumberOrString::Number)
|
||||
.unwrap_or_else(|_| NumberOrString::Str (s.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let mut cmd = Args::command();
|
||||
let args = Args::parse();
|
||||
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.quiet(args.options.quiet)
|
||||
.verbosity(usize::from(args.options.verbose + 2))
|
||||
.timestamp(stderrlog::Timestamp::Second)
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
debug!("Start");
|
||||
|
||||
let ids = &mut Vec::new();
|
||||
let tags = &mut Vec::new();
|
||||
|
||||
for v in args.ids_or_tags.iter() {
|
||||
match v.clone() {
|
||||
NumberOrString::Number(num) => ids.push(num),
|
||||
NumberOrString::Str(str) => tags.push(str),
|
||||
}
|
||||
}
|
||||
|
||||
let mut mode: KeepModes = KeepModes::Unknown;
|
||||
if args.mode.save {
|
||||
mode = KeepModes::Save;
|
||||
} else if args.mode.get {
|
||||
mode = KeepModes::Get;
|
||||
} else if args.mode.list {
|
||||
mode = KeepModes::List;
|
||||
} else if args.mode.delete {
|
||||
mode = KeepModes::Delete;
|
||||
} else if args.mode.update {
|
||||
mode = KeepModes::Update;
|
||||
} else if args.mode.status {
|
||||
mode = KeepModes::Status;
|
||||
}
|
||||
|
||||
if mode == KeepModes::Unknown {
|
||||
if ! ids.is_empty() {
|
||||
mode = KeepModes::Get;
|
||||
} else {
|
||||
mode = KeepModes::Save;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("args: {:?}", args);
|
||||
debug!("ids: {:?}", ids);
|
||||
debug!("tags: {:?}", tags);
|
||||
debug!("mode: {:?}", mode);
|
||||
|
||||
match mode {
|
||||
KeepModes::Save => mode_save(&mut cmd, args, ids, tags),
|
||||
KeepModes::Get => mode_get(&mut cmd, args, ids, tags),
|
||||
KeepModes::List => mode_list(&mut cmd, args, ids, tags),
|
||||
KeepModes::Update => mode_update(&mut cmd, args, ids, tags),
|
||||
KeepModes::Delete => mode_delete(&mut cmd, args, ids, tags),
|
||||
KeepModes::Status => mode_status(&mut cmd, args),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn mode_save(cmd: &mut Command, args: Args, ids: &mut Vec<u32>, tags: &mut Vec<String>) {
|
||||
if ! ids.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "ID given, you cannot supply IDs when using --save").exit();
|
||||
}
|
||||
|
||||
if tags.is_empty() {
|
||||
tags.push("none".to_string());
|
||||
}
|
||||
|
||||
let compression_type = compression::get_compression(args.item.compress);
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn mode_get(cmd: &mut Command, args: Args, ids: &mut Vec<u32>, tags: &mut Vec<String>) {
|
||||
if ids.is_empty() && tags.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "No ID or tags given, ou must supply one ID or atleast one tag when using --get").exit();
|
||||
} else if ! ids.is_empty() && ! tags.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "Both ID and tags given, you must supply one ID or atleast one tag when using --get").exit();
|
||||
} else if ids.len() > 1 {
|
||||
cmd.error(ErrorKind::InvalidValue, "More than one ID given, you must supply one ID or atleast one tag when using --get").exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn mode_list(cmd: &mut Command, args: Args, ids: &mut Vec<u32>, tags: &mut Vec<String>) {
|
||||
if ! ids.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "ID given, you can only supply tags when using --list").exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn mode_update(cmd: &mut Command, args: Args, ids: &mut Vec<u32>, tags: &mut Vec<String>) {
|
||||
if ids.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "No ID given, you must supply one ID when using --update").exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn mode_delete(cmd: &mut Command, args: Args, ids: &mut Vec<u32>, tags: &mut Vec<String>) {
|
||||
if ids.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "No ID given, you must supply one ID when using --delete").exit();
|
||||
} else if ! tags.is_empty() {
|
||||
cmd.error(ErrorKind::InvalidValue, "Tags given, you must supply one ID when using --delete").exit();
|
||||
} else if ids.len() > 1 {
|
||||
cmd.error(ErrorKind::InvalidValue, "More than one ID given, you must supply one ID when using --delete").exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn mode_status_show_compression() {
|
||||
let compression_types = compression::supported_compression_types();
|
||||
|
||||
println!("compression_types:");
|
||||
for compression_type in compression_types.into_iter() {
|
||||
println!(" {}", compression_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn mode_status(cmd: &mut Command, args: Args) {
|
||||
mode_status_show_compression();
|
||||
}
|
||||
Reference in New Issue
Block a user