Skip to main content

codex_mobile_bridge/storage/
directories.rs

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