diff options
Diffstat (limited to 'src/local')
| -rw-r--r-- | src/local/database.rs | 31 | ||||
| -rw-r--r-- | src/local/history.rs | 4 | ||||
| -rw-r--r-- | src/local/import.rs | 101 | ||||
| -rw-r--r-- | src/local/mod.rs | 1 |
4 files changed, 118 insertions, 19 deletions
diff --git a/src/local/database.rs b/src/local/database.rs index b2c009b6..2a4cc582 100644 --- a/src/local/database.rs +++ b/src/local/database.rs @@ -9,6 +9,7 @@ use crate::History; pub trait Database { fn save(&self, h: History) -> Result<()>; + fn save_bulk(&mut self, h: &Vec<History>) -> Result<()>; fn load(&self, id: &str) -> Result<History>; fn list(&self) -> Result<()>; fn update(&self, h: History) -> Result<()>; @@ -51,7 +52,9 @@ impl SqliteDatabase { duration integer not null, exit integer not null, command text not null, - cwd text not null + cwd text not null, + + unique(timestamp, cwd, command) )", NO_PARAMS, )?; @@ -65,7 +68,7 @@ impl Database for SqliteDatabase { debug!("saving history to sqlite"); self.conn.execute( - "insert into history ( + "insert or ignore into history ( id, timestamp, duration, @@ -79,6 +82,30 @@ impl Database for SqliteDatabase { Ok(()) } + fn save_bulk(&mut self, h: &Vec<History>) -> Result<()> { + debug!("saving history to sqlite"); + + let tx = self.conn.transaction()?; + + for i in h { + tx.execute( + "insert or ignore into history ( + id, + timestamp, + duration, + exit, + command, + cwd + ) values (?1, ?2, ?3, ?4, ?5, ?6)", + params![i.id, i.timestamp, i.duration, i.exit, i.command, i.cwd], + )?; + } + + tx.commit()?; + + Ok(()) + } + fn load(&self, id: &str) -> Result<History> { debug!("loading history item"); diff --git a/src/local/history.rs b/src/local/history.rs index 3c9a9069..00109621 100644 --- a/src/local/history.rs +++ b/src/local/history.rs @@ -12,10 +12,10 @@ pub struct History { } impl History { - pub fn new(command: String, cwd: String, exit: i64, duration: i64) -> History { + pub fn new(timestamp: i64, command: String, cwd: String, exit: i64, duration: i64) -> History { History { id: Uuid::new_v4().to_simple().to_string(), - timestamp: chrono::Utc::now().timestamp_millis(), + timestamp, command, cwd, exit, diff --git a/src/local/import.rs b/src/local/import.rs index 8db8f0e3..ce141c52 100644 --- a/src/local/import.rs +++ b/src/local/import.rs @@ -4,38 +4,109 @@ use std::fs::File; use std::io::{BufRead, BufReader}; -use eyre::Result; +use chrono::{TimeZone, Utc}; +use eyre::{eyre, Result}; -use crate::models::history::History; +use crate::local::history::History; -pub struct ImportBash { +#[derive(Debug)] +pub struct ImportZsh { file: BufReader<File>, + + pub loc: u64, } -impl ImportBash { - pub fn new(path: &str) -> Result<ImportBash> { +// this could probably be sped up +fn count_lines(path: &str) -> Result<usize> { + let file = File::open(path)?; + let buf = BufReader::new(file); + + Ok(buf.lines().count()) +} + +impl ImportZsh { + pub fn new(path: &str) -> Result<ImportZsh> { + let loc = count_lines(path)?; + let file = File::open(path)?; let buf = BufReader::new(file); - Ok(ImportBash { file: buf }) + Ok(ImportZsh { + file: buf, + loc: loc as u64, + }) } } -impl Iterator for ImportBash { - type Item = History; +fn trim_newline(s: &str) -> String { + let mut s = String::from(s); + + if s.ends_with('\n') { + s.pop(); + if s.ends_with('\r') { + s.pop(); + } + } + + s +} - fn next(&mut self) -> Option<History> { +fn parse_extended(line: String) -> History { + let line = line.replacen(": ", "", 2); + let mut split = line.splitn(2, ":"); + + let time = split.next().unwrap_or("-1"); + let time = time + .parse::<i64>() + .unwrap_or(chrono::Utc::now().timestamp_nanos()); + + let duration = split.next().unwrap(); // might be 0;the command + let mut split = duration.split(";"); + + let duration = split.next().unwrap_or("-1"); // should just be the 0 + let duration = duration.parse::<i64>().unwrap_or(-1); + + let command = split.next().unwrap(); + + // use nanos, because why the hell not? we won't display them. + History::new( + Utc.timestamp(time, 0).timestamp_nanos(), + trim_newline(command), + String::from("unknown"), + -1, + duration * 1_000_000_000, + ) +} + +impl Iterator for ImportZsh { + type Item = Result<History>; + + fn next(&mut self) -> Option<Self::Item> { + // ZSH extended history records the timestamp + command duration + // These lines begin with : + // So, if the line begins with :, parse it. Otherwise it's just + // the command let mut line = String::new(); match self.file.read_line(&mut line) { Ok(0) => None, - Err(_) => None, + Err(e) => Some(Err(eyre!("failed to parse line: {}", e))), + + Ok(_) => { + let extended = line.starts_with(":"); - Ok(_) => Some(History { - cwd: "none".to_string(), - command: line, - timestamp: -1, - }), + if extended { + Some(Ok(parse_extended(line))) + } else { + Some(Ok(History::new( + chrono::Utc::now().timestamp_nanos(), // what else? :/ + trim_newline(line.as_str()), + String::from("unknown"), + -1, + -1, + ))) + } + } } } } diff --git a/src/local/mod.rs b/src/local/mod.rs index f587d016..a11ee213 100644 --- a/src/local/mod.rs +++ b/src/local/mod.rs @@ -1,2 +1,3 @@ pub mod database; pub mod history; +pub mod import; |
