Skip to main content

codex_mobile_bridge/storage/
mod.rs

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