Skip to main content

codex_mobile_bridge/storage/
mod.rs

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