browser_control/registry/
foreground.rs1use anyhow::Result;
7
8use crate::registry::{db, now_epoch_s, pid_alive, Registry};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ForegroundRow {
13 pub browser_name: String,
14 pub target_id: String,
15 pub pid: u32,
16 pub started_at_epoch_s: i64,
17 pub expires_at_epoch_s: i64,
19}
20
21fn row_to_fg(r: &rusqlite::Row<'_>) -> Result<ForegroundRow> {
22 Ok(ForegroundRow {
23 browser_name: r.get(0)?,
24 target_id: r.get(1)?,
25 pid: r.get::<_, i64>(2)? as u32,
26 started_at_epoch_s: r.get(3)?,
27 expires_at_epoch_s: r.get(4)?,
28 })
29}
30
31impl Registry {
32 pub fn foreground_upsert(
34 &self,
35 browser_name: &str,
36 target_id: &str,
37 pid: u32,
38 expires_at_epoch_s: i64,
39 ) -> Result<()> {
40 db::execute(
41 &self.conn,
42 "INSERT INTO foreground_holders \
43 (browser_name, target_id, pid, started_at_epoch_s, expires_at_epoch_s) \
44 VALUES (?1, ?2, ?3, ?4, ?5) \
45 ON CONFLICT(browser_name, target_id) DO UPDATE SET \
46 pid = excluded.pid, \
47 started_at_epoch_s = excluded.started_at_epoch_s, \
48 expires_at_epoch_s = excluded.expires_at_epoch_s",
49 rusqlite::params![
50 browser_name,
51 target_id,
52 pid as i64,
53 now_epoch_s(),
54 expires_at_epoch_s
55 ],
56 || format!("upsert foreground holder {browser_name}/{target_id}"),
57 )
58 }
59
60 pub fn foreground_get(
62 &self,
63 browser_name: &str,
64 target_id: &str,
65 ) -> Result<Option<ForegroundRow>> {
66 let row = db::query_optional(
67 &self.conn,
68 "SELECT browser_name, target_id, pid, started_at_epoch_s, expires_at_epoch_s \
69 FROM foreground_holders WHERE browser_name = ?1 AND target_id = ?2",
70 rusqlite::params![browser_name, target_id],
71 row_to_fg,
72 )?;
73 match row {
74 Some(r) if pid_alive(r.pid) => Ok(Some(r)),
75 Some(r) => {
76 self.foreground_delete(browser_name, target_id)?;
77 let _ = r;
78 Ok(None)
79 }
80 None => Ok(None),
81 }
82 }
83
84 pub fn foreground_list(&self, browser_name: &str) -> Result<Vec<ForegroundRow>> {
86 let rows = db::query_vec(
87 &self.conn,
88 "SELECT browser_name, target_id, pid, started_at_epoch_s, expires_at_epoch_s \
89 FROM foreground_holders WHERE browser_name = ?1 ORDER BY target_id",
90 [browser_name],
91 row_to_fg,
92 )?;
93 let mut live = Vec::with_capacity(rows.len());
94 for r in rows {
95 if pid_alive(r.pid) {
96 live.push(r);
97 } else {
98 self.foreground_delete(&r.browser_name, &r.target_id)?;
99 }
100 }
101 Ok(live)
102 }
103
104 pub fn foreground_delete(&self, browser_name: &str, target_id: &str) -> Result<()> {
106 db::execute_bare(
107 &self.conn,
108 "DELETE FROM foreground_holders WHERE browser_name = ?1 AND target_id = ?2",
109 rusqlite::params![browser_name, target_id],
110 )
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn tmp_registry() -> (tempfile::TempDir, Registry) {
119 let td = tempfile::TempDir::new().unwrap();
120 let reg = Registry::open_at(&td.path().join("registry.db")).unwrap();
121 (td, reg)
122 }
123
124 #[test]
125 fn upsert_get_list_and_prune_dead_pid() {
126 let (_td, reg) = tmp_registry();
127 let me = std::process::id();
128 reg.foreground_upsert("brave-x", "T1", me, 0).unwrap();
129 reg.foreground_upsert("brave-x", "T2", 999_999_999, 0)
130 .unwrap();
131 let live = reg.foreground_get("brave-x", "T1").unwrap().unwrap();
132 assert_eq!(live.pid, me);
133 assert!(reg.foreground_get("brave-x", "T2").unwrap().is_none());
134 let list = reg.foreground_list("brave-x").unwrap();
135 assert_eq!(list.len(), 1);
136 assert_eq!(list[0].target_id, "T1");
137 reg.foreground_delete("brave-x", "T1").unwrap();
138 assert!(reg.foreground_list("brave-x").unwrap().is_empty());
139 }
140}