browser_control/registry/
scratches.rs1use anyhow::Result;
21
22use crate::registry::{db, now_epoch_s, Registry};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct ScratchRow {
27 pub browser_name: String,
28 pub target_id: String,
29 pub last_used_at_epoch_s: i64,
30}
31
32impl Registry {
33 pub fn scratch_get(&self, browser_name: &str) -> Result<Option<ScratchRow>> {
35 db::query_optional(
36 &self.conn,
37 "SELECT browser_name, target_id, last_used_at_epoch_s \
38 FROM scratches WHERE browser_name = ?1",
39 [browser_name],
40 |r| {
41 Ok(ScratchRow {
42 browser_name: r.get(0)?,
43 target_id: r.get(1)?,
44 last_used_at_epoch_s: r.get(2)?,
45 })
46 },
47 )
48 }
49
50 pub fn scratch_upsert(&self, browser_name: &str, target_id: &str) -> Result<()> {
53 let now = now_epoch_s();
54 db::execute(
55 &self.conn,
56 "INSERT INTO scratches (browser_name, target_id, last_used_at_epoch_s) \
57 VALUES (?1, ?2, ?3) \
58 ON CONFLICT(browser_name) DO UPDATE SET \
59 target_id = excluded.target_id, \
60 last_used_at_epoch_s = excluded.last_used_at_epoch_s",
61 rusqlite::params![browser_name, target_id, now],
62 || format!("upsert scratch for {browser_name}"),
63 )
64 }
65
66 pub fn scratch_touch(&self, browser_name: &str) -> Result<()> {
70 let now = now_epoch_s();
71 db::execute_bare(
72 &self.conn,
73 "UPDATE scratches SET last_used_at_epoch_s = ?1 WHERE browser_name = ?2",
74 rusqlite::params![now, browser_name],
75 )
76 }
77
78 pub fn scratch_delete(&self, browser_name: &str) -> Result<()> {
81 db::execute_bare(
82 &self.conn,
83 "DELETE FROM scratches WHERE browser_name = ?1",
84 [browser_name],
85 )
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn upsert_then_get_round_trip() {
95 let reg = Registry::open_in_memory().unwrap();
96 assert!(reg.scratch_get("brave-twilight").unwrap().is_none());
97 reg.scratch_upsert("brave-twilight", "T1").unwrap();
98 let got = reg.scratch_get("brave-twilight").unwrap().unwrap();
99 assert_eq!(got.browser_name, "brave-twilight");
100 assert_eq!(got.target_id, "T1");
101 assert!(got.last_used_at_epoch_s > 0);
102 }
103
104 #[test]
105 fn upsert_replaces_existing_target_id() {
106 let reg = Registry::open_in_memory().unwrap();
107 reg.scratch_upsert("brave-twilight", "T1").unwrap();
108 reg.scratch_upsert("brave-twilight", "T2").unwrap();
109 let got = reg.scratch_get("brave-twilight").unwrap().unwrap();
110 assert_eq!(got.target_id, "T2");
111 }
112
113 #[test]
114 fn touch_bumps_last_used() {
115 let reg = Registry::open_in_memory().unwrap();
116 reg.scratch_upsert("a", "T1").unwrap();
117 let first = reg.scratch_get("a").unwrap().unwrap().last_used_at_epoch_s;
118 reg.scratch_touch("a").unwrap();
121 let later = reg.scratch_get("a").unwrap().unwrap().last_used_at_epoch_s;
122 assert!(later >= first);
123 }
124
125 #[test]
126 fn delete_removes_the_row() {
127 let reg = Registry::open_in_memory().unwrap();
128 reg.scratch_upsert("a", "T1").unwrap();
129 reg.scratch_delete("a").unwrap();
130 assert!(reg.scratch_get("a").unwrap().is_none());
131 }
132
133 #[test]
134 fn touch_on_missing_row_is_noop() {
135 let reg = Registry::open_in_memory().unwrap();
136 reg.scratch_touch("nonexistent").unwrap();
138 }
139}