hennery_kernel/
recents.rs1use crate::hats::{host_default_hat, is_canonical, resolve, rules_of};
7use crate::hosts::{Hosts, is_displayable_path};
8use anyhow::Result;
9use rusqlite::{TransactionBehavior, params};
10use std::collections::HashSet;
11
12pub const KEPT: usize = 50;
14
15pub const SHOWN: usize = 20;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Recent {
21 pub path: String,
22 pub last_used_at: i64,
24}
25
26impl Hosts {
27 pub fn remember(&self, host_id: &str, hat_id: &str, path: &str, now: i64) -> Result<bool> {
33 if !is_canonical(path) || !is_displayable_path(path) {
34 return Ok(false);
35 }
36 let owner = self.owner_id();
37 let mut conn = self.conn();
38 let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
39 tx.execute(
40 "INSERT INTO project_recents(owner_id, host_id, hat_id, path, last_used_at) VALUES (?1, ?2, ?3, ?4, ?5)
41 ON CONFLICT(host_id, hat_id, path) DO UPDATE SET last_used_at = max(last_used_at, excluded.last_used_at)
42 WHERE project_recents.owner_id = ?1",
43 params![owner, host_id, hat_id, path, now],
44 )?;
45 tx.execute(
46 "DELETE FROM project_recents WHERE owner_id = ?1 AND host_id = ?2 AND hat_id = ?3 AND path NOT IN (
47 SELECT path FROM project_recents WHERE owner_id = ?1 AND host_id = ?2 AND hat_id = ?3
48 ORDER BY last_used_at DESC, path LIMIT ?4)",
49 params![owner, host_id, hat_id, KEPT as i64],
50 )?;
51 tx.commit()?;
52 Ok(true)
53 }
54
55 pub fn recents(&self, host_id: &str, hat_id: &str, limit: usize) -> Result<Vec<Recent>> {
61 let conn = self.conn();
64 let Some(default_hat) = host_default_hat(&conn, self.owner_id(), host_id)? else {
65 return Ok(Vec::new());
66 };
67 let rules = rules_of(&conn, self.owner_id(), host_id)?;
68 let mut stmt = conn.prepare(
69 "SELECT path, last_used_at FROM project_recents WHERE owner_id = ?1 AND host_id = ?2
70 ORDER BY last_used_at DESC, path",
71 )?;
72 let rows = stmt.query_map(params![self.owner_id(), host_id], |r| {
73 Ok(Recent {
74 path: r.get(0)?,
75 last_used_at: r.get(1)?,
76 })
77 })?;
78 let mut seen = HashSet::new();
79 let mut shown = Vec::new();
80 for recent in rows {
81 let recent = recent?;
82 if resolve(&rules, &default_hat, &recent.path).hat_id == hat_id && seen.insert(recent.path.clone()) {
85 shown.push(recent);
86 if shown.len() == limit {
87 break;
88 }
89 }
90 }
91 Ok(shown)
92 }
93}