reef 0.1.17

a package to execute and log system commands
Documentation
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)
            }
        }
    }
}