codex_mobile_bridge/storage/
runtimes.rs1use rusqlite::{OptionalExtension, params};
2
3use super::decode::decode_json_row;
4use super::{PRIMARY_RUNTIME_ID, Storage};
5use crate::bridge_protocol::{RuntimeRecord, now_millis};
6
7impl Storage {
8 pub fn ensure_primary_runtime(
9 &self,
10 codex_home: Option<String>,
11 codex_binary: String,
12 ) -> anyhow::Result<RuntimeRecord> {
13 if let Some(existing) = self.get_runtime(PRIMARY_RUNTIME_ID)? {
14 let desired_home = codex_home.or(existing.codex_home.clone());
15 let desired_binary = if codex_binary.trim().is_empty() {
16 existing.codex_binary.clone()
17 } else {
18 codex_binary
19 };
20 let needs_update = existing.codex_home != desired_home
21 || existing.codex_binary != desired_binary
22 || !existing.is_primary
23 || !existing.auto_start;
24
25 if !needs_update {
26 return Ok(existing);
27 }
28
29 let updated = RuntimeRecord {
30 codex_home: desired_home,
31 codex_binary: desired_binary,
32 is_primary: true,
33 auto_start: true,
34 updated_at_ms: now_millis(),
35 ..existing
36 };
37 self.upsert_runtime(&updated)?;
38 return Ok(updated);
39 }
40
41 let now = now_millis();
42 let record = RuntimeRecord {
43 runtime_id: PRIMARY_RUNTIME_ID.to_string(),
44 display_name: "Primary".to_string(),
45 codex_home,
46 codex_binary,
47 is_primary: true,
48 auto_start: true,
49 created_at_ms: now,
50 updated_at_ms: now,
51 };
52 self.upsert_runtime(&record)?;
53 Ok(record)
54 }
55
56 pub fn list_runtimes(&self) -> anyhow::Result<Vec<RuntimeRecord>> {
57 let conn = self.connect()?;
58 let mut stmt = conn.prepare(
59 "SELECT raw_json
60 FROM runtimes
61 ORDER BY is_primary DESC, created_at_ms ASC",
62 )?;
63
64 let rows = stmt.query_map([], |row| {
65 let raw: String = row.get(0)?;
66 decode_json_row(raw)
67 })?;
68
69 Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
70 }
71
72 pub fn get_runtime(&self, runtime_id: &str) -> anyhow::Result<Option<RuntimeRecord>> {
73 let conn = self.connect()?;
74 let record = conn
75 .query_row(
76 "SELECT raw_json FROM runtimes WHERE runtime_id = ?1",
77 params![runtime_id],
78 |row| {
79 let raw: String = row.get(0)?;
80 decode_json_row(raw)
81 },
82 )
83 .optional()?;
84 Ok(record)
85 }
86
87 pub fn upsert_runtime(&self, runtime: &RuntimeRecord) -> anyhow::Result<()> {
88 let conn = self.connect()?;
89 conn.execute(
90 "INSERT INTO runtimes (
91 runtime_id, display_name, codex_home, codex_binary, is_primary,
92 auto_start, created_at_ms, updated_at_ms, raw_json
93 )
94 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
95 ON CONFLICT(runtime_id) DO UPDATE SET
96 display_name = excluded.display_name,
97 codex_home = excluded.codex_home,
98 codex_binary = excluded.codex_binary,
99 is_primary = excluded.is_primary,
100 auto_start = excluded.auto_start,
101 created_at_ms = excluded.created_at_ms,
102 updated_at_ms = excluded.updated_at_ms,
103 raw_json = excluded.raw_json",
104 params![
105 runtime.runtime_id,
106 runtime.display_name,
107 runtime.codex_home,
108 runtime.codex_binary,
109 if runtime.is_primary { 1_i64 } else { 0_i64 },
110 if runtime.auto_start { 1_i64 } else { 0_i64 },
111 runtime.created_at_ms,
112 runtime.updated_at_ms,
113 serde_json::to_string(runtime)?,
114 ],
115 )?;
116 Ok(())
117 }
118
119 pub fn remove_runtime(&self, runtime_id: &str) -> anyhow::Result<()> {
120 let conn = self.connect()?;
121 conn.execute(
122 "DELETE FROM runtimes WHERE runtime_id = ?1",
123 params![runtime_id],
124 )?;
125 Ok(())
126 }
127}