aboutsummaryrefslogtreecommitdiffstats
path: root/src/history.rs
diff options
context:
space:
mode:
authorJonas Maier <>2026-03-08 13:15:46 +0100
committerJonas Maier <>2026-03-08 13:15:46 +0100
commit25a3def15ce676f0902971c4d81d16636f2ea933 (patch)
treee9b15974523d2099cd4eab6cd74fd1f4bba275d2 /src/history.rs
parent7bca57f2dddd637fa9a11a49c2cf524502e34e0e (diff)
downloadpish-25a3def15ce676f0902971c4d81d16636f2ea933.tar.gz
history :)
Diffstat (limited to 'src/history.rs')
-rw-r--r--src/history.rs153
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
+ })
+}