use std::path::{Path, PathBuf};
use serde::Serialize;
#[derive(Serialize, Debug, Clone)]
pub struct RecentFile {
pub path: String,
pub name: String,
pub opened_at: i64,
}
pub fn get_recent_files_impl(vault_root: &Path) -> Result<Vec<RecentFile>, String> {
use crate::vault::init_vault_db;
let db = init_vault_db(vault_root).map_err(|e| e.to_string())?;
let mut stmt = db
.conn
.prepare(
r#"
SELECT
f.path,
f.name,
rf.opened_at
FROM recent_files rf
INNER JOIN files f ON rf.file_id = f.id
ORDER BY rf.opened_at DESC
LIMIT 10
"#,
)
.map_err(|e| format!("Failed to prepare statement: {}", e))?;
let recent = stmt
.query_map([], |row| {
Ok(RecentFile {
path: row.get(0)?,
name: row.get(1)?,
opened_at: row.get(2)?,
})
})
.map_err(|e| format!("Failed to query recent files: {}", e))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("Failed to read recent files: {}", e))?;
Ok(recent)
}
pub fn update_recent_file_impl(vault_root: &Path, file_path: &Path) -> Result<(), String> {
use crate::vault::init_vault_db;
if !file_path.is_file() {
return Err(format!("File not found: {}", file_path.display()));
}
let abs_vault = vault_root
.canonicalize()
.map_err(|e| format!("Failed to canonicalize vault root: {}", e))?;
let abs_file = file_path
.canonicalize()
.map_err(|e| format!("Failed to canonicalize file path: {}", e))?;
if !abs_file.starts_with(&abs_vault) {
return Err("File is outside the vault".to_string());
}
let db = init_vault_db(vault_root).map_err(|e| e.to_string())?;
let rel_path = abs_file
.strip_prefix(&abs_vault)
.map_err(|_| "Failed to compute relative path")?
.to_string_lossy()
.into_owned();
let file_name = file_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
db.conn
.execute(
r#"
INSERT OR IGNORE INTO files (path, name, extension, modified_at, is_indexed)
VALUES (?, ?, ?, ?, 0)
"#,
[
&rel_path,
&file_name,
&file_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_string(),
¤t_time.to_string(),
],
)
.map_err(|e| format!("Failed to insert file record: {}", e))?;
let file_id: i64 = db
.conn
.query_row(
"SELECT id FROM files WHERE path = ?",
[&rel_path],
|row| row.get(0),
)
.map_err(|e| format!("Failed to get file id: {}", e))?;
db.conn
.execute(
"INSERT OR REPLACE INTO recent_files (file_id, opened_at) VALUES (?, ?)",
[file_id.to_string(), current_time.to_string()],
)
.map_err(|e| format!("Failed to update recent files: {}", e))?;
Ok(())
}
#[tauri::command]
pub async fn get_recent_files(vault_root: String) -> Result<Vec<RecentFile>, String> {
let vr = PathBuf::from(&vault_root);
get_recent_files_impl(&vr)
}
#[tauri::command]
pub async fn update_recent_file(vault_root: String, file_path: String) -> Result<(), String> {
let vr = PathBuf::from(&vault_root);
let fp = PathBuf::from(&file_path);
update_recent_file_impl(&vr, &fp)
}