diff options
| author | Jonas Maier <> | 2026-03-08 13:15:46 +0100 |
|---|---|---|
| committer | Jonas Maier <> | 2026-03-08 13:15:46 +0100 |
| commit | 25a3def15ce676f0902971c4d81d16636f2ea933 (patch) | |
| tree | e9b15974523d2099cd4eab6cd74fd1f4bba275d2 /src/history.rs | |
| parent | 7bca57f2dddd637fa9a11a49c2cf524502e34e0e (diff) | |
| download | pish-25a3def15ce676f0902971c4d81d16636f2ea933.tar.gz | |
history :)
Diffstat (limited to 'src/history.rs')
| -rw-r--r-- | src/history.rs | 153 |
1 files changed, 147 insertions, 6 deletions
diff --git a/src/history.rs b/src/history.rs index 865d949..ea71fa7 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,8 +1,9 @@ -use sqlite::Connection; +use sqlite::{Connection, State}; use crate::BString; use crate::date::DateTime; use std::env::current_dir; +use std::i64; use std::path::PathBuf; fn db_file() -> PathBuf { @@ -21,15 +22,27 @@ pub struct HistoryEntry { pub cmd: BString, } +pub fn canonical_path(mut path: BString) -> BString { + while let Some(b'/') = path.last() { + path.pop(); + } + if path.is_empty() { + path.push(b'/'); + } + path +} + impl HistoryEntry { pub fn new(cmd: BString) -> Self { Self { time: DateTime::now(), - loc: current_dir() - .unwrap() - .as_os_str() - .as_encoded_bytes() - .to_vec(), + loc: canonical_path( + current_dir() + .unwrap() + .as_os_str() + .as_encoded_bytes() + .to_vec(), + ), cmd, } } @@ -74,5 +87,133 @@ fn try_persist(entry: &HistoryEntry) -> sqlite::Result<()> { pub fn persist(entry: &HistoryEntry) { // keep quiet in case db fails + // TODO maybe better behavior? let _ = try_persist(entry); } + +pub struct HistoryQueryer { + db: Connection, +} + +impl HistoryQueryer { + pub fn new() -> sqlite::Result<Self> { + Ok(Self { db: try_db()? }) + } + + pub fn query( + &self, + min_time: Option<DateTime>, + max_time: Option<DateTime>, + path_prefix: Option<&[u8]>, + path_strict: bool, + ) -> sqlite::Result<impl Iterator<Item = HistoryEntry>> { + let mut query = String::from("SELECT id, ts, loc, cmd FROM history\n"); + let mut has_cond = false; + let mut cond = |c| { + if has_cond { + query += "AND "; + } + query += c; + query += "\n"; + has_cond = true; + }; + + let path_prefix = path_prefix.map(|p| canonical_path(p.to_vec())); + let mut upper = Vec::new(); + + if let Some(path_prefix) = &path_prefix { + if path_strict { + cond("loc = ?"); + } else { + upper = path_prefix.to_vec(); + if let Some(last) = upper.last_mut() { + *last = last.saturating_add(1); + } else { + upper.push(0xFF); + } + cond("loc >= ? AND loc < ?"); + } + } + + if min_time.is_some() { + cond("ts >= ?"); + } + + if max_time.is_some() { + cond("ts <= ?"); + } + + query += "ORDER BY id asc"; + let mut stmt = self.db.prepare(&query)?; + + let mut i = 1; + if let Some(path_prefix) = &path_prefix { + stmt.bind((i, path_prefix.as_slice()))?; + i += 1; + if !path_strict { + stmt.bind((i, upper.as_slice()))?; + i += 1; + } + } + + if let Some(t) = min_time { + stmt.bind((i, t.unix() as i64))?; + i += 1; + } + + if let Some(t) = max_time { + stmt.bind((i, t.unix() as i64))?; + i += 1; + } + + let _ = i; + + Ok(std::iter::from_fn(move || match stmt.next() { + Ok(State::Row) => { + let ts: i64 = stmt.read::<i64, _>(1).ok()?; + let loc: Vec<u8> = stmt.read::<Vec<u8>, _>(2).ok()?; + let cmd: Vec<u8> = stmt.read::<Vec<u8>, _>(3).ok()?; + let time = DateTime::from_unix(ts as u64); + Some(HistoryEntry { time, loc, cmd }) + } + _ => None, + })) + } +} + +pub fn local_history_filter( + hist: Vec<HistoryEntry>, + min_time: Option<DateTime>, + max_time: Option<DateTime>, + path_prefix: Option<&[u8]>, + path_strict: bool, +) -> impl Iterator<Item = HistoryEntry> { + let path_prefix = path_prefix.map(|p| canonical_path(p.to_vec())); + + hist.into_iter().filter(move |entry| { + if let Some(t) = &min_time + && t.unix() > entry.time.unix() + { + return false; + } + + if let Some(t) = &max_time + && t.unix() < entry.time.unix() + { + return false; + } + + if let Some(path_prefix) = &path_prefix { + if path_strict { + let canon_path = canonical_path(entry.loc.clone()); + return &canon_path == path_prefix; + } else { + if !entry.loc.starts_with(&path_prefix) { + return false; + } + } + } + + true + }) +} |
