Skip to main content

codex_mobile_bridge/storage/
mod.rs

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