use anyhow::Result;
use crate::registry::{db, now_epoch_s, Registry};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScratchRow {
pub browser_name: String,
pub target_id: String,
pub last_used_at_epoch_s: i64,
}
impl Registry {
pub fn scratch_get(&self, browser_name: &str) -> Result<Option<ScratchRow>> {
db::query_optional(
&self.conn,
"SELECT browser_name, target_id, last_used_at_epoch_s \
FROM scratches WHERE browser_name = ?1",
[browser_name],
|r| {
Ok(ScratchRow {
browser_name: r.get(0)?,
target_id: r.get(1)?,
last_used_at_epoch_s: r.get(2)?,
})
},
)
}
pub fn scratch_upsert(&self, browser_name: &str, target_id: &str) -> Result<()> {
let now = now_epoch_s();
db::execute(
&self.conn,
"INSERT INTO scratches (browser_name, target_id, last_used_at_epoch_s) \
VALUES (?1, ?2, ?3) \
ON CONFLICT(browser_name) DO UPDATE SET \
target_id = excluded.target_id, \
last_used_at_epoch_s = excluded.last_used_at_epoch_s",
rusqlite::params![browser_name, target_id, now],
|| format!("upsert scratch for {browser_name}"),
)
}
pub fn scratch_touch(&self, browser_name: &str) -> Result<()> {
let now = now_epoch_s();
db::execute_bare(
&self.conn,
"UPDATE scratches SET last_used_at_epoch_s = ?1 WHERE browser_name = ?2",
rusqlite::params![now, browser_name],
)
}
pub fn scratch_delete(&self, browser_name: &str) -> Result<()> {
db::execute_bare(
&self.conn,
"DELETE FROM scratches WHERE browser_name = ?1",
[browser_name],
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn upsert_then_get_round_trip() {
let reg = Registry::open_in_memory().unwrap();
assert!(reg.scratch_get("brave-twilight").unwrap().is_none());
reg.scratch_upsert("brave-twilight", "T1").unwrap();
let got = reg.scratch_get("brave-twilight").unwrap().unwrap();
assert_eq!(got.browser_name, "brave-twilight");
assert_eq!(got.target_id, "T1");
assert!(got.last_used_at_epoch_s > 0);
}
#[test]
fn upsert_replaces_existing_target_id() {
let reg = Registry::open_in_memory().unwrap();
reg.scratch_upsert("brave-twilight", "T1").unwrap();
reg.scratch_upsert("brave-twilight", "T2").unwrap();
let got = reg.scratch_get("brave-twilight").unwrap().unwrap();
assert_eq!(got.target_id, "T2");
}
#[test]
fn touch_bumps_last_used() {
let reg = Registry::open_in_memory().unwrap();
reg.scratch_upsert("a", "T1").unwrap();
let first = reg.scratch_get("a").unwrap().unwrap().last_used_at_epoch_s;
reg.scratch_touch("a").unwrap();
let later = reg.scratch_get("a").unwrap().unwrap().last_used_at_epoch_s;
assert!(later >= first);
}
#[test]
fn delete_removes_the_row() {
let reg = Registry::open_in_memory().unwrap();
reg.scratch_upsert("a", "T1").unwrap();
reg.scratch_delete("a").unwrap();
assert!(reg.scratch_get("a").unwrap().is_none());
}
#[test]
fn touch_on_missing_row_is_noop() {
let reg = Registry::open_in_memory().unwrap();
reg.scratch_touch("nonexistent").unwrap();
}
}