2021-02-14 09:53:18 -07:00
|
|
|
use chrono::Utc;
|
2020-10-04 17:59:28 -06:00
|
|
|
use std::path::Path;
|
|
|
|
|
2021-02-14 11:00:41 -07:00
|
|
|
use eyre::Result;
|
2020-10-04 17:59:28 -06:00
|
|
|
|
2020-10-05 10:20:48 -06:00
|
|
|
use rusqlite::{params, Connection};
|
2021-02-14 10:18:02 -07:00
|
|
|
use rusqlite::{Transaction, NO_PARAMS};
|
2020-10-04 17:59:28 -06:00
|
|
|
|
2021-02-14 10:18:02 -07:00
|
|
|
use super::history::History;
|
2020-10-04 17:59:28 -06:00
|
|
|
|
2021-02-15 14:30:13 -07:00
|
|
|
pub enum QueryParam {
|
|
|
|
Text(String),
|
|
|
|
}
|
|
|
|
|
2020-10-04 17:59:28 -06:00
|
|
|
pub trait Database {
|
2021-02-14 10:18:02 -07:00
|
|
|
fn save(&mut self, h: &History) -> Result<()>;
|
2021-02-14 08:15:26 -07:00
|
|
|
fn save_bulk(&mut self, h: &[History]) -> Result<()>;
|
2021-02-13 10:02:52 -07:00
|
|
|
fn load(&self, id: &str) -> Result<History>;
|
2021-02-14 09:53:18 -07:00
|
|
|
fn list(&self) -> Result<Vec<History>>;
|
2021-02-14 15:12:35 -07:00
|
|
|
fn range(&self, from: chrono::DateTime<Utc>, to: chrono::DateTime<Utc>)
|
|
|
|
-> Result<Vec<History>>;
|
2021-02-14 10:18:02 -07:00
|
|
|
fn update(&self, h: &History) -> Result<()>;
|
2021-02-15 14:30:13 -07:00
|
|
|
fn query(&self, query: &str, params: &[QueryParam]) -> Result<Vec<History>>;
|
2020-10-04 17:59:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Intended for use on a developer machine and not a sync server.
|
|
|
|
// TODO: implement IntoIterator
|
2021-02-14 08:15:26 -07:00
|
|
|
pub struct Sqlite {
|
2020-10-04 17:59:28 -06:00
|
|
|
conn: Connection,
|
|
|
|
}
|
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
impl Sqlite {
|
|
|
|
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
|
2020-10-04 17:59:28 -06:00
|
|
|
let path = path.as_ref();
|
|
|
|
debug!("opening sqlite database at {:?}", path);
|
|
|
|
|
2020-10-05 10:20:48 -06:00
|
|
|
let create = !path.exists();
|
|
|
|
if create {
|
|
|
|
if let Some(dir) = path.parent() {
|
|
|
|
std::fs::create_dir_all(dir)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-04 17:59:28 -06:00
|
|
|
let conn = Connection::open(path)?;
|
|
|
|
|
|
|
|
if create {
|
|
|
|
Self::setup_db(&conn)?;
|
|
|
|
}
|
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
Ok(Self { conn })
|
2020-10-04 17:59:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
fn setup_db(conn: &Connection) -> Result<()> {
|
|
|
|
debug!("running sqlite database setup");
|
|
|
|
|
|
|
|
conn.execute(
|
|
|
|
"create table if not exists history (
|
2021-02-13 10:02:52 -07:00
|
|
|
id text primary key,
|
2020-10-05 10:20:48 -06:00
|
|
|
timestamp integer not null,
|
2021-02-13 10:02:52 -07:00
|
|
|
duration integer not null,
|
|
|
|
exit integer not null,
|
2020-10-05 10:20:48 -06:00
|
|
|
command text not null,
|
2021-02-13 12:37:00 -07:00
|
|
|
cwd text not null,
|
2021-02-13 13:21:49 -07:00
|
|
|
session text not null,
|
|
|
|
hostname text not null,
|
2021-02-13 12:37:00 -07:00
|
|
|
|
|
|
|
unique(timestamp, cwd, command)
|
2020-10-05 10:20:48 -06:00
|
|
|
)",
|
2020-10-04 17:59:28 -06:00
|
|
|
NO_PARAMS,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-14 10:18:02 -07:00
|
|
|
|
|
|
|
fn save_raw(tx: &Transaction, h: &History) -> Result<()> {
|
|
|
|
tx.execute(
|
|
|
|
"insert or ignore into history (
|
|
|
|
id,
|
|
|
|
timestamp,
|
|
|
|
duration,
|
|
|
|
exit,
|
|
|
|
command,
|
|
|
|
cwd,
|
|
|
|
session,
|
|
|
|
hostname
|
|
|
|
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
|
|
|
params![
|
|
|
|
h.id,
|
|
|
|
h.timestamp,
|
|
|
|
h.duration,
|
|
|
|
h.exit,
|
|
|
|
h.command,
|
|
|
|
h.cwd,
|
|
|
|
h.session,
|
|
|
|
h.hostname
|
|
|
|
],
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-04 17:59:28 -06:00
|
|
|
}
|
|
|
|
|
2021-02-15 14:30:13 -07:00
|
|
|
impl rusqlite::ToSql for QueryParam {
|
|
|
|
fn to_sql(&self) -> Result<rusqlite::types::ToSqlOutput<'_>, rusqlite::Error> {
|
|
|
|
use rusqlite::types::{ToSqlOutput, Value};
|
|
|
|
|
|
|
|
match self {
|
2021-02-15 14:36:07 -07:00
|
|
|
Self::Text(s) => Ok(ToSqlOutput::Owned(Value::Text(s.clone()))),
|
2021-02-15 14:30:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
impl Database for Sqlite {
|
2021-02-14 10:18:02 -07:00
|
|
|
fn save(&mut self, h: &History) -> Result<()> {
|
2020-10-04 17:59:28 -06:00
|
|
|
debug!("saving history to sqlite");
|
|
|
|
|
2021-02-14 10:18:02 -07:00
|
|
|
let tx = self.conn.transaction()?;
|
|
|
|
Self::save_raw(&tx, h)?;
|
|
|
|
tx.commit()?;
|
|
|
|
|
|
|
|
Ok(())
|
2021-02-13 10:02:52 -07:00
|
|
|
}
|
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
fn save_bulk(&mut self, h: &[History]) -> Result<()> {
|
2021-02-13 12:37:00 -07:00
|
|
|
debug!("saving history to sqlite");
|
|
|
|
|
|
|
|
let tx = self.conn.transaction()?;
|
|
|
|
for i in h {
|
2021-02-14 10:18:02 -07:00
|
|
|
Self::save_raw(&tx, i)?
|
2021-02-13 12:37:00 -07:00
|
|
|
}
|
|
|
|
tx.commit()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-13 10:02:52 -07:00
|
|
|
fn load(&self, id: &str) -> Result<History> {
|
|
|
|
debug!("loading history item");
|
|
|
|
|
|
|
|
let mut stmt = self.conn.prepare(
|
2021-02-13 13:21:49 -07:00
|
|
|
"select id, timestamp, duration, exit, command, cwd, session, hostname from history
|
2021-02-13 10:02:52 -07:00
|
|
|
where id = ?1",
|
|
|
|
)?;
|
|
|
|
|
2021-02-14 11:00:41 -07:00
|
|
|
let history = stmt.query_row(params![id], |row| {
|
2021-02-14 10:18:02 -07:00
|
|
|
history_from_sqlite_row(Some(id.to_string()), row)
|
2021-02-13 10:02:52 -07:00
|
|
|
})?;
|
|
|
|
|
2021-02-14 11:00:41 -07:00
|
|
|
Ok(history)
|
2021-02-13 10:02:52 -07:00
|
|
|
}
|
|
|
|
|
2021-02-14 10:18:02 -07:00
|
|
|
fn update(&self, h: &History) -> Result<()> {
|
2021-02-13 10:02:52 -07:00
|
|
|
debug!("updating sqlite history");
|
|
|
|
|
|
|
|
self.conn.execute(
|
|
|
|
"update history
|
2021-02-13 13:21:49 -07:00
|
|
|
set timestamp = ?2, duration = ?3, exit = ?4, command = ?5, cwd = ?6, session = ?7, hostname = ?8
|
2021-02-13 10:02:52 -07:00
|
|
|
where id = ?1",
|
2021-02-13 13:21:49 -07:00
|
|
|
params![h.id, h.timestamp, h.duration, h.exit, h.command, h.cwd, h.session, h.hostname],
|
2020-10-05 10:20:48 -06:00
|
|
|
)?;
|
2020-10-04 17:59:28 -06:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-14 09:53:18 -07:00
|
|
|
fn list(&self) -> Result<Vec<History>> {
|
2020-10-04 17:59:28 -06:00
|
|
|
debug!("listing history");
|
2020-10-05 10:20:48 -06:00
|
|
|
|
2021-02-14 09:53:18 -07:00
|
|
|
let mut stmt = self
|
|
|
|
.conn
|
|
|
|
.prepare("SELECT * FROM history order by timestamp asc")?;
|
2021-02-13 10:02:52 -07:00
|
|
|
|
2021-02-14 10:18:02 -07:00
|
|
|
let history_iter = stmt.query_map(params![], |row| history_from_sqlite_row(None, row))?;
|
2020-10-04 17:59:28 -06:00
|
|
|
|
2021-02-14 10:18:02 -07:00
|
|
|
Ok(history_iter.filter_map(Result::ok).collect())
|
2021-02-14 09:53:18 -07:00
|
|
|
}
|
2020-10-04 17:59:28 -06:00
|
|
|
|
2021-02-14 15:12:35 -07:00
|
|
|
fn range(
|
|
|
|
&self,
|
|
|
|
from: chrono::DateTime<Utc>,
|
|
|
|
to: chrono::DateTime<Utc>,
|
|
|
|
) -> Result<Vec<History>> {
|
|
|
|
debug!("listing history from {:?} to {:?}", from, to);
|
2020-10-04 17:59:28 -06:00
|
|
|
|
2021-02-14 09:53:18 -07:00
|
|
|
let mut stmt = self.conn.prepare(
|
2021-02-14 15:12:35 -07:00
|
|
|
"SELECT * FROM history where timestamp >= ?1 and timestamp <= ?2 order by timestamp asc",
|
2021-02-14 09:53:18 -07:00
|
|
|
)?;
|
|
|
|
|
2021-02-14 15:12:35 -07:00
|
|
|
let history_iter = stmt.query_map(
|
|
|
|
params![from.timestamp_nanos(), to.timestamp_nanos()],
|
|
|
|
|row| history_from_sqlite_row(None, row),
|
|
|
|
)?;
|
2021-02-14 09:53:18 -07:00
|
|
|
|
2021-02-14 10:18:02 -07:00
|
|
|
Ok(history_iter.filter_map(Result::ok).collect())
|
2020-10-04 17:59:28 -06:00
|
|
|
}
|
2021-02-15 14:30:13 -07:00
|
|
|
|
|
|
|
fn query(&self, query: &str, params: &[QueryParam]) -> Result<Vec<History>> {
|
|
|
|
let mut stmt = self.conn.prepare(query)?;
|
|
|
|
|
|
|
|
let history_iter = stmt.query_map(params, |row| history_from_sqlite_row(None, row))?;
|
|
|
|
|
|
|
|
Ok(history_iter.filter_map(Result::ok).collect())
|
|
|
|
}
|
2020-10-04 17:59:28 -06:00
|
|
|
}
|
2021-02-14 10:18:02 -07:00
|
|
|
|
|
|
|
fn history_from_sqlite_row(
|
|
|
|
id: Option<String>,
|
|
|
|
row: &rusqlite::Row,
|
|
|
|
) -> Result<History, rusqlite::Error> {
|
|
|
|
let id = match id {
|
|
|
|
Some(id) => id,
|
|
|
|
None => row.get(0)?,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(History {
|
|
|
|
id,
|
|
|
|
timestamp: row.get(1)?,
|
|
|
|
duration: row.get(2)?,
|
|
|
|
exit: row.get(3)?,
|
|
|
|
command: row.get(4)?,
|
|
|
|
cwd: row.get(5)?,
|
|
|
|
session: row.get(6)?,
|
|
|
|
hostname: row.get(7)?,
|
|
|
|
})
|
|
|
|
}
|