codex_mobile_bridge/storage/
threads.rs1use std::path::Path;
2
3use rusqlite::{OptionalExtension, params, params_from_iter};
4
5use super::Storage;
6use super::decode::decode_thread_row;
7use crate::bridge_protocol::ThreadSummary;
8use crate::directory::{directory_contains, normalize_absolute_directory};
9
10impl Storage {
11 pub fn upsert_thread_index(&self, thread: &ThreadSummary) -> anyhow::Result<()> {
12 let conn = self.connect()?;
13 conn.execute(
14 "INSERT INTO thread_index (
15 thread_id, runtime_id, name, preview, cwd, status,
16 model_provider, source, created_at_ms, updated_at_ms, is_loaded, is_active,
17 archived, raw_json
18 )
19 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)
20 ON CONFLICT(thread_id) DO UPDATE SET
21 runtime_id = excluded.runtime_id,
22 name = excluded.name,
23 preview = excluded.preview,
24 cwd = excluded.cwd,
25 status = excluded.status,
26 model_provider = excluded.model_provider,
27 source = excluded.source,
28 created_at_ms = excluded.created_at_ms,
29 updated_at_ms = excluded.updated_at_ms,
30 is_loaded = excluded.is_loaded,
31 is_active = excluded.is_active,
32 archived = excluded.archived,
33 raw_json = excluded.raw_json",
34 params![
35 thread.id,
36 thread.runtime_id,
37 thread.name,
38 thread.preview,
39 thread.cwd,
40 thread.status,
41 thread.model_provider,
42 thread.source,
43 thread.created_at,
44 thread.updated_at,
45 if thread.is_loaded { 1_i64 } else { 0_i64 },
46 if thread.is_active { 1_i64 } else { 0_i64 },
47 if thread.archived { 1_i64 } else { 0_i64 },
48 serde_json::to_string(thread)?
49 ],
50 )?;
51 Ok(())
52 }
53
54 pub fn get_thread_index(&self, thread_id: &str) -> anyhow::Result<Option<ThreadSummary>> {
55 let conn = self.connect()?;
56 let record = conn
57 .query_row(
58 "SELECT raw_json, archived FROM thread_index WHERE thread_id = ?1",
59 params![thread_id],
60 |row| decode_thread_row(row.get::<_, String>(0)?, row.get::<_, i64>(1)?),
61 )
62 .optional()?;
63 Ok(record)
64 }
65
66 pub fn list_thread_index(
67 &self,
68 directory_prefix: Option<&str>,
69 runtime_id: Option<&str>,
70 archived: Option<bool>,
71 search_term: Option<&str>,
72 ) -> anyhow::Result<Vec<ThreadSummary>> {
73 let conn = self.connect()?;
74 let mut sql = String::from(
75 "SELECT raw_json, archived
76 FROM thread_index",
77 );
78 let mut clauses = Vec::new();
79 let mut values = Vec::new();
80
81 if let Some(runtime_id) = runtime_id {
82 clauses.push("runtime_id = ?");
83 values.push(rusqlite::types::Value::from(runtime_id.to_string()));
84 }
85
86 if let Some(archived) = archived {
87 clauses.push("archived = ?");
88 values.push(rusqlite::types::Value::from(if archived {
89 1_i64
90 } else {
91 0_i64
92 }));
93 }
94
95 if let Some(search_term) = search_term.filter(|value| !value.trim().is_empty()) {
96 clauses.push(
97 "(LOWER(COALESCE(name, '')) LIKE ? OR LOWER(preview) LIKE ? OR LOWER(cwd) LIKE ?)",
98 );
99 let pattern = format!("%{}%", search_term.trim().to_lowercase());
100 values.push(rusqlite::types::Value::from(pattern.clone()));
101 values.push(rusqlite::types::Value::from(pattern.clone()));
102 values.push(rusqlite::types::Value::from(pattern));
103 }
104
105 if !clauses.is_empty() {
106 sql.push_str(" WHERE ");
107 sql.push_str(&clauses.join(" AND "));
108 }
109 sql.push_str(" ORDER BY updated_at_ms DESC");
110
111 let mut stmt = conn.prepare(&sql)?;
112 let rows = stmt.query_map(params_from_iter(values), |row| {
113 decode_thread_row(row.get::<_, String>(0)?, row.get::<_, i64>(1)?)
114 })?;
115
116 let mut threads = rows.collect::<rusqlite::Result<Vec<_>>>()?;
117
118 if let Some(directory_prefix) = directory_prefix {
119 let prefix = normalize_absolute_directory(Path::new(directory_prefix))?;
120 threads.retain(|thread| directory_contains(&prefix, Path::new(&thread.cwd)));
121 }
122
123 Ok(threads)
124 }
125 pub fn set_thread_archived(&self, thread_id: &str, archived: bool) -> anyhow::Result<()> {
126 let conn = self.connect()?;
127 conn.execute(
128 "UPDATE thread_index
129 SET archived = ?2
130 WHERE thread_id = ?1",
131 params![thread_id, if archived { 1_i64 } else { 0_i64 }],
132 )?;
133 Ok(())
134 }
135}