codex_mobile_bridge/storage/
mod.rs1mod decode;
2mod directories;
3mod events;
4mod pending_requests;
5mod runtimes;
6mod schema;
7#[cfg(test)]
8mod tests;
9mod threads;
10
11use std::fs;
12use std::path::PathBuf;
13
14use anyhow::{Context, Result};
15use rusqlite::Connection;
16
17pub const PRIMARY_RUNTIME_ID: &str = "primary";
18
19#[derive(Debug, Clone)]
20pub struct Storage {
21 db_path: PathBuf,
22}
23
24impl Storage {
25 pub fn open(db_path: PathBuf) -> Result<Self> {
26 if let Some(parent) = db_path.parent() {
27 fs::create_dir_all(parent)
28 .with_context(|| format!("创建数据库目录失败: {}", parent.display()))?;
29 }
30
31 let storage = Self { db_path };
32 storage.migrate()?;
33 storage.clear_pending_requests()?;
34 storage.clear_legacy_pending_approvals()?;
35 Ok(storage)
36 }
37
38 fn clear_legacy_pending_approvals(&self) -> Result<()> {
39 let conn = self.connect()?;
40 conn.execute("DELETE FROM pending_approvals", [])?;
41 Ok(())
42 }
43
44 fn connect(&self) -> Result<Connection> {
45 let conn = Connection::open(&self.db_path)
46 .with_context(|| format!("打开数据库失败: {}", self.db_path.display()))?;
47 conn.execute_batch(
48 "PRAGMA foreign_keys = ON;
49 PRAGMA journal_mode = WAL;",
50 )?;
51 Ok(conn)
52 }
53
54 fn migrate(&self) -> Result<()> {
55 let conn = self.connect()?;
56 conn.execute_batch(
57 "CREATE TABLE IF NOT EXISTS directory_bookmarks (
58 path TEXT PRIMARY KEY,
59 display_name TEXT NOT NULL,
60 created_at_ms INTEGER NOT NULL,
61 updated_at_ms INTEGER NOT NULL
62 );
63
64 CREATE TABLE IF NOT EXISTS directory_history (
65 path TEXT PRIMARY KEY,
66 last_used_at_ms INTEGER NOT NULL,
67 use_count INTEGER NOT NULL
68 );
69
70 CREATE TABLE IF NOT EXISTS runtimes (
71 runtime_id TEXT PRIMARY KEY,
72 display_name TEXT NOT NULL,
73 codex_home TEXT NULL,
74 codex_binary TEXT NOT NULL,
75 is_primary INTEGER NOT NULL,
76 auto_start INTEGER NOT NULL,
77 created_at_ms INTEGER NOT NULL,
78 updated_at_ms INTEGER NOT NULL,
79 raw_json TEXT NOT NULL
80 );
81
82 CREATE TABLE IF NOT EXISTS mobile_sessions (
83 device_id TEXT PRIMARY KEY,
84 last_ack_seq INTEGER NOT NULL,
85 updated_at_ms INTEGER NOT NULL
86 );
87
88 CREATE TABLE IF NOT EXISTS events (
89 seq INTEGER PRIMARY KEY AUTOINCREMENT,
90 event_type TEXT NOT NULL,
91 runtime_id TEXT NULL,
92 thread_id TEXT NULL,
93 payload TEXT NOT NULL,
94 created_at_ms INTEGER NOT NULL
95 );
96
97 CREATE TABLE IF NOT EXISTS pending_approvals (
98 approval_id TEXT PRIMARY KEY,
99 runtime_id TEXT NOT NULL DEFAULT 'primary',
100 thread_id TEXT NOT NULL,
101 turn_id TEXT NOT NULL,
102 item_id TEXT NOT NULL,
103 kind TEXT NOT NULL,
104 reason TEXT NULL,
105 command TEXT NULL,
106 cwd TEXT NULL,
107 grant_root TEXT NULL,
108 available_decisions TEXT NOT NULL,
109 created_at_ms INTEGER NOT NULL,
110 raw_json TEXT NOT NULL
111 );
112
113 CREATE TABLE IF NOT EXISTS pending_server_requests (
114 request_id TEXT PRIMARY KEY,
115 runtime_id TEXT NOT NULL DEFAULT 'primary',
116 request_type TEXT NOT NULL,
117 thread_id TEXT NULL,
118 turn_id TEXT NULL,
119 item_id TEXT NULL,
120 title TEXT NULL,
121 reason TEXT NULL,
122 command TEXT NULL,
123 cwd TEXT NULL,
124 grant_root TEXT NULL,
125 tool_name TEXT NULL,
126 arguments TEXT NULL,
127 questions TEXT NOT NULL,
128 proposed_execpolicy_amendment TEXT NULL,
129 network_approval_context TEXT NULL,
130 schema TEXT NULL,
131 available_decisions TEXT NOT NULL,
132 raw_payload TEXT NOT NULL,
133 created_at_ms INTEGER NOT NULL,
134 raw_json TEXT NOT NULL
135 );",
136 )?;
137
138 schema::ensure_thread_index_schema(&conn)?;
139 schema::migrate_legacy_workspaces(&conn)?;
140 schema::ensure_column(
141 &conn,
142 "thread_index",
143 "runtime_id",
144 "TEXT NOT NULL DEFAULT 'primary'",
145 )?;
146 schema::ensure_column(&conn, "thread_index", "note", "TEXT NULL")?;
147 schema::ensure_column(
148 &conn,
149 "thread_index",
150 "archived",
151 "INTEGER NOT NULL DEFAULT 0",
152 )?;
153 schema::ensure_column(&conn, "events", "runtime_id", "TEXT NULL")?;
154 schema::ensure_column(
155 &conn,
156 "pending_approvals",
157 "runtime_id",
158 "TEXT NOT NULL DEFAULT 'primary'",
159 )?;
160 schema::ensure_column(
161 &conn,
162 "pending_server_requests",
163 "runtime_id",
164 "TEXT NOT NULL DEFAULT 'primary'",
165 )?;
166
167 Ok(())
168 }
169}