Skip to main content

codex_mobile_bridge/storage/
directories.rs

1use std::path::Path;
2
3use rusqlite::{OptionalExtension, 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 existing = conn
42            .query_row(
43                "SELECT created_at_ms FROM directory_bookmarks WHERE path = ?1",
44                params![path_string],
45                |row| row.get::<_, i64>(0),
46            )
47            .optional()?;
48        let created_at_ms = existing.unwrap_or(now);
49        let display_name = display_name
50            .map(str::trim)
51            .filter(|value| !value.is_empty())
52            .map(ToOwned::to_owned)
53            .unwrap_or_else(|| default_display_name(&canonical));
54        conn.execute(
55            "INSERT INTO directory_bookmarks (path, display_name, created_at_ms, updated_at_ms)
56             VALUES (?1, ?2, ?3, ?4)
57             ON CONFLICT(path) DO UPDATE SET
58                 display_name = excluded.display_name,
59                 updated_at_ms = excluded.updated_at_ms",
60            params![path_string, display_name, created_at_ms, now],
61        )?;
62
63        Ok(DirectoryBookmarkRecord {
64            path: canonical.to_string_lossy().to_string(),
65            display_name,
66            created_at_ms,
67            updated_at_ms: now,
68        })
69    }
70
71    pub fn remove_directory_bookmark(&self, path: &Path) -> anyhow::Result<()> {
72        let canonical = normalize_absolute_directory(path)?;
73        let conn = self.connect()?;
74        conn.execute(
75            "DELETE FROM directory_bookmarks WHERE path = ?1",
76            params![canonical.to_string_lossy().to_string()],
77        )?;
78        Ok(())
79    }
80
81    pub fn list_directory_history(
82        &self,
83        limit: usize,
84    ) -> anyhow::Result<Vec<DirectoryHistoryRecord>> {
85        let conn = self.connect()?;
86        let mut stmt = conn.prepare(
87            "SELECT path, last_used_at_ms, use_count
88             FROM directory_history
89             ORDER BY last_used_at_ms DESC
90             LIMIT ?1",
91        )?;
92        let rows = stmt.query_map(params![limit.max(1) as i64], |row| {
93            let path: String = row.get(0)?;
94            Ok(DirectoryHistoryRecord {
95                display_name: default_display_name(Path::new(&path)),
96                path,
97                last_used_at_ms: row.get(1)?,
98                use_count: row.get(2)?,
99            })
100        })?;
101        Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
102    }
103
104    pub fn record_directory_usage(&self, path: &Path) -> anyhow::Result<DirectoryHistoryRecord> {
105        let normalized = normalize_absolute_directory(path)?;
106        let path_string = normalized.to_string_lossy().to_string();
107        let now = now_millis();
108        let conn = self.connect()?;
109        let existing = conn
110            .query_row(
111                "SELECT use_count FROM directory_history WHERE path = ?1",
112                params![&path_string],
113                |row| row.get::<_, i64>(0),
114            )
115            .optional()?
116            .unwrap_or(0);
117        let use_count = existing + 1;
118        conn.execute(
119            "INSERT INTO directory_history (path, last_used_at_ms, use_count)
120             VALUES (?1, ?2, ?3)
121             ON CONFLICT(path) DO UPDATE SET
122                 last_used_at_ms = excluded.last_used_at_ms,
123                 use_count = directory_history.use_count + 1",
124            params![path_string, now, use_count],
125        )?;
126        Ok(DirectoryHistoryRecord {
127            path: normalized.to_string_lossy().to_string(),
128            display_name: default_display_name(&normalized),
129            last_used_at_ms: now,
130            use_count,
131        })
132    }
133}