use std::path::Path;
use rusqlite::{OptionalExtension, params};
use super::Storage;
use crate::bridge_protocol::{DirectoryBookmarkRecord, DirectoryHistoryRecord, now_millis};
use crate::directory::{
canonicalize_directory, default_display_name, normalize_absolute_directory,
};
impl Storage {
pub fn list_directory_bookmarks(&self) -> anyhow::Result<Vec<DirectoryBookmarkRecord>> {
let conn = self.connect()?;
let mut stmt = conn.prepare(
"SELECT path, display_name, created_at_ms, updated_at_ms
FROM directory_bookmarks
ORDER BY display_name COLLATE NOCASE ASC",
)?;
let rows = stmt.query_map([], |row| {
Ok(DirectoryBookmarkRecord {
path: row.get(0)?,
display_name: row.get(1)?,
created_at_ms: row.get(2)?,
updated_at_ms: row.get(3)?,
})
})?;
Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
}
pub fn upsert_directory_bookmark(
&self,
path: &Path,
display_name: Option<&str>,
) -> anyhow::Result<DirectoryBookmarkRecord> {
let canonical = canonicalize_directory(path)?;
let path_string = canonical.to_string_lossy().to_string();
let now = now_millis();
let conn = self.connect()?;
let existing = conn
.query_row(
"SELECT created_at_ms FROM directory_bookmarks WHERE path = ?1",
params![path_string],
|row| row.get::<_, i64>(0),
)
.optional()?;
let created_at_ms = existing.unwrap_or(now);
let display_name = display_name
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.unwrap_or_else(|| default_display_name(&canonical));
conn.execute(
"INSERT INTO directory_bookmarks (path, display_name, created_at_ms, updated_at_ms)
VALUES (?1, ?2, ?3, ?4)
ON CONFLICT(path) DO UPDATE SET
display_name = excluded.display_name,
updated_at_ms = excluded.updated_at_ms",
params![path_string, display_name, created_at_ms, now],
)?;
Ok(DirectoryBookmarkRecord {
path: canonical.to_string_lossy().to_string(),
display_name,
created_at_ms,
updated_at_ms: now,
})
}
pub fn remove_directory_bookmark(&self, path: &Path) -> anyhow::Result<()> {
let canonical = normalize_absolute_directory(path)?;
let conn = self.connect()?;
conn.execute(
"DELETE FROM directory_bookmarks WHERE path = ?1",
params![canonical.to_string_lossy().to_string()],
)?;
Ok(())
}
pub fn list_directory_history(&self, limit: usize) -> anyhow::Result<Vec<DirectoryHistoryRecord>> {
let conn = self.connect()?;
let mut stmt = conn.prepare(
"SELECT path, last_used_at_ms, use_count
FROM directory_history
ORDER BY last_used_at_ms DESC
LIMIT ?1",
)?;
let rows = stmt.query_map(params![limit.max(1) as i64], |row| {
let path: String = row.get(0)?;
Ok(DirectoryHistoryRecord {
display_name: default_display_name(Path::new(&path)),
path,
last_used_at_ms: row.get(1)?,
use_count: row.get(2)?,
})
})?;
Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
}
pub fn record_directory_usage(&self, path: &Path) -> anyhow::Result<DirectoryHistoryRecord> {
let normalized = normalize_absolute_directory(path)?;
let path_string = normalized.to_string_lossy().to_string();
let now = now_millis();
let conn = self.connect()?;
let existing = conn
.query_row(
"SELECT use_count FROM directory_history WHERE path = ?1",
params![&path_string],
|row| row.get::<_, i64>(0),
)
.optional()?
.unwrap_or(0);
let use_count = existing + 1;
conn.execute(
"INSERT INTO directory_history (path, last_used_at_ms, use_count)
VALUES (?1, ?2, ?3)
ON CONFLICT(path) DO UPDATE SET
last_used_at_ms = excluded.last_used_at_ms,
use_count = directory_history.use_count + 1",
params![path_string, now, use_count],
)?;
Ok(DirectoryHistoryRecord {
path: normalized.to_string_lossy().to_string(),
display_name: default_display_name(&normalized),
last_used_at_ms: now,
use_count,
})
}
}