Skip to main content

codex_mobile_bridge/storage/
runtimes.rs

1use 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}