Workstream E (backend): wire tauri-specta — annotate commands, derive Type, generate-ready Builder
- Add #[specta::specta] to all 201 #[tauri::command] functions. - Derive specta::Type on all IPC DTOs (repository/types, settings, player/storage/ download command DTOs, player enums, jellyfin SessionInfo/NowPlayingItem/PlayState, ThumbnailCacheStats, DownloadInfo, CacheConfig, etc.). - Replace tauri::generate_handler! with a tauri_specta::Builder + collect_commands! in lib.rs (exports bindings.ts in debug builds). Two contract changes required by specta constraints (frontend migration follows): - specta caps command arity at 10 args: download_item_and_start / download_item / download_video now take a single request struct (params bundled, body unchanged via destructuring). - specta can't parse split serde rename_all: SessionInfo/NowPlayingItem/PlayState switched to rename_all = "PascalCase" (Jellyfin deserialization preserved; these now serialize PascalCase to the frontend). cargo check --lib is clean (0 errors). Frontend migration to bindings.ts is the next step.
This commit is contained in:
@@ -32,7 +32,7 @@ pub struct CredentialStoreWrapper(pub Mutex<CredentialStore>);
|
||||
pub struct ThumbnailCacheWrapper(pub Arc<ThumbnailCache>);
|
||||
|
||||
/// Server info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerInfo {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
@@ -41,7 +41,7 @@ pub struct ServerInfo {
|
||||
}
|
||||
|
||||
/// User info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserInfo {
|
||||
pub id: String,
|
||||
@@ -51,7 +51,7 @@ pub struct UserInfo {
|
||||
}
|
||||
|
||||
/// Active session info (for session restoration)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ActiveSession {
|
||||
pub user_id: String,
|
||||
@@ -63,7 +63,7 @@ pub struct ActiveSession {
|
||||
}
|
||||
|
||||
/// Security status info
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SecurityStatus {
|
||||
pub using_keyring: bool,
|
||||
@@ -72,6 +72,7 @@ pub struct SecurityStatus {
|
||||
|
||||
/// Initialize the database and run migrations
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_init(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
Ok(database.path().to_string_lossy().to_string())
|
||||
@@ -79,6 +80,7 @@ pub fn storage_init(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
|
||||
/// Get storage directory path (parent directory of the database file)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_path(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
let db_path = database.path();
|
||||
@@ -92,6 +94,7 @@ pub fn storage_get_path(db: State<DatabaseWrapper>) -> Result<String, String> {
|
||||
|
||||
/// Get database file size in bytes
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_size(db: State<DatabaseWrapper>) -> Result<Option<u64>, String> {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
Ok(database.file_size())
|
||||
@@ -99,6 +102,7 @@ pub fn storage_get_size(db: State<DatabaseWrapper>) -> Result<Option<u64>, Strin
|
||||
|
||||
/// Get security status (keyring vs encrypted file fallback)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_security_status(
|
||||
creds: State<CredentialStoreWrapper>,
|
||||
) -> Result<SecurityStatus, String> {
|
||||
@@ -117,6 +121,7 @@ pub fn storage_get_security_status(
|
||||
/// Save a server connection
|
||||
/// Uses INSERT ... ON CONFLICT to avoid triggering CASCADE DELETE on users
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_server(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: String,
|
||||
@@ -154,6 +159,7 @@ pub async fn storage_save_server(
|
||||
|
||||
/// Get all saved servers
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_servers(db: State<'_, DatabaseWrapper>) -> Result<Vec<ServerInfo>, String> {
|
||||
let db_service = {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
@@ -179,6 +185,7 @@ pub async fn storage_get_servers(db: State<'_, DatabaseWrapper>) -> Result<Vec<S
|
||||
|
||||
/// Delete a server and all associated data
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_delete_server(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -221,6 +228,7 @@ pub async fn storage_delete_server(
|
||||
|
||||
/// Save a user account (token stored in secure storage, not database)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -298,6 +306,7 @@ pub async fn storage_save_user(
|
||||
|
||||
/// Get users for a server
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_users(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -330,6 +339,7 @@ pub async fn storage_get_users(
|
||||
|
||||
/// Set a user as active (and deactivate all other users globally)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_set_active_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -378,6 +388,7 @@ pub async fn storage_set_active_user(
|
||||
|
||||
/// Get the active user for a server
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_active_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -408,6 +419,7 @@ pub async fn storage_get_active_user(
|
||||
|
||||
/// Get the active session (user + server + token) for session restoration
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_active_session(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -483,6 +495,7 @@ pub async fn storage_get_active_session(
|
||||
|
||||
/// Get user's access token from secure storage
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn storage_get_access_token(
|
||||
creds: State<CredentialStoreWrapper>,
|
||||
user_id: String,
|
||||
@@ -497,6 +510,7 @@ pub fn storage_get_access_token(
|
||||
|
||||
/// Delete a user account and their token from secure storage
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_delete_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
creds: State<'_, CredentialStoreWrapper>,
|
||||
@@ -529,7 +543,7 @@ pub async fn storage_delete_user(
|
||||
}
|
||||
|
||||
/// Playback progress info
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PlaybackProgress {
|
||||
pub item_id: String,
|
||||
@@ -542,6 +556,7 @@ pub struct PlaybackProgress {
|
||||
/// Update playback progress in local database
|
||||
/// This stores the progress locally for offline access and "continue watching"
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_update_playback_progress(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -593,6 +608,7 @@ pub async fn storage_update_playback_progress(
|
||||
/// Update playback progress with context in local database
|
||||
/// This stores the progress along with playback context (container vs single)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_update_playback_context(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -642,6 +658,7 @@ pub async fn storage_update_playback_context(
|
||||
|
||||
/// Mark item as played in local database
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_mark_played(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
smart_cache: State<'_, SmartCacheWrapper>,
|
||||
@@ -772,6 +789,7 @@ pub async fn storage_mark_played(
|
||||
|
||||
/// Get playback progress for an item
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_playback_progress(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -804,6 +822,7 @@ pub async fn storage_get_playback_progress(
|
||||
|
||||
/// Mark pending sync as completed for an item
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_mark_synced(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -828,6 +847,7 @@ pub async fn storage_mark_synced(
|
||||
/// Toggle favorite status for an item in local database
|
||||
/// This updates the is_favorite field and marks it for sync to Jellyfin
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_toggle_favorite(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -873,7 +893,7 @@ pub async fn storage_toggle_favorite(
|
||||
// =============================================================================
|
||||
|
||||
/// Cached library info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CachedLibrary {
|
||||
pub id: String,
|
||||
@@ -884,7 +904,7 @@ pub struct CachedLibrary {
|
||||
}
|
||||
|
||||
/// Cached media item returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CachedItem {
|
||||
pub id: String,
|
||||
@@ -915,6 +935,7 @@ pub struct CachedItem {
|
||||
|
||||
/// Get cached libraries for a server
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_libraries(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -977,6 +998,7 @@ fn row_to_cached_item(row: &rusqlite::Row) -> rusqlite::Result<CachedItem> {
|
||||
|
||||
/// Get cached items with optional filtering
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_items(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -1043,6 +1065,7 @@ pub async fn storage_get_items(
|
||||
|
||||
/// Get a single cached item by ID
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_item(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
item_id: String,
|
||||
@@ -1070,6 +1093,7 @@ pub async fn storage_get_item(
|
||||
|
||||
/// Search cached items using FTS
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_search_items(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
server_id: String,
|
||||
@@ -1111,6 +1135,7 @@ pub async fn storage_search_items(
|
||||
|
||||
/// Save a library to the cache
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_library(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: String,
|
||||
@@ -1144,6 +1169,7 @@ pub async fn storage_save_library(
|
||||
|
||||
/// Save an item to the cache
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_item(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
item: CachedItem,
|
||||
@@ -1207,6 +1233,7 @@ pub async fn storage_save_item(
|
||||
|
||||
/// Get count of pending sync operations for a user
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_pending_sync_count(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::storage::db_service::{DatabaseService, Query, QueryParam};
|
||||
|
||||
|
||||
/// Cached person info returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CachedPerson {
|
||||
pub id: String,
|
||||
@@ -22,7 +22,7 @@ pub struct CachedPerson {
|
||||
}
|
||||
|
||||
/// Item-person association for caching
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CachedItemPerson {
|
||||
pub item_id: String,
|
||||
@@ -35,6 +35,7 @@ pub struct CachedItemPerson {
|
||||
|
||||
/// Save a person to the cache
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_person(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
person: CachedPerson,
|
||||
@@ -66,6 +67,7 @@ pub async fn storage_save_person(
|
||||
|
||||
/// Get a cached person by ID
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_person(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
person_id: String,
|
||||
@@ -101,6 +103,7 @@ pub async fn storage_get_person(
|
||||
|
||||
/// Save item-person associations (batch)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_item_people(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
associations: Vec<CachedItemPerson>,
|
||||
@@ -139,6 +142,7 @@ pub async fn storage_save_item_people(
|
||||
|
||||
/// Get people for an item (with person details joined)
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_item_people(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
item_id: String,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::storage::db_service::{DatabaseService, Query, QueryParam};
|
||||
|
||||
|
||||
/// Audio track preference for a series
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SeriesAudioPreference {
|
||||
pub series_id: String,
|
||||
@@ -20,6 +20,7 @@ pub struct SeriesAudioPreference {
|
||||
|
||||
/// Save user's preferred audio track for a series
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_save_series_audio_preference(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
@@ -59,6 +60,7 @@ pub async fn storage_save_series_audio_preference(
|
||||
|
||||
/// Get user's preferred audio track for a series
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn storage_get_series_audio_preference(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::thumbnail::{ThumbnailCacheStats, ThumbnailWorker};
|
||||
/// Get cached thumbnail path, returns None if not cached
|
||||
/// Also updates last_accessed timestamp for LRU tracking
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn thumbnail_get_cached(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
@@ -38,6 +39,7 @@ pub async fn thumbnail_get_cached(
|
||||
/// Download and save a thumbnail to cache
|
||||
/// Returns the local file path on success
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn thumbnail_save(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
@@ -65,6 +67,7 @@ pub async fn thumbnail_save(
|
||||
|
||||
/// Get thumbnail cache statistics
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn thumbnail_get_stats(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
@@ -87,6 +90,7 @@ pub async fn thumbnail_get_stats(
|
||||
|
||||
/// Set thumbnail cache storage limit in bytes
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn thumbnail_set_limit(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
@@ -102,6 +106,7 @@ pub async fn thumbnail_set_limit(
|
||||
|
||||
/// Clear all cached thumbnails
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn thumbnail_clear_cache(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
@@ -116,6 +121,7 @@ pub async fn thumbnail_clear_cache(
|
||||
|
||||
/// Delete cached thumbnails for a specific item
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn thumbnail_delete_item(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
@@ -149,7 +155,7 @@ fn image_semaphore() -> &'static Semaphore {
|
||||
}
|
||||
|
||||
/// Request to get an image URL (with caching)
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(specta::Type, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GetImageRequest {
|
||||
pub item_id: String,
|
||||
@@ -165,6 +171,7 @@ pub struct GetImageRequest {
|
||||
/// Get image as base64 data URL, caching if not already cached
|
||||
/// This extends the thumbnail system to serve all images through Rust with automatic caching
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn image_get_url(
|
||||
repository_manager: State<'_, RepositoryManagerWrapper>,
|
||||
thumbnail_cache: State<'_, ThumbnailCacheWrapper>,
|
||||
|
||||
Reference in New Issue
Block a user