mcfly 0.9.4

McFly replaces your default ctrl-r shell history search with an intelligent search engine that takes into account your working directory and the context of recently executed commands. McFly's suggestions are prioritized in real time with a small neural network.
Documentation
use crate::simplified_command::SimplifiedCommand;
use rusqlite::{Connection, named_params};
use std::io;
use std::io::Write;

pub const CURRENT_SCHEMA_VERSION: u16 = 3;

pub fn first_time_setup(connection: &Connection) {
    make_schema_versions_table(connection);
    write_current_schema_version(connection);
}

pub fn migrate(connection: &Connection) {
    make_schema_versions_table(connection);

    let current_version: u16 = connection
        .query_row::<Option<u16>, _, _>(
            "select max(version) FROM schema_versions ORDER BY version DESC LIMIT 1",
            [],
            |row| row.get(0),
        )
        .unwrap_or_else(|err| panic!("McFly error: Query to work ({err})"))
        .unwrap_or(0);

    assert!(
        current_version <= CURRENT_SCHEMA_VERSION,
        "McFly error: Database schema version ({current_version}) is newer than the max version supported by this binary ({CURRENT_SCHEMA_VERSION}). You should update mcfly.",
    );

    if current_version < CURRENT_SCHEMA_VERSION {
        print!("McFly: Upgrading McFly DB to version {CURRENT_SCHEMA_VERSION}, please wait...");
        io::stdout()
            .flush()
            .unwrap_or_else(|err| panic!("McFly error: STDOUT flush should work ({err})"));
    }

    if current_version < 1 {
        connection
            .execute_batch(
                "ALTER TABLE commands ADD COLUMN cmd_tpl TEXT; UPDATE commands SET cmd_tpl = '';",
            )
            .unwrap_or_else(|err| panic!("McFly error: Unable to add cmd_tpl to commands ({err})"));

        let mut statement = connection
            .prepare("UPDATE commands SET cmd_tpl = :cmd_tpl WHERE id = :id")
            .unwrap_or_else(|err| panic!("McFly error: Unable to prepare update ({err})"));

        for (id, cmd) in cmd_strings(connection) {
            let simplified_command = SimplifiedCommand::new(cmd.as_str(), true);
            statement
                .execute(named_params! { ":cmd_tpl": &simplified_command.result, ":id": &id })
                .unwrap_or_else(|err| panic!("McFly error: Insert to work ({err})"));
        }
    }

    if current_version < 2 {
        connection
            .execute_batch(
                "ALTER TABLE commands ADD COLUMN session_id TEXT; \
                 UPDATE commands SET session_id = 'UNKNOWN'; \
                 CREATE INDEX command_session_id ON commands (session_id);",
            )
            .unwrap_or_else(|err| {
                panic!("McFly error: Unable to add session_id to commands ({err})")
            });
    }

    if current_version < 3 {
        connection
            .execute_batch(
                "CREATE TABLE selected_commands( \
              id INTEGER PRIMARY KEY AUTOINCREMENT, \
              cmd TEXT NOT NULL, \
              session_id TEXT NOT NULL, \
              dir TEXT NOT NULL \
            ); \
            CREATE INDEX selected_command_session_cmds ON selected_commands (session_id, cmd); \
            \
            ALTER TABLE commands ADD COLUMN selected INTEGER; \
            UPDATE commands SET selected = 0;",
            )
            .unwrap_or_else(|err| panic!("McFly error: Unable to add selected_commands ({err})"));
    }

    if current_version < CURRENT_SCHEMA_VERSION {
        println!("done.");
        write_current_schema_version(connection);
    }
}

fn make_schema_versions_table(connection: &Connection) {
    connection
        .execute_batch(
            "CREATE TABLE IF NOT EXISTS schema_versions( \
                id INTEGER PRIMARY KEY AUTOINCREMENT, \
                version INTEGER NOT NULL, \
                when_run INTEGER NOT NULL);

             CREATE UNIQUE INDEX IF NOT EXISTS schema_versions_index ON schema_versions (version);",
        )
        .unwrap_or_else(|err| {
            panic!("McFly error: Unable to create schema_versions db table ({err})")
        });
}

fn write_current_schema_version(connection: &Connection) {
    let insert = format!(
        "INSERT INTO schema_versions (version, when_run) VALUES ({CURRENT_SCHEMA_VERSION}, strftime('%s','now'))"
    );
    connection
        .execute_batch(&insert)
        .unwrap_or_else(|err| panic!("McFly error: Unable to update schema_versions ({err})"));
}

fn cmd_strings(connection: &Connection) -> Vec<(i64, String)> {
    let query = "SELECT id, cmd FROM commands ORDER BY id DESC";
    let mut statement = connection.prepare(query).unwrap();
    let command_iter = statement
        .query_map([], |row| Ok((row.get_unwrap(0), row.get_unwrap(1))))
        .unwrap_or_else(|err| panic!("McFly error: Query Map to work ({err})"));

    let mut vec = Vec::new();
    for command in command_iter.flatten() {
        vec.push(command);
    }

    vec
}