Initial commit — Tauri image gallery with sqlite-vec
Sets up Tauri v2 + React frontend with SQLite metadata store, sqlite-vec for CLIP embedding infrastructure, and r2d2 connection pool replacing the single Arc<Mutex<Connection>>. Indexer now uses rayon for parallel file metadata collection.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
use crate::db::{self, DbPool, Folder, ImageRecord};
|
||||
use crate::indexer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
pub type DbState = DbPool;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ImagesPage {
|
||||
pub images: Vec<ImageRecord>,
|
||||
pub total: i64,
|
||||
pub offset: i64,
|
||||
pub limit: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GetImagesParams {
|
||||
pub folder_id: Option<i64>,
|
||||
pub search: Option<String>,
|
||||
pub media_kind: Option<String>,
|
||||
pub favorites_only: Option<bool>,
|
||||
pub sort: Option<String>,
|
||||
pub offset: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateImageDetailsParams {
|
||||
pub image_id: i64,
|
||||
pub favorite: Option<bool>,
|
||||
pub rating: Option<i64>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn add_folder(
|
||||
app: AppHandle,
|
||||
db: State<'_, DbState>,
|
||||
path: String,
|
||||
cache_dir: String,
|
||||
) -> Result<Folder, String> {
|
||||
let folder_path = PathBuf::from(&path);
|
||||
|
||||
if !folder_path.exists() || !folder_path.is_dir() {
|
||||
return Err("Path is not a valid directory".into());
|
||||
}
|
||||
|
||||
let name = folder_path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| path.clone());
|
||||
|
||||
let folder_id = {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::insert_folder(&conn, &path, &name).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
let folders = {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::get_folders(&conn).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
let folder = folders
|
||||
.into_iter()
|
||||
.find(|f| f.id == folder_id)
|
||||
.ok_or("Folder not found after insert")?;
|
||||
|
||||
let cache_path = PathBuf::from(cache_dir);
|
||||
indexer::index_folder(app, db.inner().clone(), folder_id, folder_path, cache_path);
|
||||
|
||||
Ok(folder)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_folders(db: State<'_, DbState>) -> Result<Vec<Folder>, String> {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::get_folders(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_folder(db: State<'_, DbState>, folder_id: i64) -> Result<(), String> {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::delete_folder(&conn, folder_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_images(
|
||||
db: State<'_, DbState>,
|
||||
params: GetImagesParams,
|
||||
) -> Result<ImagesPage, String> {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
|
||||
let sort = params.sort.as_deref().unwrap_or("date_desc");
|
||||
let offset = params.offset.unwrap_or(0);
|
||||
let limit = params.limit.unwrap_or(100);
|
||||
let search = params.search.as_deref();
|
||||
let media_kind = params.media_kind.as_deref();
|
||||
let favorites_only = params.favorites_only.unwrap_or(false);
|
||||
|
||||
let total = db::count_images(&conn, params.folder_id, search, media_kind, favorites_only)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let images = db::get_images(
|
||||
&conn,
|
||||
params.folder_id,
|
||||
search,
|
||||
media_kind,
|
||||
favorites_only,
|
||||
sort,
|
||||
offset,
|
||||
limit,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(ImagesPage {
|
||||
images,
|
||||
total,
|
||||
offset,
|
||||
limit,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_image_details(
|
||||
db: State<'_, DbState>,
|
||||
params: UpdateImageDetailsParams,
|
||||
) -> Result<ImageRecord, String> {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::update_image_details(&conn, params.image_id, params.favorite, params.rating)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn reindex_folder(
|
||||
app: AppHandle,
|
||||
db: State<'_, DbState>,
|
||||
folder_id: i64,
|
||||
cache_dir: String,
|
||||
) -> Result<(), String> {
|
||||
let folder_path = {
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
let folders = db::get_folders(&conn).map_err(|e| e.to_string())?;
|
||||
folders
|
||||
.into_iter()
|
||||
.find(|f| f.id == folder_id)
|
||||
.map(|f| PathBuf::from(f.path))
|
||||
.ok_or("Folder not found")?
|
||||
};
|
||||
|
||||
let cache_path = PathBuf::from(cache_dir);
|
||||
indexer::index_folder(app, db.inner().clone(), folder_id, folder_path, cache_path);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user