codex_mobile_bridge/storage/
directories.rs1use std::path::Path;
2
3use rusqlite::{Connection, params};
4
5use super::Storage;
6use crate::bridge_protocol::{DirectoryBookmarkRecord, DirectoryHistoryRecord, now_millis};
7use crate::directory::{
8 canonicalize_directory, default_display_name, normalize_absolute_directory,
9};
10
11impl Storage {
12 pub fn list_directory_bookmarks(&self) -> anyhow::Result<Vec<DirectoryBookmarkRecord>> {
13 let conn = self.connect()?;
14 let mut stmt = conn.prepare(
15 "SELECT path, display_name, created_at_ms, updated_at_ms
16 FROM directory_bookmarks
17 ORDER BY display_name COLLATE NOCASE ASC",
18 )?;
19
20 let rows = stmt.query_map([], |row| {
21 Ok(DirectoryBookmarkRecord {
22 path: row.get(0)?,
23 display_name: row.get(1)?,
24 created_at_ms: row.get(2)?,
25 updated_at_ms: row.get(3)?,
26 })
27 })?;
28
29 Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
30 }
31
32 pub fn upsert_directory_bookmark(
33 &self,
34 path: &Path,
35 display_name: Option<&str>,
36 ) -> anyhow::Result<DirectoryBookmarkRecord> {
37 let canonical = canonicalize_directory(path)?;
38 let path_string = canonical.to_string_lossy().to_string();
39 let now = now_millis();
40 let conn = self.connect()?;
41 let display_name = display_name
42 .map(str::trim)
43 .filter(|value| !value.is_empty())
44 .map(ToOwned::to_owned)
45 .unwrap_or_else(|| default_display_name(&canonical));
46 let inserted = insert_directory_bookmark_row(&conn, &path_string, &display_name, now, now)?;
47 if inserted == 0 {
48 update_directory_bookmark_row(&conn, &path_string, &display_name, now)?;
49 }
50 let created_at_ms = directory_bookmark_created_at(&conn, &path_string)?;
51
52 Ok(DirectoryBookmarkRecord {
53 path: canonical.to_string_lossy().to_string(),
54 display_name,
55 created_at_ms,
56 updated_at_ms: now,
57 })
58 }
59
60 pub fn remove_directory_bookmark(&self, path: &Path) -> anyhow::Result<()> {
61 let canonical = normalize_absolute_directory(path)?;
62 let conn = self.connect()?;
63 conn.execute(
64 "DELETE FROM directory_bookmarks WHERE path = ?1",
65 params![canonical.to_string_lossy().to_string()],
66 )?;
67 Ok(())
68 }
69
70 pub fn list_directory_history(
71 &self,
72 limit: usize,
73 ) -> anyhow::Result<Vec<DirectoryHistoryRecord>> {
74 let conn = self.connect()?;
75 let mut stmt = conn.prepare(
76 "SELECT path, last_used_at_ms, use_count
77 FROM directory_history
78 ORDER BY last_used_at_ms DESC
79 LIMIT ?1",
80 )?;
81 let rows = stmt.query_map(params![limit.max(1) as i64], |row| {
82 let path: String = row.get(0)?;
83 Ok(DirectoryHistoryRecord {
84 display_name: default_display_name(Path::new(&path)),
85 path,
86 last_used_at_ms: row.get(1)?,
87 use_count: row.get(2)?,
88 })
89 })?;
90 Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
91 }
92
93 pub fn record_directory_usage(&self, path: &Path) -> anyhow::Result<DirectoryHistoryRecord> {
94 let normalized = normalize_absolute_directory(path)?;
95 let path_string = normalized.to_string_lossy().to_string();
96 let now = now_millis();
97 let conn = self.connect()?;
98 let inserted = insert_directory_history_row(&conn, &path_string, now)?;
99 if inserted == 0 {
100 increment_directory_history_row(&conn, &path_string, now)?;
101 }
102 let use_count = directory_history_use_count(&conn, &path_string)?;
103 Ok(DirectoryHistoryRecord {
104 path: normalized.to_string_lossy().to_string(),
105 display_name: default_display_name(&normalized),
106 last_used_at_ms: now,
107 use_count,
108 })
109 }
110}
111
112fn update_directory_bookmark_row(
113 conn: &Connection,
114 path: &str,
115 display_name: &str,
116 updated_at_ms: i64,
117) -> rusqlite::Result<()> {
118 conn.execute(
119 "UPDATE directory_bookmarks
120 SET display_name = ?2, updated_at_ms = ?3
121 WHERE path = ?1",
122 params![path, display_name, updated_at_ms],
123 )?;
124 Ok(())
125}
126
127fn directory_bookmark_created_at(conn: &Connection, path: &str) -> rusqlite::Result<i64> {
128 conn.query_row(
129 "SELECT created_at_ms FROM directory_bookmarks WHERE path = ?1",
130 params![path],
131 |row| row.get(0),
132 )
133}
134
135fn insert_directory_bookmark_row(
136 conn: &Connection,
137 path: &str,
138 display_name: &str,
139 created_at_ms: i64,
140 updated_at_ms: i64,
141) -> rusqlite::Result<usize> {
142 conn.execute(
143 "INSERT OR IGNORE INTO directory_bookmarks (path, display_name, created_at_ms, updated_at_ms)
144 VALUES (?1, ?2, ?3, ?4)",
145 params![path, display_name, created_at_ms, updated_at_ms],
146 )
147}
148
149fn directory_history_use_count(conn: &Connection, path: &str) -> rusqlite::Result<i64> {
150 conn.query_row(
151 "SELECT use_count FROM directory_history WHERE path = ?1",
152 params![path],
153 |row| row.get(0),
154 )
155}
156
157fn increment_directory_history_row(
158 conn: &Connection,
159 path: &str,
160 last_used_at_ms: i64,
161) -> rusqlite::Result<()> {
162 conn.execute(
163 "UPDATE directory_history
164 SET last_used_at_ms = ?2, use_count = use_count + 1
165 WHERE path = ?1",
166 params![path, last_used_at_ms],
167 )?;
168 Ok(())
169}
170
171fn insert_directory_history_row(
172 conn: &Connection,
173 path: &str,
174 last_used_at_ms: i64,
175) -> rusqlite::Result<usize> {
176 conn.execute(
177 "INSERT OR IGNORE INTO directory_history (path, last_used_at_ms, use_count)
178 VALUES (?1, ?2, 1)",
179 params![path, last_used_at_ms],
180 )
181}