use crate::Command;
use anyhow::{anyhow, Result};
use directories::UserDirs;
use rusqlite::{params, Connection};
use std::path::PathBuf;
pub struct Storage;
static COMMANDS_CREATE: &'static str = "CREATE TABLE IF NOT EXISTS commands (
start TEXT,
stdout BLOB,
stderr BLOB,
json TEXT
)";
impl Storage {
pub fn add(command: &Command) -> Result<()> {
let conn = Storage::conn()?;
conn.execute(COMMANDS_CREATE, [])?;
Storage::remove_older_than(10)?;
let mut cmd2 = command.clone();
cmd2.set_stdout(Vec::new());
cmd2.set_stderr(Vec::new());
let cmd_json = serde_json::to_string(&cmd2)?;
conn.execute(
"INSERT INTO commands
(start,stdout,stderr,json)
VALUES (?1,?2,?3,?4);",
params!(
match command.start() {
Some(start) => format!("{}", start.format("%+")),
None => "".to_string(),
},
command.stdout(),
command.stderr(),
cmd_json
),
)?;
Ok(())
}
pub fn search(pattern: &str, limit: usize) -> Result<Vec<Command>> {
let mut commands = Vec::new();
let conn = Storage::conn()?;
let mut stmt = conn.prepare(
"SELECT start,stdout,stderr,json
FROM commands
ORDER BY start DESC;",
)?;
let command_iter = stmt.query_map([], |row| {
let json: String = row.get(3)?;
let mut command = serde_json::from_str::<Command>(&json).unwrap();
let stdout: Vec<u8> = row.get(1)?;
let stderr: Vec<u8> = row.get(2)?;
command.set_stdout(stdout);
command.set_stderr(stderr);
Ok(command)
})?;
for iter in command_iter {
let cmd = iter.unwrap();
if cmd.matches(pattern) {
commands.push(cmd);
}
if commands.len() >= limit {
return Ok(commands);
}
}
Ok(commands)
}
pub fn remove_older_than(days: usize) -> Result<()> {
let conn = Storage::conn()?;
conn.execute(
&format!(
"DELETE FROM commands WHERE start <= date('now','-{} day');",
days
),
[],
)?;
Ok(())
}
fn conn() -> Result<Connection> {
let filename = Storage::default_filename()?;
match filename.parent() {
Some(parent) => {
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
Ok(Connection::open(filename)?)
}
None => Err(anyhow!(
"unable to determine parent directory of filename '{}'",
filename.display()
)),
}
}
fn default_filename() -> Result<PathBuf> {
match std::env::var("REEF_COMMANDS_DB") {
Ok(val) => Ok(PathBuf::from(val)),
Err(_) => {
let dir = match UserDirs::new() {
Some(user_dirs) => user_dirs.home_dir().join("reef.commands.db").to_path_buf(),
None => std::env::temp_dir().join("reef.commands.db").to_path_buf(),
};
Ok(dir)
}
}
}
}