use anyhow::Result;
use crate::registry::{db, now_epoch_s, pid_alive, Registry};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForegroundRow {
pub browser_name: String,
pub target_id: String,
pub pid: u32,
pub started_at_epoch_s: i64,
pub expires_at_epoch_s: i64,
}
fn row_to_fg(r: &rusqlite::Row<'_>) -> Result<ForegroundRow> {
Ok(ForegroundRow {
browser_name: r.get(0)?,
target_id: r.get(1)?,
pid: r.get::<_, i64>(2)? as u32,
started_at_epoch_s: r.get(3)?,
expires_at_epoch_s: r.get(4)?,
})
}
impl Registry {
pub fn foreground_upsert(
&self,
browser_name: &str,
target_id: &str,
pid: u32,
expires_at_epoch_s: i64,
) -> Result<()> {
db::execute(
&self.conn,
"INSERT INTO foreground_holders \
(browser_name, target_id, pid, started_at_epoch_s, expires_at_epoch_s) \
VALUES (?1, ?2, ?3, ?4, ?5) \
ON CONFLICT(browser_name, target_id) DO UPDATE SET \
pid = excluded.pid, \
started_at_epoch_s = excluded.started_at_epoch_s, \
expires_at_epoch_s = excluded.expires_at_epoch_s",
rusqlite::params![
browser_name,
target_id,
pid as i64,
now_epoch_s(),
expires_at_epoch_s
],
|| format!("upsert foreground holder {browser_name}/{target_id}"),
)
}
pub fn foreground_get(
&self,
browser_name: &str,
target_id: &str,
) -> Result<Option<ForegroundRow>> {
let row = db::query_optional(
&self.conn,
"SELECT browser_name, target_id, pid, started_at_epoch_s, expires_at_epoch_s \
FROM foreground_holders WHERE browser_name = ?1 AND target_id = ?2",
rusqlite::params![browser_name, target_id],
row_to_fg,
)?;
match row {
Some(r) if pid_alive(r.pid) => Ok(Some(r)),
Some(r) => {
self.foreground_delete(browser_name, target_id)?;
let _ = r;
Ok(None)
}
None => Ok(None),
}
}
pub fn foreground_list(&self, browser_name: &str) -> Result<Vec<ForegroundRow>> {
let rows = db::query_vec(
&self.conn,
"SELECT browser_name, target_id, pid, started_at_epoch_s, expires_at_epoch_s \
FROM foreground_holders WHERE browser_name = ?1 ORDER BY target_id",
[browser_name],
row_to_fg,
)?;
let mut live = Vec::with_capacity(rows.len());
for r in rows {
if pid_alive(r.pid) {
live.push(r);
} else {
self.foreground_delete(&r.browser_name, &r.target_id)?;
}
}
Ok(live)
}
pub fn foreground_delete(&self, browser_name: &str, target_id: &str) -> Result<()> {
db::execute_bare(
&self.conn,
"DELETE FROM foreground_holders WHERE browser_name = ?1 AND target_id = ?2",
rusqlite::params![browser_name, target_id],
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tmp_registry() -> (tempfile::TempDir, Registry) {
let td = tempfile::TempDir::new().unwrap();
let reg = Registry::open_at(&td.path().join("registry.db")).unwrap();
(td, reg)
}
#[test]
fn upsert_get_list_and_prune_dead_pid() {
let (_td, reg) = tmp_registry();
let me = std::process::id();
reg.foreground_upsert("brave-x", "T1", me, 0).unwrap();
reg.foreground_upsert("brave-x", "T2", 999_999_999, 0)
.unwrap();
let live = reg.foreground_get("brave-x", "T1").unwrap().unwrap();
assert_eq!(live.pid, me);
assert!(reg.foreground_get("brave-x", "T2").unwrap().is_none());
let list = reg.foreground_list("brave-x").unwrap();
assert_eq!(list.len(), 1);
assert_eq!(list[0].target_id, "T1");
reg.foreground_delete("brave-x", "T1").unwrap();
assert!(reg.foreground_list("brave-x").unwrap().is_empty());
}
}