cicada 0.9.2

A simple Unix shell.
Documentation
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::Write;
use std::path::Path;

use linefeed::terminal::DefaultTerminal;
use linefeed::Interface;
use rusqlite::Connection as Conn;
use rusqlite::Error::SqliteFailure;
use rusqlite::NO_PARAMS;

use crate::tools::{self, clog};

fn init_db(hfile: &str, htable: &str) {
    let path = Path::new(hfile);
    if !path.exists() {
        let _parent;
        match path.parent() {
            Some(x) => _parent = x,
            None => {
                println_stderr!("cicada: history init - no parent found");
                return;
            }
        }
        let parent;
        match _parent.to_str() {
            Some(x) => parent = x,
            None => {
                println_stderr!("cicada: parent to_str is None");
                return;
            }
        }
        match fs::create_dir_all(parent) {
            Ok(_) => {}
            Err(e) => {
                println_stderr!("cicada: dirs create failed: {:?}", e);
                return;
            }
        }
        match fs::File::create(hfile) {
            Ok(_) => {
                println!("cicada: created history file: {}", hfile);
            }
            Err(e) => {
                println_stderr!("cicada: history: file create failed: {:?}", e);
            }
        }
    }

    let conn = match Conn::open(&hfile) {
        Ok(x) => x,
        Err(e) => {
            println_stderr!("cicada: history: cannot open sqlite db: {:?}", e);
            return;
        }
    };
    let sql = format!(
        "
        CREATE TABLE IF NOT EXISTS {}
            (inp TEXT,
             rtn INTEGER,
             tsb REAL,
             tse REAL,
             sessionid TEXT,
             out TEXT,
             info TEXT
            );
    ",
        htable
    );
    match conn.execute(&sql, NO_PARAMS) {
        Ok(_) => {}
        Err(e) => println_stderr!("cicada: sqlite exec error - {:?}", e),
    }
}

pub fn init(rl: &mut Interface<DefaultTerminal>) {
    let mut hist_size: usize = 99999;
    if let Ok(x) = env::var("HISTORY_SIZE") {
        if let Ok(y) = x.parse::<usize>() {
            hist_size = y;
        }
    }
    rl.set_history_size(hist_size);

    let history_table = get_history_table();
    let hfile = get_history_file();

    if !Path::new(&hfile).exists() {
        init_db(&hfile, &history_table);
    }
    if let Ok(x) = env::var("HISTORY_DELETE_DUPS") {
        if x == "1" {
            delete_duplicated_histories();
        }
    }

    let conn = match Conn::open(&hfile) {
        Ok(x) => x,
        Err(e) => {
            println_stderr!("cicada: sqlite conn open error: {:?}", e);
            return;
        }
    };
    let sql = format!("SELECT inp FROM {} ORDER BY tsb;", history_table);
    let mut stmt = match conn.prepare(&sql) {
        Ok(x) => x,
        Err(e) => {
            println_stderr!("cicada: prepare select error: {:?}", e);
            return;
        }
    };

    let rows = match stmt.query_map(NO_PARAMS, |row| row.get(0)) {
        Ok(x) => x,
        Err(e) => {
            println_stderr!("cicada: query select error: {:?}", e);
            return;
        }
    };

    let mut dict_helper: HashMap<String, bool> = HashMap::new();
    for x in rows {
        if let Ok(inp) = x {
            let _inp: String = inp;
            if dict_helper.contains_key(&_inp) {
                continue;
            }
            dict_helper.insert(_inp.clone(), true);
            rl.add_history(_inp.trim().to_string());
        }
    }
}

pub fn get_history_file() -> String {
    if let Ok(hfile) = env::var("HISTORY_FILE") {
        return hfile;
    } else if let Ok(d) = env::var("XDG_DATA_HOME") {
        return format!("{}/{}", d, "cicada/history.sqlite");
    } else {
        let home = tools::get_user_home();
        return format!("{}/{}", home, ".local/share/cicada/history.sqlite");
    }
}

pub fn get_history_table() -> String {
    if let Ok(hfile) = env::var("HISTORY_TABLE") {
        return hfile;
    } else {
        return String::from("cicada_history");
    }
}

fn delete_duplicated_histories() {
    let hfile = get_history_file();
    let history_table = get_history_table();
    let conn = match Conn::open(&hfile) {
        Ok(x) => x,
        Err(e) => {
            println_stderr!("cicada: sqlite conn open error: {:?}", e);
            return;
        }
    };
    let sql = format!(
        "DELETE FROM {} WHERE rowid NOT IN (
        SELECT MAX(rowid) FROM {} GROUP BY inp)",
        history_table, history_table
    );
    match conn.execute(&sql, NO_PARAMS) {
        Ok(_) => {}
        Err(e) => match e {
            SqliteFailure(ee, msg) => {
                if ee.extended_code == 5 {
                    log!(
                        "failed to delete dup histories: {}",
                        msg.unwrap_or("db is locked?".to_owned()),
                    );
                    return;
                }
                println_stderr!(
                    "cicada: failed to delete dup histories: {:?}: {:?}",
                    &ee,
                    &msg
                );
            }
            _ => {
                println_stderr!("cicada: failed to delete dup histories: {:?}", e);
            }
        },
    }
}

pub fn add(
    rl: &mut Interface<DefaultTerminal>,
    line: &str,
    status: i32,
    tsb: f64,
    tse: f64,
) {
    rl.add_history(line.to_string());

    let hfile = get_history_file();
    let history_table = get_history_table();
    if !Path::new(&hfile).exists() {
        init_db(&hfile, &history_table);
    }

    let conn = match Conn::open(&hfile) {
        Ok(x) => x,
        Err(e) => {
            println_stderr!("cicada: sqlite conn open error: {:?}", e);
            return;
        }
    };
    let sql = format!(
        "INSERT INTO \
         {} (inp, rtn, tsb, tse, sessionid) \
         VALUES('{}', {}, {}, {}, '{}');",
        history_table,
        str::replace(line.trim(), "'", "''"),
        status,
        tsb,
        tse,
        "cicada"
    );
    match conn.execute(&sql, NO_PARAMS) {
        Ok(_) => {}
        Err(e) => println_stderr!("cicada: failed to save history: {:?}", e),
    }
}