diff --git a/src-tauri/src/auth/mod.rs b/src-tauri/src/auth/mod.rs index 776b9c7..99fe09b 100644 --- a/src-tauri/src/auth/mod.rs +++ b/src-tauri/src/auth/mod.rs @@ -10,7 +10,7 @@ use crate::connectivity::ConnectivityMonitor; pub use session_verifier::SessionVerifier; /// Server information returned from Jellyfin -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ServerInfo { pub name: String, @@ -21,7 +21,7 @@ pub struct ServerInfo { } /// User information -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct User { pub id: String, @@ -31,7 +31,7 @@ pub struct User { } /// Authentication result -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthResult { pub user: User, @@ -40,7 +40,7 @@ pub struct AuthResult { } /// Active session for restoration -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Session { pub user_id: String, @@ -55,7 +55,7 @@ pub struct Session { // Jellyfin API response types (PascalCase from server) -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "PascalCase")] struct PublicSystemInfo { server_name: String, @@ -63,7 +63,7 @@ struct PublicSystemInfo { id: String, } -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "PascalCase")] struct AuthenticateByNameResponse { user: JellyfinUser, @@ -71,7 +71,7 @@ struct AuthenticateByNameResponse { server_id: String, } -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "PascalCase")] struct JellyfinUser { id: String, diff --git a/src-tauri/src/commands/auth.rs b/src-tauri/src/commands/auth.rs index b225f56..20f2c34 100644 --- a/src-tauri/src/commands/auth.rs +++ b/src-tauri/src/commands/auth.rs @@ -12,6 +12,7 @@ pub struct SessionVerifierWrapper(pub Arc, database: State<'_, crate::commands::DatabaseWrapper>, @@ -61,6 +62,7 @@ pub async fn auth_initialize( /// Connect to a Jellyfin server and get server info #[tauri::command] +#[specta::specta] pub async fn auth_connect_to_server( server_url: String, auth_manager: State<'_, AuthManagerWrapper>, @@ -70,6 +72,7 @@ pub async fn auth_connect_to_server( /// Login with username and password #[tauri::command] +#[specta::specta] pub async fn auth_login( server_url: String, username: String, @@ -100,6 +103,7 @@ pub async fn auth_login( /// Verify current session #[tauri::command] +#[specta::specta] pub async fn auth_verify_session( server_url: String, user_id: String, @@ -118,6 +122,7 @@ pub async fn auth_verify_session( /// Logout (clear session and call Jellyfin logout endpoint) #[tauri::command] +#[specta::specta] pub async fn auth_logout( server_url: String, access_token: String, @@ -143,6 +148,7 @@ pub async fn auth_logout( /// Get current session #[tauri::command] +#[specta::specta] pub async fn auth_get_session( auth_manager: State<'_, AuthManagerWrapper>, ) -> Result, String> { @@ -151,6 +157,7 @@ pub async fn auth_get_session( /// Set current session (for restoration from storage) #[tauri::command] +#[specta::specta] pub async fn auth_set_session( session: Option, auth_manager: State<'_, AuthManagerWrapper>, @@ -170,6 +177,7 @@ pub async fn auth_set_session( /// Start background session verification #[tauri::command] +#[specta::specta] pub async fn auth_start_verification( device_id: String, app_handle: tauri::AppHandle, @@ -198,6 +206,7 @@ pub async fn auth_start_verification( /// Stop background session verification #[tauri::command] +#[specta::specta] pub async fn auth_stop_verification( session_verifier: State<'_, SessionVerifierWrapper>, ) -> Result<(), String> { @@ -212,6 +221,7 @@ pub async fn auth_stop_verification( /// Re-authenticate with password (when session expired) #[tauri::command] +#[specta::specta] pub async fn auth_reauthenticate( password: String, device_id: String, diff --git a/src-tauri/src/commands/connectivity.rs b/src-tauri/src/commands/connectivity.rs index e0e959e..c6ba09b 100644 --- a/src-tauri/src/commands/connectivity.rs +++ b/src-tauri/src/commands/connectivity.rs @@ -7,6 +7,7 @@ pub struct ConnectivityMonitorWrapper(pub Arc, ) -> Result { @@ -16,6 +17,7 @@ pub async fn connectivity_check_server( /// Set the server URL and trigger an immediate check #[tauri::command] +#[specta::specta] pub async fn connectivity_set_server_url( url: String, state: State<'_, ConnectivityMonitorWrapper>, @@ -27,6 +29,7 @@ pub async fn connectivity_set_server_url( /// Get the current connectivity status #[tauri::command] +#[specta::specta] pub async fn connectivity_get_status( state: State<'_, ConnectivityMonitorWrapper>, ) -> Result { @@ -36,6 +39,7 @@ pub async fn connectivity_get_status( /// Start monitoring connectivity with adaptive polling #[tauri::command] +#[specta::specta] pub async fn connectivity_start_monitoring( state: State<'_, ConnectivityMonitorWrapper>, ) -> Result<(), String> { @@ -46,6 +50,7 @@ pub async fn connectivity_start_monitoring( /// Stop monitoring connectivity #[tauri::command] +#[specta::specta] pub async fn connectivity_stop_monitoring( state: State<'_, ConnectivityMonitorWrapper>, ) -> Result<(), String> { @@ -56,6 +61,7 @@ pub async fn connectivity_stop_monitoring( /// Mark the server as reachable (called after successful API calls) #[tauri::command] +#[specta::specta] pub async fn connectivity_mark_reachable( state: State<'_, ConnectivityMonitorWrapper>, ) -> Result<(), String> { @@ -66,6 +72,7 @@ pub async fn connectivity_mark_reachable( /// Mark the server as unreachable (called after failed API calls) #[tauri::command] +#[specta::specta] pub async fn connectivity_mark_unreachable( error: Option, state: State<'_, ConnectivityMonitorWrapper>, diff --git a/src-tauri/src/commands/conversions.rs b/src-tauri/src/commands/conversions.rs index 478c428..4482c80 100644 --- a/src-tauri/src/commands/conversions.rs +++ b/src-tauri/src/commands/conversions.rs @@ -16,6 +16,7 @@ use crate::utils::conversions::{ /// # Returns /// Formatted string like "3:45" or "12:09" #[tauri::command] +#[specta::specta] pub fn format_time_seconds(seconds: f64) -> String { format_time(seconds) } @@ -32,6 +33,7 @@ pub fn format_time_seconds(seconds: f64) -> String { /// # Returns /// Formatted string like "1:23:45" or "3:45" #[tauri::command] +#[specta::specta] pub fn format_time_seconds_long(seconds: f64) -> String { format_time_long(seconds) } @@ -44,6 +46,7 @@ pub fn format_time_seconds_long(seconds: f64) -> String { /// # Returns /// Time in seconds #[tauri::command] +#[specta::specta] pub fn convert_ticks_to_seconds(ticks: i64) -> f64 { ticks_to_seconds(ticks) } @@ -57,6 +60,7 @@ pub fn convert_ticks_to_seconds(ticks: i64) -> f64 { /// # Returns /// Progress as percentage (0.0 to 100.0) #[tauri::command] +#[specta::specta] pub fn calc_progress(position: f64, duration: f64) -> f64 { calculate_progress(position, duration) } @@ -69,6 +73,7 @@ pub fn calc_progress(position: f64, duration: f64) -> f64 { /// # Returns /// Normalized volume (0.0 to 1.0) #[tauri::command] +#[specta::specta] pub fn convert_percent_to_volume(percent: f64) -> f64 { percent_to_volume(percent) } diff --git a/src-tauri/src/commands/device.rs b/src-tauri/src/commands/device.rs index e12399a..f2b0b50 100644 --- a/src-tauri/src/commands/device.rs +++ b/src-tauri/src/commands/device.rs @@ -23,6 +23,7 @@ use crate::storage::db_service::{DatabaseService, Query, QueryParam}; /// /// TRACES: UR-009 | DR-011 #[tauri::command] +#[specta::specta] pub async fn device_get_id(db: State<'_, DatabaseWrapper>) -> Result { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -79,6 +80,7 @@ pub async fn device_get_id(db: State<'_, DatabaseWrapper>) -> Result) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; diff --git a/src-tauri/src/commands/download/mod.rs b/src-tauri/src/commands/download/mod.rs index d393df5..38f15cf 100644 --- a/src-tauri/src/commands/download/mod.rs +++ b/src-tauri/src/commands/download/mod.rs @@ -22,7 +22,7 @@ pub struct DownloadManagerWrapper(pub Mutex); /// Download statistics computed server-side #[allow(dead_code)] -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct DownloadStats { pub total: usize, @@ -35,7 +35,7 @@ pub struct DownloadStats { /// Enhanced response with pre-computed stats #[allow(dead_code)] -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct DownloadsResponse { pub downloads: Vec, @@ -52,22 +52,66 @@ fn sanitize_filename(name: &str) -> String { .collect() } +/// Request payload for download_item_and_start (bundled to stay within specta's +/// 10-argument command limit). +#[derive(Debug, specta::Type, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DownloadItemAndStartRequest { + pub item_id: String, + pub user_id: String, + pub stream_url: String, + pub target_dir: String, + pub item_name: Option, + pub artist_name: Option, + pub album_name: Option, +} + +/// Request payload for download_item. +#[derive(Debug, specta::Type, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DownloadItemRequest { + pub item_id: String, + pub user_id: String, + pub file_path: String, + pub mime_type: Option, + pub priority: Option, + pub item_name: Option, + pub artist_name: Option, + pub album_name: Option, + pub expected_size: Option, +} + +/// Request payload for download_video. +#[derive(Debug, specta::Type, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DownloadVideoRequest { + pub item_id: String, + pub user_id: String, + pub file_path: String, + pub mime_type: Option, + pub priority: Option, + pub item_name: Option, + pub quality_preset: Option, + pub series_name: Option, + pub season_name: Option, + pub episode_number: Option, + pub season_number: Option, +} + /// Queue and start a download in a single atomic operation /// This simplifies the frontend flow by combining multiple steps #[tauri::command] +#[specta::specta] pub async fn download_item_and_start( db: State<'_, DatabaseWrapper>, smart_cache: State<'_, SmartCacheWrapper>, download_manager: State<'_, DownloadManagerWrapper>, app: tauri::AppHandle, - item_id: String, - user_id: String, - stream_url: String, - target_dir: String, - item_name: Option, - artist_name: Option, - album_name: Option, + request: DownloadItemAndStartRequest, ) -> Result { + let DownloadItemAndStartRequest { + item_id, user_id, stream_url, target_dir, item_name, artist_name, album_name, + } = request; // Sanitize filename let safe_name = sanitize_filename(item_name.as_deref().unwrap_or(&item_id)); let file_path = format!("downloads/{}.mp3", safe_name); @@ -76,15 +120,17 @@ pub async fn download_item_and_start( let download_id = download_item( db.clone(), smart_cache.clone(), - item_id, - user_id, - file_path, - None, // mime_type - None, // priority - item_name, - artist_name, - album_name, - None, // expected_size + DownloadItemRequest { + item_id, + user_id, + file_path, + mime_type: None, + priority: None, + item_name, + artist_name, + album_name, + expected_size: None, + }, ).await?; // Start the download immediately @@ -102,19 +148,15 @@ pub async fn download_item_and_start( /// Queue a media item for download #[tauri::command] +#[specta::specta] pub async fn download_item( db: State<'_, DatabaseWrapper>, smart_cache: State<'_, SmartCacheWrapper>, - item_id: String, - user_id: String, - file_path: String, - mime_type: Option, - priority: Option, - item_name: Option, - artist_name: Option, - album_name: Option, - expected_size: Option, + request: DownloadItemRequest, ) -> Result { + let DownloadItemRequest { + item_id, user_id, file_path, mime_type, priority, item_name, artist_name, album_name, expected_size, + } = request; let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) @@ -196,6 +238,7 @@ pub async fn download_item( /// Queue an entire album for download #[tauri::command] +#[specta::specta] pub async fn download_album( db: State<'_, DatabaseWrapper>, album_id: String, @@ -267,21 +310,15 @@ pub async fn download_album( /// Queue a video item (movie or episode) for download with quality preset #[tauri::command] +#[specta::specta] pub async fn download_video( db: State<'_, DatabaseWrapper>, - item_id: String, - user_id: String, - file_path: String, - mime_type: Option, - priority: Option, - item_name: Option, - quality_preset: Option, - // Video-specific metadata - series_name: Option, - season_name: Option, - episode_number: Option, - season_number: Option, + request: DownloadVideoRequest, ) -> Result { + let DownloadVideoRequest { + item_id, user_id, file_path, mime_type, priority, item_name, quality_preset, + series_name, season_name, episode_number, season_number, + } = request; let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; Arc::new(database.service()) @@ -338,6 +375,7 @@ pub async fn download_video( /// Queue all episodes of a series for download #[tauri::command] +#[specta::specta] pub async fn download_series( db: State<'_, DatabaseWrapper>, series_id: String, @@ -438,6 +476,7 @@ pub async fn download_series( /// Queue all episodes of a specific season for download #[tauri::command] +#[specta::specta] pub async fn download_season( db: State<'_, DatabaseWrapper>, season_id: String, @@ -558,6 +597,7 @@ fn compute_download_stats(downloads: &[DownloadInfo]) -> DownloadStats { /// Get all downloads for a user, optionally filtered by status #[tauri::command] +#[specta::specta] pub async fn get_downloads( db: State<'_, DatabaseWrapper>, user_id: String, @@ -632,6 +672,7 @@ pub async fn get_downloads( /// Pause a download #[tauri::command] +#[specta::specta] pub async fn pause_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -649,6 +690,7 @@ pub async fn pause_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> /// Resume a paused download #[tauri::command] +#[specta::specta] pub async fn resume_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -666,6 +708,7 @@ pub async fn resume_download(db: State<'_, DatabaseWrapper>, download_id: i64) - /// Cancel a download #[tauri::command] +#[specta::specta] pub async fn cancel_download( db: State<'_, DatabaseWrapper>, download_manager: State<'_, DownloadManagerWrapper>, @@ -714,6 +757,7 @@ pub async fn cancel_download( /// Mark a download as completed #[tauri::command] +#[specta::specta] pub async fn mark_download_completed( db: State<'_, DatabaseWrapper>, download_id: i64, @@ -742,6 +786,7 @@ pub async fn mark_download_completed( /// Mark a download as failed #[tauri::command] +#[specta::specta] pub async fn mark_download_failed( db: State<'_, DatabaseWrapper>, download_id: i64, @@ -764,6 +809,7 @@ pub async fn mark_download_failed( /// Start downloading a file immediately /// This command actually downloads the file using the worker #[tauri::command] +#[specta::specta] pub async fn start_download( db: State<'_, DatabaseWrapper>, download_manager: State<'_, DownloadManagerWrapper>, @@ -981,6 +1027,7 @@ pub async fn start_download( /// Delete a completed download #[tauri::command] +#[specta::specta] pub async fn delete_download(db: State<'_, DatabaseWrapper>, download_id: i64) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -1048,7 +1095,7 @@ fn map_download_row(row: &rusqlite::Row) -> rusqlite::Result { } /// Storage statistics for downloads -#[derive(Debug, Clone, serde::Serialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize)] pub struct StorageStats { pub total_bytes: i64, pub total_items: i64, @@ -1056,7 +1103,7 @@ pub struct StorageStats { } /// Storage info for a single album -#[derive(Debug, Clone, serde::Serialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize)] pub struct AlbumStorageInfo { pub album_id: String, pub album_name: String, @@ -1067,6 +1114,7 @@ pub struct AlbumStorageInfo { /// Get storage statistics for downloads #[tauri::command] +#[specta::specta] pub async fn get_download_storage_stats( db: State<'_, DatabaseWrapper>, user_id: String, @@ -1127,6 +1175,7 @@ pub async fn get_download_storage_stats( /// Delete all downloads for a user #[tauri::command] +#[specta::specta] pub async fn delete_all_downloads(db: State<'_, DatabaseWrapper>, user_id: String) -> Result { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -1166,6 +1215,7 @@ pub async fn delete_all_downloads(db: State<'_, DatabaseWrapper>, user_id: Strin /// Clear all stale pending/failed/paused downloads #[tauri::command] +#[specta::specta] pub async fn clear_stale_downloads( db: State<'_, DatabaseWrapper>, user_id: String, @@ -1208,6 +1258,7 @@ pub async fn clear_stale_downloads( /// Delete all downloads for a specific album #[tauri::command] +#[specta::specta] pub async fn delete_album_downloads( db: State<'_, DatabaseWrapper>, album_id: String, @@ -1252,7 +1303,7 @@ pub async fn delete_album_downloads( } /// Download manager statistics -#[derive(Debug, Clone, serde::Serialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize)] pub struct DownloadManagerStats { pub max_concurrent: usize, pub active_count: usize, @@ -1261,6 +1312,7 @@ pub struct DownloadManagerStats { /// Get download manager statistics #[tauri::command] +#[specta::specta] pub async fn get_download_manager_stats( download_manager: State<'_, DownloadManagerWrapper>, ) -> Result { @@ -1278,6 +1330,7 @@ pub async fn get_download_manager_stats( /// Set the maximum concurrent downloads #[tauri::command] +#[specta::specta] pub async fn set_max_concurrent_downloads( download_manager: State<'_, DownloadManagerWrapper>, max: usize, diff --git a/src-tauri/src/commands/download/pinning.rs b/src-tauri/src/commands/download/pinning.rs index 8a49fd0..897179b 100644 --- a/src-tauri/src/commands/download/pinning.rs +++ b/src-tauri/src/commands/download/pinning.rs @@ -8,6 +8,7 @@ use crate::storage::db_service::{DatabaseService, Query, QueryParam}; /// Pin an item's metadata (protects from cache clear) #[tauri::command] +#[specta::specta] pub async fn pin_item(db: State<'_, DatabaseWrapper>, item_id: String) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -25,6 +26,7 @@ pub async fn pin_item(db: State<'_, DatabaseWrapper>, item_id: String) -> Result /// Unpin an item's metadata #[tauri::command] +#[specta::specta] pub async fn unpin_item(db: State<'_, DatabaseWrapper>, item_id: String) -> Result<(), String> { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; @@ -42,6 +44,7 @@ pub async fn unpin_item(db: State<'_, DatabaseWrapper>, item_id: String) -> Resu /// Check if an item is pinned #[tauri::command] +#[specta::specta] pub async fn is_item_pinned(db: State<'_, DatabaseWrapper>, item_id: String) -> Result { let db_service = { let database = db.0.lock().map_err(|e| e.to_string())?; diff --git a/src-tauri/src/commands/download/smart_cache.rs b/src-tauri/src/commands/download/smart_cache.rs index 1b0afb8..0da9a6d 100644 --- a/src-tauri/src/commands/download/smart_cache.rs +++ b/src-tauri/src/commands/download/smart_cache.rs @@ -8,7 +8,7 @@ use crate::commands::{DatabaseWrapper, SmartCacheWrapper}; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; /// SmartCache statistics -#[derive(Debug, Clone, serde::Serialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize)] pub struct SmartCacheStats { pub total_size: u64, pub storage_limit: u64, @@ -19,6 +19,7 @@ pub struct SmartCacheStats { /// Get SmartCache statistics #[tauri::command] +#[specta::specta] pub async fn get_smart_cache_stats( db: State<'_, DatabaseWrapper>, smart_cache: State<'_, SmartCacheWrapper>, @@ -67,6 +68,7 @@ pub async fn get_smart_cache_stats( /// Update SmartCache configuration #[tauri::command] +#[specta::specta] pub async fn update_smart_cache_config( smart_cache: State<'_, SmartCacheWrapper>, config: crate::download::cache::CacheConfig, @@ -79,6 +81,7 @@ pub async fn update_smart_cache_config( /// Get SmartCache configuration #[tauri::command] +#[specta::specta] pub async fn get_smart_cache_config( smart_cache: State<'_, SmartCacheWrapper>, ) -> Result { @@ -89,7 +92,7 @@ pub async fn get_smart_cache_config( } /// Album recommendation info -#[derive(Debug, Clone, serde::Serialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize)] pub struct AlbumRecommendation { pub album_id: String, pub album_name: String, @@ -99,7 +102,7 @@ pub struct AlbumRecommendation { } /// Album affinity status info -#[derive(Debug, Clone, serde::Serialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbumAffinityStatus { pub album_id: String, @@ -110,6 +113,7 @@ pub struct AlbumAffinityStatus { /// Get album recommendations based on play history #[tauri::command] +#[specta::specta] pub async fn get_album_recommendations( db: State<'_, DatabaseWrapper>, smart_cache: State<'_, SmartCacheWrapper>, @@ -189,6 +193,7 @@ pub async fn get_album_recommendations( /// Get album affinity status for all tracked albums /// This shows the SmartCache's internal play history and threshold status #[tauri::command] +#[specta::specta] pub fn get_album_affinity_status( smart_cache: State<'_, SmartCacheWrapper>, ) -> Result, String> { diff --git a/src-tauri/src/commands/offline.rs b/src-tauri/src/commands/offline.rs index 77f1cba..539fde7 100644 --- a/src-tauri/src/commands/offline.rs +++ b/src-tauri/src/commands/offline.rs @@ -7,7 +7,7 @@ use tauri::State; use super::DatabaseWrapper; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OfflineItem { pub id: String, @@ -22,6 +22,7 @@ pub struct OfflineItem { /// Check if an item is available offline #[tauri::command] +#[specta::specta] pub async fn offline_is_available( db: State<'_, DatabaseWrapper>, item_id: String, @@ -46,6 +47,7 @@ pub async fn offline_is_available( /// Get all offline items for a user #[tauri::command] +#[specta::specta] pub async fn offline_get_items( db: State<'_, DatabaseWrapper>, user_id: String, @@ -84,6 +86,7 @@ pub async fn offline_get_items( /// Search offline items #[tauri::command] +#[specta::specta] pub async fn offline_search( db: State<'_, DatabaseWrapper>, user_id: String, diff --git a/src-tauri/src/commands/playback_mode.rs b/src-tauri/src/commands/playback_mode.rs index 7476f98..63d0208 100644 --- a/src-tauri/src/commands/playback_mode.rs +++ b/src-tauri/src/commands/playback_mode.rs @@ -8,6 +8,7 @@ pub struct PlaybackModeManagerWrapper(pub Arc); /// Get the current playback mode #[tauri::command] +#[specta::specta] pub fn playback_mode_get_current( manager: State<'_, PlaybackModeManagerWrapper>, ) -> Result { @@ -16,6 +17,7 @@ pub fn playback_mode_get_current( /// Set the playback mode (internal/testing use) #[tauri::command] +#[specta::specta] pub fn playback_mode_set( manager: State<'_, PlaybackModeManagerWrapper>, mode: PlaybackMode, @@ -26,6 +28,7 @@ pub fn playback_mode_set( /// Check if currently transferring between playback modes #[tauri::command] +#[specta::specta] pub fn playback_mode_is_transferring( manager: State<'_, PlaybackModeManagerWrapper>, ) -> Result { @@ -34,6 +37,7 @@ pub fn playback_mode_is_transferring( /// Transfer playback from local device to a remote Jellyfin session #[tauri::command] +#[specta::specta] pub async fn playback_mode_transfer_to_remote( manager: State<'_, PlaybackModeManagerWrapper>, session_id: String, @@ -51,6 +55,7 @@ pub async fn playback_mode_transfer_to_remote( /// - current_item_id: The Jellyfin item ID currently playing on remote /// - position_ticks: Current playback position in ticks (10,000 ticks = 1ms) #[tauri::command] +#[specta::specta] pub async fn playback_mode_transfer_to_local( manager: State<'_, PlaybackModeManagerWrapper>, current_item_id: String, @@ -69,6 +74,7 @@ pub async fn playback_mode_transfer_to_local( /// Get remote session status (for polling position/duration) #[tauri::command] +#[specta::specta] pub async fn playback_mode_get_remote_status( manager: State<'_, PlaybackModeManagerWrapper>, player: State<'_, crate::commands::PlayerStateWrapper>, @@ -119,7 +125,7 @@ pub async fn playback_mode_get_remote_status( } /// Remote session status for UI updates -#[derive(serde::Serialize)] +#[derive(specta::Type, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct RemoteSessionStatus { pub position: f64, diff --git a/src-tauri/src/commands/playback_reporting.rs b/src-tauri/src/commands/playback_reporting.rs index ca2be51..f55521d 100644 --- a/src-tauri/src/commands/playback_reporting.rs +++ b/src-tauri/src/commands/playback_reporting.rs @@ -24,6 +24,7 @@ pub struct PlaybackReporterWrapper(pub Arc>> /// Initialize playback reporter (called after login) #[tauri::command] +#[specta::specta] pub async fn playback_reporter_init( reporter_wrapper: State<'_, PlaybackReporterWrapper>, db: State<'_, DatabaseWrapper>, @@ -66,6 +67,7 @@ pub async fn playback_reporter_init( /// Destroy playback reporter (called on logout) #[tauri::command] +#[specta::specta] pub async fn playback_reporter_destroy( reporter_wrapper: State<'_, PlaybackReporterWrapper>, ) -> Result<(), String> { @@ -76,6 +78,7 @@ pub async fn playback_reporter_destroy( /// Report playback start #[tauri::command] +#[specta::specta] pub async fn playback_report_start( reporter: State<'_, PlaybackReporterWrapper>, connectivity: State<'_, ConnectivityMonitorWrapper>, @@ -110,6 +113,7 @@ pub async fn playback_report_start( /// Report playback progress #[tauri::command] +#[specta::specta] pub async fn playback_report_progress( reporter: State<'_, PlaybackReporterWrapper>, connectivity: State<'_, ConnectivityMonitorWrapper>, @@ -138,6 +142,7 @@ pub async fn playback_report_progress( /// Report playback stopped #[tauri::command] +#[specta::specta] pub async fn playback_report_stopped( reporter: State<'_, PlaybackReporterWrapper>, connectivity: State<'_, ConnectivityMonitorWrapper>, @@ -164,6 +169,7 @@ pub async fn playback_report_stopped( /// Mark item as played #[tauri::command] +#[specta::specta] pub async fn playback_mark_played( reporter: State<'_, PlaybackReporterWrapper>, connectivity: State<'_, ConnectivityMonitorWrapper>, diff --git a/src-tauri/src/commands/player/mod.rs b/src-tauri/src/commands/player/mod.rs index 55130f6..91225c2 100644 --- a/src-tauri/src/commands/player/mod.rs +++ b/src-tauri/src/commands/player/mod.rs @@ -56,7 +56,7 @@ pub struct MediaSessionManagerWrapper(pub Mutex); pub struct VideoSettingsWrapper(pub Mutex); /// Response for player state queries -#[derive(Debug, Serialize)] +#[derive(specta::Type, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlayerStatus { pub state: PlayerState, @@ -82,7 +82,7 @@ pub struct PlayerStatus { /// Lightweight media item for merged playback state /// Converts from both local MediaItem and remote NowPlayingItem -#[derive(Debug, Serialize, Clone)] +#[derive(specta::Type, Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct MergedMediaItem { pub id: String, @@ -134,7 +134,7 @@ impl From<&crate::jellyfin::client::NowPlayingItem> for MergedMediaItem { } /// Response for queue queries -#[derive(Debug, Serialize)] +#[derive(specta::Type, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct QueueStatus { pub items: Vec, @@ -146,7 +146,7 @@ pub struct QueueStatus { } /// Backend type for video playback -#[derive(Debug, Serialize)] +#[derive(specta::Type, Debug, Serialize)] #[serde(rename_all = "lowercase")] pub enum VideoBackend { /// Native backend (ExoPlayer on Android, libmpv on Linux) @@ -159,7 +159,7 @@ pub enum VideoBackend { /// /// Simplified to video playback only. Audio playback uses player_play_tracks /// to avoid Tauri Android serialization issues with complex objects. -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlayItemRequest { pub id: String, @@ -172,7 +172,7 @@ pub struct PlayItemRequest { } /// Queue context for remote transfer - what type of queue is this? -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum PlayQueueContext { /// Playing from a specific album @@ -194,7 +194,7 @@ pub enum PlayQueueContext { } /// Request to play a queue of items -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlayQueueRequest { pub items: Vec, @@ -207,7 +207,7 @@ pub struct PlayQueueRequest { } /// Request to play a track from an album (backend fetches all tracks) -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlayAlbumTrackRequest { pub album_id: String, @@ -217,7 +217,7 @@ pub struct PlayAlbumTrackRequest { } /// Request to play tracks by ID (backend fetches metadata) -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlayTracksRequest { pub track_ids: Vec, @@ -227,7 +227,7 @@ pub struct PlayTracksRequest { } /// Context information for track playback -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum PlayTracksContext { Playlist { @@ -249,7 +249,7 @@ pub enum PlayTracksContext { } /// Response for video seek operations -#[derive(Debug, Serialize)] +#[derive(specta::Type, Debug, Serialize)] #[serde(tag = "strategy", rename_all = "camelCase")] pub enum VideoSeekResponse { /// Use native seeking (HLS or direct stream) @@ -267,7 +267,7 @@ pub enum VideoSeekResponse { } /// Response for audio track switching operations -#[derive(Debug, Serialize)] +#[derive(specta::Type, Debug, Serialize)] #[serde(tag = "strategy", rename_all = "camelCase")] pub enum AudioTrackSwitchResponse { /// Native backend handled it (Android ExoPlayer) @@ -384,6 +384,7 @@ pub(super) async fn check_for_local_download( /// @req: UR-005 - Control media playback (play operation) /// @req: DR-009 - Audio player UI #[tauri::command] +#[specta::specta] pub async fn player_play_item( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -435,6 +436,7 @@ pub async fn player_play_item( /// @req: UR-015 - View and manage current audio queue /// @req: DR-005 - Queue manager with shuffle, repeat, history #[tauri::command] +#[specta::specta] pub async fn player_play_queue( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -511,6 +513,7 @@ pub async fn player_play_queue( } #[tauri::command] +#[specta::specta] pub async fn player_play( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -538,6 +541,7 @@ pub async fn player_play( } #[tauri::command] +#[specta::specta] pub async fn player_pause( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -565,6 +569,7 @@ pub async fn player_pause( } #[tauri::command] +#[specta::specta] pub async fn player_toggle( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -592,6 +597,7 @@ pub async fn player_toggle( } #[tauri::command] +#[specta::specta] pub async fn player_stop( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -651,6 +657,7 @@ pub async fn player_stop( } #[tauri::command] +#[specta::specta] pub async fn player_next( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -703,6 +710,7 @@ pub async fn player_next( } #[tauri::command] +#[specta::specta] pub async fn player_previous( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -753,6 +761,7 @@ pub async fn player_previous( } #[tauri::command] +#[specta::specta] pub async fn player_seek( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -792,6 +801,7 @@ pub async fn player_seek( /// For native (non-HTML5) backends, this command handles the entire stream reload /// internally. For HTML5 backends, it returns the new URL for the frontend to handle. #[tauri::command] +#[specta::specta] pub async fn player_seek_video( player: State<'_, PlayerStateWrapper>, repository_manager: State<'_, super::repository::RepositoryManagerWrapper>, @@ -929,6 +939,7 @@ pub async fn player_seek_video( /// Switch audio track - handles both HTML5 (stream reload) and native (direct switch) /// Note: Frontend should handle saving series preferences after this command succeeds #[tauri::command] +#[specta::specta] pub async fn player_switch_audio_track( player: State<'_, PlayerStateWrapper>, repository_manager: State<'_, super::repository::RepositoryManagerWrapper>, @@ -984,6 +995,7 @@ pub async fn player_switch_audio_track( } #[tauri::command] +#[specta::specta] pub async fn player_set_audio_track( player: State<'_, PlayerStateWrapper>, stream_index: i32, @@ -994,6 +1006,7 @@ pub async fn player_set_audio_track( } #[tauri::command] +#[specta::specta] pub async fn player_set_subtitle_track( player: State<'_, PlayerStateWrapper>, stream_index: Option, @@ -1004,6 +1017,7 @@ pub async fn player_set_subtitle_track( } #[tauri::command] +#[specta::specta] pub async fn player_set_volume( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -1034,6 +1048,7 @@ pub async fn player_set_volume( } #[tauri::command] +#[specta::specta] pub async fn player_toggle_mute( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -1062,6 +1077,7 @@ pub async fn player_toggle_mute( } #[tauri::command] +#[specta::specta] pub async fn player_toggle_shuffle( player: State<'_, PlayerStateWrapper>, ) -> Result { @@ -1072,6 +1088,7 @@ pub async fn player_toggle_shuffle( } #[tauri::command] +#[specta::specta] pub async fn player_cycle_repeat(player: State<'_, PlayerStateWrapper>) -> Result { let controller = player.0.lock().await; controller.cycle_repeat(); @@ -1080,6 +1097,7 @@ pub async fn player_cycle_repeat(player: State<'_, PlayerStateWrapper>) -> Resul } #[tauri::command] +#[specta::specta] pub async fn player_get_status( player: State<'_, PlayerStateWrapper>, playback_mode: State<'_, super::playback_mode::PlaybackModeManagerWrapper>, @@ -1165,6 +1183,7 @@ pub async fn player_get_status( } #[tauri::command] +#[specta::specta] pub async fn player_get_queue(player: State<'_, PlayerStateWrapper>) -> Result { let controller = player.0.lock().await; Ok(get_queue_status(&controller)) @@ -1216,6 +1235,7 @@ pub(super) fn get_queue_status(controller: &PlayerController) -> QueueStatus { /// Play a track from an album - backend fetches all album tracks and builds queue #[tauri::command] +#[specta::specta] pub async fn player_play_album_track( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -1383,6 +1403,7 @@ pub async fn player_play_album_track( /// Play tracks by ID - backend fetches all metadata #[tauri::command] +#[specta::specta] pub async fn player_play_tracks( player: State<'_, PlayerStateWrapper>, session: State<'_, MediaSessionManagerWrapper>, @@ -1521,7 +1542,7 @@ pub async fn player_play_tracks( } /// Response for preload operation -#[derive(Debug, Serialize)] +#[derive(specta::Type, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PreloadResult { /// Number of tracks queued for preload @@ -1535,6 +1556,7 @@ pub struct PreloadResult { /// Preload upcoming tracks from the queue /// This queues background downloads for the next N tracks that aren't already downloaded #[tauri::command] +#[specta::specta] pub async fn player_preload_upcoming( player: State<'_, PlayerStateWrapper>, db: State<'_, DatabaseWrapper>, @@ -1671,6 +1693,7 @@ fn sanitize_filename(name: &str) -> String { /// Update SmartCache configuration #[tauri::command] +#[specta::specta] pub async fn player_set_cache_config( smart_cache: State<'_, SmartCacheWrapper>, config: CacheConfig, @@ -1682,6 +1705,7 @@ pub async fn player_set_cache_config( /// Get current SmartCache configuration #[tauri::command] +#[specta::specta] pub async fn player_get_cache_config( smart_cache: State<'_, SmartCacheWrapper>, ) -> Result { @@ -1691,6 +1715,7 @@ pub async fn player_get_cache_config( /// Configure Jellyfin API client for automatic playback reporting #[tauri::command] +#[specta::specta] pub async fn player_configure_jellyfin( player: State<'_, PlayerStateWrapper>, server_url: String, @@ -1717,6 +1742,7 @@ pub async fn player_configure_jellyfin( /// Disable Jellyfin automatic playback reporting #[tauri::command] +#[specta::specta] pub async fn player_disable_jellyfin( player: State<'_, PlayerStateWrapper>, ) -> Result<(), String> { diff --git a/src-tauri/src/commands/player/queue.rs b/src-tauri/src/commands/player/queue.rs index f8e5783..8665a09 100644 --- a/src-tauri/src/commands/player/queue.rs +++ b/src-tauri/src/commands/player/queue.rs @@ -16,7 +16,7 @@ use crate::repository::types::{ImageOptions, ImageType}; use crate::repository::MediaRepository; /// Request to add items to queue -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AddToQueueRequest { pub items: Vec, @@ -24,7 +24,7 @@ pub struct AddToQueueRequest { } /// Request to add a track by ID - backend fetches metadata -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AddTrackByIdRequest { pub track_id: String, @@ -32,7 +32,7 @@ pub struct AddTrackByIdRequest { } /// Request to add multiple tracks by IDs - backend fetches metadata -#[derive(Debug, Deserialize)] +#[derive(specta::Type, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AddTracksByIdsRequest { pub track_ids: Vec, @@ -40,6 +40,7 @@ pub struct AddTracksByIdsRequest { } #[tauri::command] +#[specta::specta] pub async fn player_add_to_queue( player: State<'_, PlayerStateWrapper>, db: State<'_, DatabaseWrapper>, @@ -81,6 +82,7 @@ pub async fn player_add_to_queue( } #[tauri::command] +#[specta::specta] pub async fn player_remove_from_queue( player: State<'_, PlayerStateWrapper>, index: usize, @@ -107,6 +109,7 @@ pub async fn player_remove_from_queue( } #[tauri::command] +#[specta::specta] pub async fn player_move_in_queue( player: State<'_, PlayerStateWrapper>, from_index: usize, @@ -137,6 +140,7 @@ pub async fn player_move_in_queue( /// Add a track to queue by ID - backend fetches metadata and constructs URLs #[tauri::command] +#[specta::specta] pub async fn player_add_track_by_id( player: State<'_, PlayerStateWrapper>, db: State<'_, DatabaseWrapper>, @@ -236,6 +240,7 @@ pub async fn player_add_track_by_id( /// Add multiple tracks to queue by IDs - backend fetches metadata and constructs URLs #[tauri::command] +#[specta::specta] pub async fn player_add_tracks_by_ids( player: State<'_, PlayerStateWrapper>, db: State<'_, DatabaseWrapper>, @@ -339,6 +344,7 @@ pub async fn player_add_tracks_by_ids( } #[tauri::command] +#[specta::specta] pub async fn player_skip_to( player: State<'_, PlayerStateWrapper>, index: usize, diff --git a/src-tauri/src/commands/player/remote.rs b/src-tauri/src/commands/player/remote.rs index 8d3bf68..687257a 100644 --- a/src-tauri/src/commands/player/remote.rs +++ b/src-tauri/src/commands/player/remote.rs @@ -9,6 +9,7 @@ use super::PlayerStateWrapper; /// Play items on a remote Jellyfin session (casting) #[tauri::command] +#[specta::specta] pub async fn remote_play_on_session( player: State<'_, PlayerStateWrapper>, session_id: String, @@ -36,6 +37,7 @@ pub async fn remote_play_on_session( /// Send a playback command to a remote session #[tauri::command] +#[specta::specta] pub async fn remote_send_command( player: State<'_, PlayerStateWrapper>, session_id: String, @@ -59,6 +61,7 @@ pub async fn remote_send_command( /// Seek on a remote session #[tauri::command] +#[specta::specta] pub async fn remote_session_seek( player: State<'_, PlayerStateWrapper>, session_id: String, @@ -82,6 +85,7 @@ pub async fn remote_session_seek( /// Set volume on a remote session #[tauri::command] +#[specta::specta] pub async fn remote_session_set_volume( player: State<'_, PlayerStateWrapper>, session_id: String, @@ -105,6 +109,7 @@ pub async fn remote_session_set_volume( /// Toggle mute on a remote session #[tauri::command] +#[specta::specta] pub async fn remote_session_toggle_mute( player: State<'_, PlayerStateWrapper>, session_id: String, diff --git a/src-tauri/src/commands/player/session.rs b/src-tauri/src/commands/player/session.rs index 91e84e7..e0308f4 100644 --- a/src-tauri/src/commands/player/session.rs +++ b/src-tauri/src/commands/player/session.rs @@ -10,6 +10,7 @@ use crate::player::PlayerStatusEvent; /// Get the current media session state #[tauri::command] +#[specta::specta] pub async fn player_get_session( session: State<'_, MediaSessionManagerWrapper>, ) -> Result { @@ -19,6 +20,7 @@ pub async fn player_get_session( /// Dismiss the current media session (returns to Idle) #[tauri::command] +#[specta::specta] pub async fn player_dismiss_session( session: State<'_, MediaSessionManagerWrapper>, player: State<'_, PlayerStateWrapper>, diff --git a/src-tauri/src/commands/player/settings.rs b/src-tauri/src/commands/player/settings.rs index 7bfdbf7..32befb5 100644 --- a/src-tauri/src/commands/player/settings.rs +++ b/src-tauri/src/commands/player/settings.rs @@ -7,6 +7,7 @@ use crate::player::AutoplaySettings; use crate::settings::{AudioSettings, VideoSettings}; #[tauri::command] +#[specta::specta] pub async fn player_set_audio_settings( player: State<'_, PlayerStateWrapper>, settings: AudioSettings, @@ -19,6 +20,7 @@ pub async fn player_set_audio_settings( } #[tauri::command] +#[specta::specta] pub async fn player_get_audio_settings( player: State<'_, PlayerStateWrapper>, ) -> Result { @@ -27,6 +29,7 @@ pub async fn player_get_audio_settings( } #[tauri::command] +#[specta::specta] pub async fn player_set_video_settings( video_settings: State<'_, VideoSettingsWrapper>, player: State<'_, PlayerStateWrapper>, @@ -50,6 +53,7 @@ pub async fn player_set_video_settings( } #[tauri::command] +#[specta::specta] pub async fn player_get_video_settings( video_settings: State<'_, VideoSettingsWrapper>, ) -> Result { diff --git a/src-tauri/src/commands/player/timers.rs b/src-tauri/src/commands/player/timers.rs index aa3bca3..a7c0f93 100644 --- a/src-tauri/src/commands/player/timers.rs +++ b/src-tauri/src/commands/player/timers.rs @@ -17,6 +17,7 @@ use crate::storage::db_service::{DatabaseService, Query, QueryParam}; /// Set sleep timer mode #[tauri::command] +#[specta::specta] pub async fn player_set_sleep_timer( player: State<'_, PlayerStateWrapper>, mode: SleepTimerMode, @@ -28,6 +29,7 @@ pub async fn player_set_sleep_timer( /// Cancel sleep timer #[tauri::command] +#[specta::specta] pub async fn player_cancel_sleep_timer( player: State<'_, PlayerStateWrapper>, ) -> Result { @@ -38,6 +40,7 @@ pub async fn player_cancel_sleep_timer( /// Get current sleep timer state #[tauri::command] +#[specta::specta] pub async fn player_get_sleep_timer( player: State<'_, PlayerStateWrapper>, ) -> Result { @@ -49,6 +52,7 @@ pub async fn player_get_sleep_timer( /// Get autoplay settings #[tauri::command] +#[specta::specta] pub async fn player_get_autoplay_settings( player: State<'_, PlayerStateWrapper>, ) -> Result { @@ -58,6 +62,7 @@ pub async fn player_get_autoplay_settings( /// Set autoplay settings and persist to database #[tauri::command] +#[specta::specta] pub async fn player_set_autoplay_settings( player: State<'_, PlayerStateWrapper>, db: State<'_, DatabaseWrapper>, @@ -101,6 +106,7 @@ pub async fn player_set_autoplay_settings( /// Cancel active autoplay countdown #[tauri::command] +#[specta::specta] pub async fn player_cancel_autoplay_countdown( player: State<'_, PlayerStateWrapper>, ) -> Result<(), String> { @@ -111,6 +117,7 @@ pub async fn player_cancel_autoplay_countdown( /// Play next episode (user confirmed from popup) #[tauri::command] +#[specta::specta] pub async fn player_play_next_episode( player: State<'_, PlayerStateWrapper>, db: State<'_, DatabaseWrapper>, @@ -131,6 +138,7 @@ pub async fn player_play_next_episode( /// - Frontend when audio track ends via backend event - no itemId/repositoryHandle needed /// - Android JNI callback also triggers this logic directly #[tauri::command] +#[specta::specta] pub async fn player_on_playback_ended( player: State<'_, PlayerStateWrapper>, repository_manager: State<'_, crate::commands::repository::RepositoryManagerWrapper>, diff --git a/src-tauri/src/commands/playlist.rs b/src-tauri/src/commands/playlist.rs index 84fac05..3bdc323 100644 --- a/src-tauri/src/commands/playlist.rs +++ b/src-tauri/src/commands/playlist.rs @@ -11,6 +11,7 @@ use super::repository::RepositoryManagerWrapper; /// Create a new playlist #[tauri::command] +#[specta::specta] pub async fn playlist_create( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -27,6 +28,7 @@ pub async fn playlist_create( /// Delete a playlist #[tauri::command] +#[specta::specta] pub async fn playlist_delete( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -41,6 +43,7 @@ pub async fn playlist_delete( /// Rename a playlist #[tauri::command] +#[specta::specta] pub async fn playlist_rename( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -56,6 +59,7 @@ pub async fn playlist_rename( /// Get playlist items with PlaylistItemId #[tauri::command] +#[specta::specta] pub async fn playlist_get_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -70,6 +74,7 @@ pub async fn playlist_get_items( /// Add items to a playlist #[tauri::command] +#[specta::specta] pub async fn playlist_add_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -85,6 +90,7 @@ pub async fn playlist_add_items( /// Remove items from a playlist (uses PlaylistItemId entry IDs, NOT media item IDs) #[tauri::command] +#[specta::specta] pub async fn playlist_remove_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -100,6 +106,7 @@ pub async fn playlist_remove_items( /// Move a playlist item to a new position #[tauri::command] +#[specta::specta] pub async fn playlist_move_item( manager: State<'_, RepositoryManagerWrapper>, handle: String, diff --git a/src-tauri/src/commands/repository.rs b/src-tauri/src/commands/repository.rs index f8070b8..b82ddc1 100644 --- a/src-tauri/src/commands/repository.rs +++ b/src-tauri/src/commands/repository.rs @@ -48,6 +48,7 @@ pub struct RepositoryManagerWrapper(pub RepositoryManager); /// Create a new repository instance /// Returns a handle (UUID) for accessing the repository #[tauri::command] +#[specta::specta] pub async fn repository_create( manager: State<'_, RepositoryManagerWrapper>, db: State<'_, crate::commands::storage::DatabaseWrapper>, @@ -108,6 +109,7 @@ pub async fn repository_create( /// Destroy a repository instance #[tauri::command] +#[specta::specta] pub async fn repository_destroy( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -118,6 +120,7 @@ pub async fn repository_destroy( /// Get libraries #[tauri::command] +#[specta::specta] pub async fn repository_get_libraries( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -138,6 +141,7 @@ pub async fn repository_get_libraries( /// Get items in a container (library, folder, album, etc.) #[tauri::command] +#[specta::specta] pub async fn repository_get_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -152,6 +156,7 @@ pub async fn repository_get_items( /// Get a single item by ID #[tauri::command] +#[specta::specta] pub async fn repository_get_item( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -165,6 +170,7 @@ pub async fn repository_get_item( /// Get latest items in a library #[tauri::command] +#[specta::specta] pub async fn repository_get_latest_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -179,6 +185,7 @@ pub async fn repository_get_latest_items( /// Get resume items (continue watching/listening) #[tauri::command] +#[specta::specta] pub async fn repository_get_resume_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -201,6 +208,7 @@ pub async fn repository_get_resume_items( /// Get next up episodes #[tauri::command] +#[specta::specta] pub async fn repository_get_next_up_episodes( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -215,6 +223,7 @@ pub async fn repository_get_next_up_episodes( /// Get recently played audio #[tauri::command] +#[specta::specta] pub async fn repository_get_recently_played_audio( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -228,6 +237,7 @@ pub async fn repository_get_recently_played_audio( /// Get resume movies #[tauri::command] +#[specta::specta] pub async fn repository_get_resume_movies( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -241,6 +251,7 @@ pub async fn repository_get_resume_movies( /// Get genres for a library #[tauri::command] +#[specta::specta] pub async fn repository_get_genres( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -254,6 +265,7 @@ pub async fn repository_get_genres( /// Search for items #[tauri::command] +#[specta::specta] pub async fn repository_search( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -268,6 +280,7 @@ pub async fn repository_search( /// Get playback info for an item #[tauri::command] +#[specta::specta] pub async fn repository_get_playback_info( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -281,6 +294,7 @@ pub async fn repository_get_playback_info( /// Get video stream URL with optional seeking support #[tauri::command] +#[specta::specta] pub async fn repository_get_video_stream_url( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -303,6 +317,7 @@ pub async fn repository_get_video_stream_url( /// Get audio stream URL for a track #[tauri::command] +#[specta::specta] pub async fn repository_get_audio_stream_url( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -317,6 +332,7 @@ pub async fn repository_get_audio_stream_url( /// Report playback start #[tauri::command] +#[specta::specta] pub async fn repository_report_playback_start( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -331,6 +347,7 @@ pub async fn repository_report_playback_start( /// Report playback progress #[tauri::command] +#[specta::specta] pub async fn repository_report_playback_progress( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -345,6 +362,7 @@ pub async fn repository_report_playback_progress( /// Report playback stopped #[tauri::command] +#[specta::specta] pub async fn repository_report_playback_stopped( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -359,6 +377,7 @@ pub async fn repository_report_playback_stopped( /// Get image URL for an item #[tauri::command] +#[specta::specta] pub fn repository_get_image_url( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -372,6 +391,7 @@ pub fn repository_get_image_url( /// Get subtitle URL for a media item #[tauri::command] +#[specta::specta] #[allow(dead_code)] pub fn repository_get_subtitle_url( manager: State<'_, RepositoryManagerWrapper>, @@ -387,6 +407,7 @@ pub fn repository_get_subtitle_url( /// Get video download URL with quality preset #[tauri::command] +#[specta::specta] #[allow(dead_code)] pub fn repository_get_video_download_url( manager: State<'_, RepositoryManagerWrapper>, @@ -401,6 +422,7 @@ pub fn repository_get_video_download_url( /// Mark an item as favorite #[tauri::command] +#[specta::specta] pub async fn repository_mark_favorite( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -414,6 +436,7 @@ pub async fn repository_mark_favorite( /// Unmark an item as favorite #[tauri::command] +#[specta::specta] pub async fn repository_unmark_favorite( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -427,6 +450,7 @@ pub async fn repository_unmark_favorite( /// Get person details #[tauri::command] +#[specta::specta] pub async fn repository_get_person( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -440,6 +464,7 @@ pub async fn repository_get_person( /// Get items by person (actor, director, etc.) #[tauri::command] +#[specta::specta] pub async fn repository_get_items_by_person( manager: State<'_, RepositoryManagerWrapper>, handle: String, @@ -454,6 +479,7 @@ pub async fn repository_get_items_by_person( /// Get similar/related items for a media item #[tauri::command] +#[specta::specta] pub async fn repository_get_similar_items( manager: State<'_, RepositoryManagerWrapper>, handle: String, diff --git a/src-tauri/src/commands/sessions.rs b/src-tauri/src/commands/sessions.rs index 74db87a..f665182 100644 --- a/src-tauri/src/commands/sessions.rs +++ b/src-tauri/src/commands/sessions.rs @@ -10,6 +10,7 @@ pub struct SessionPollerWrapper(pub Arc); /// Set polling frequency hint based on UI state #[tauri::command] +#[specta::specta] pub fn sessions_set_polling_hint( poller: State<'_, SessionPollerWrapper>, hint: String, @@ -27,6 +28,7 @@ pub fn sessions_set_polling_hint( /// Manually trigger a session poll (for refresh button) #[tauri::command] +#[specta::specta] pub async fn sessions_poll_now( poller: State<'_, SessionPollerWrapper>, ) -> Result, String> { diff --git a/src-tauri/src/commands/storage/mod.rs b/src-tauri/src/commands/storage/mod.rs index 779ecdb..3c80f2f 100644 --- a/src-tauri/src/commands/storage/mod.rs +++ b/src-tauri/src/commands/storage/mod.rs @@ -32,7 +32,7 @@ pub struct CredentialStoreWrapper(pub Mutex); pub struct ThumbnailCacheWrapper(pub Arc); /// 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) -> Result { 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) -> Result { /// Get storage directory path (parent directory of the database file) #[tauri::command] +#[specta::specta] pub fn storage_get_path(db: State) -> Result { 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) -> Result { /// Get database file size in bytes #[tauri::command] +#[specta::specta] pub fn storage_get_size(db: State) -> Result, 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) -> Result, Strin /// Get security status (keyring vs encrypted file fallback) #[tauri::command] +#[specta::specta] pub fn storage_get_security_status( creds: State, ) -> Result { @@ -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, 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, 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, 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 { /// 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, diff --git a/src-tauri/src/commands/storage/people.rs b/src-tauri/src/commands/storage/people.rs index 7d4970b..a51bcdb 100644 --- a/src-tauri/src/commands/storage/people.rs +++ b/src-tauri/src/commands/storage/people.rs @@ -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, @@ -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, diff --git a/src-tauri/src/commands/storage/series_prefs.rs b/src-tauri/src/commands/storage/series_prefs.rs index 4d4c1cd..00bc09f 100644 --- a/src-tauri/src/commands/storage/series_prefs.rs +++ b/src-tauri/src/commands/storage/series_prefs.rs @@ -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, diff --git a/src-tauri/src/commands/storage/thumbnails.rs b/src-tauri/src/commands/storage/thumbnails.rs index b96beae..e9922eb 100644 --- a/src-tauri/src/commands/storage/thumbnails.rs +++ b/src-tauri/src/commands/storage/thumbnails.rs @@ -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>, diff --git a/src-tauri/src/commands/sync.rs b/src-tauri/src/commands/sync.rs index a5a486f..a508fad 100644 --- a/src-tauri/src/commands/sync.rs +++ b/src-tauri/src/commands/sync.rs @@ -12,7 +12,7 @@ use super::storage::DatabaseWrapper; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; /// Sync queue item returned to frontend -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SyncQueueItem { pub id: i64, @@ -28,6 +28,7 @@ pub struct SyncQueueItem { /// Queue a mutation for sync to server #[tauri::command] +#[specta::specta] pub async fn sync_queue_mutation( db: State<'_, DatabaseWrapper>, user_id: String, @@ -59,6 +60,7 @@ pub async fn sync_queue_mutation( /// Get all pending sync operations for a user #[tauri::command] +#[specta::specta] pub async fn sync_get_pending( db: State<'_, DatabaseWrapper>, user_id: String, @@ -107,6 +109,7 @@ pub async fn sync_get_pending( /// Mark a sync operation as in progress #[tauri::command] +#[specta::specta] pub async fn sync_mark_processing( db: State<'_, DatabaseWrapper>, id: i64, @@ -127,6 +130,7 @@ pub async fn sync_mark_processing( /// Mark a sync operation as completed #[tauri::command] +#[specta::specta] pub async fn sync_mark_completed( db: State<'_, DatabaseWrapper>, id: i64, @@ -147,6 +151,7 @@ pub async fn sync_mark_completed( /// Mark a sync operation as failed with error message #[tauri::command] +#[specta::specta] pub async fn sync_mark_failed( db: State<'_, DatabaseWrapper>, id: i64, @@ -173,6 +178,7 @@ pub async fn sync_mark_failed( /// Get count of pending sync operations for a user #[tauri::command] +#[specta::specta] pub async fn sync_get_pending_count( db: State<'_, DatabaseWrapper>, user_id: String, @@ -195,6 +201,7 @@ pub async fn sync_get_pending_count( /// Delete completed sync operations older than specified days #[tauri::command] +#[specta::specta] pub async fn sync_cleanup_completed( db: State<'_, DatabaseWrapper>, days_old: i32, @@ -217,6 +224,7 @@ pub async fn sync_cleanup_completed( /// Delete all sync operations for a user (used during logout) #[tauri::command] +#[specta::specta] pub async fn sync_clear_user( db: State<'_, DatabaseWrapper>, user_id: String, diff --git a/src-tauri/src/connectivity/mod.rs b/src-tauri/src/connectivity/mod.rs index 963fe1b..26263f0 100644 --- a/src-tauri/src/connectivity/mod.rs +++ b/src-tauri/src/connectivity/mod.rs @@ -12,7 +12,7 @@ const AUTO_CHECK_INTERVAL_MS: u64 = 30000; // 30 seconds when online const RETRY_CHECK_INTERVAL_MS: u64 = 5000; // 5 seconds when offline /// Connectivity status -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ConnectivityStatus { /// Whether the Jellyfin server is reachable @@ -39,7 +39,7 @@ impl Default for ConnectivityStatus { } /// Connectivity change event emitted to frontend -#[derive(Debug, Clone, Serialize)] +#[derive(specta::Type, Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] struct ConnectivityChangeEvent { is_reachable: bool, diff --git a/src-tauri/src/download/cache.rs b/src-tauri/src/download/cache.rs index 02053ab..2e4d754 100644 --- a/src-tauri/src/download/cache.rs +++ b/src-tauri/src/download/cache.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::storage::db_service::{DatabaseService, Query, QueryParam}; /// Smart caching configuration -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CacheConfig { /// Enable queue pre-caching diff --git a/src-tauri/src/download/mod.rs b/src-tauri/src/download/mod.rs index 8357f46..bfb598d 100644 --- a/src-tauri/src/download/mod.rs +++ b/src-tauri/src/download/mod.rs @@ -77,7 +77,7 @@ impl DownloadManager { } /// Information about a download -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct DownloadInfo { pub id: i64, diff --git a/src-tauri/src/jellyfin/client.rs b/src-tauri/src/jellyfin/client.rs index b8f1dd0..3ae42e2 100644 --- a/src-tauri/src/jellyfin/client.rs +++ b/src-tauri/src/jellyfin/client.rs @@ -381,8 +381,8 @@ fn default_true() -> bool { } /// Session information from Jellyfin -#[derive(Debug, Clone, Deserialize, serde::Serialize)] -#[serde(rename_all(deserialize = "PascalCase", serialize = "camelCase"))] +#[derive(specta::Type, Debug, Clone, Deserialize, serde::Serialize)] +#[serde(rename_all = "PascalCase")] pub struct SessionInfo { #[serde(default)] pub id: Option, @@ -414,8 +414,8 @@ pub struct SessionInfo { pub supported_commands: Option>, } -#[derive(Debug, Clone, Deserialize, serde::Serialize)] -#[serde(rename_all(deserialize = "PascalCase", serialize = "camelCase"))] +#[derive(specta::Type, Debug, Clone, Deserialize, serde::Serialize)] +#[serde(rename_all = "PascalCase")] pub struct NowPlayingItem { pub id: Option, pub name: Option, @@ -431,8 +431,8 @@ pub struct NowPlayingItem { pub item_type: Option, } -#[derive(Debug, Clone, Deserialize, serde::Serialize)] -#[serde(rename_all(deserialize = "PascalCase", serialize = "camelCase"))] +#[derive(specta::Type, Debug, Clone, Deserialize, serde::Serialize)] +#[serde(rename_all = "PascalCase")] pub struct PlayState { #[serde(default)] pub position_ticks: Option, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d8ae398..0de099c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,6 +17,7 @@ pub mod utils; use std::sync::{Arc, Mutex}; use tokio::sync::Mutex as TokioMutex; use tauri::{Emitter, Manager}; +use tauri_specta::Builder; use log::{error, info}; #[cfg(target_os = "android")] use log::warn; @@ -353,6 +354,240 @@ pub fn run() { .filter_level(log::LevelFilter::Info) .init(); + let builder = Builder::::new() + .commands(tauri_specta::collect_commands![ + // Player commands + player_play_item, + player_play_queue, + player_play_album_track, + player_play_tracks, + player_play, + player_pause, + player_toggle, + player_stop, + player_next, + player_previous, + player_seek, + player_seek_video, + player_set_volume, + player_toggle_mute, + player_set_audio_track, + player_switch_audio_track, + player_set_subtitle_track, + player_toggle_shuffle, + player_cycle_repeat, + player_get_status, + player_get_queue, + player_add_to_queue, + player_add_track_by_id, + player_add_tracks_by_ids, + player_remove_from_queue, + player_move_in_queue, + player_skip_to, + player_set_audio_settings, + player_get_audio_settings, + player_set_video_settings, + player_get_video_settings, + // Sleep timer and autoplay commands + player_set_sleep_timer, + player_cancel_sleep_timer, + player_get_sleep_timer, + player_get_autoplay_settings, + player_set_autoplay_settings, + player_cancel_autoplay_countdown, + player_play_next_episode, + player_on_playback_ended, + // Preload commands + player_preload_upcoming, + player_set_cache_config, + player_get_cache_config, + // Jellyfin reporting commands + player_configure_jellyfin, + player_disable_jellyfin, + // Session management commands + player_get_session, + player_dismiss_session, + // Remote session control commands + remote_play_on_session, + remote_send_command, + remote_session_seek, + remote_session_set_volume, + remote_session_toggle_mute, + // Session polling commands + sessions_set_polling_hint, + sessions_poll_now, + // Playback mode commands + playback_mode_get_current, + playback_mode_set, + playback_mode_is_transferring, + playback_mode_transfer_to_remote, + playback_mode_get_remote_status, + playback_mode_transfer_to_local, + // Playback reporting commands + playback_reporter_init, + playback_reporter_destroy, + playback_report_start, + playback_report_progress, + playback_report_stopped, + playback_mark_played, + // Auth commands + auth_initialize, + auth_connect_to_server, + auth_login, + auth_verify_session, + auth_logout, + auth_get_session, + auth_set_session, + auth_start_verification, + auth_stop_verification, + auth_reauthenticate, + // Device commands + device_get_id, + device_set_id, + // Connectivity commands + connectivity_check_server, + connectivity_set_server_url, + connectivity_get_status, + connectivity_start_monitoring, + connectivity_stop_monitoring, + connectivity_mark_reachable, + connectivity_mark_unreachable, + // Storage commands + storage_init, + storage_get_path, + storage_get_size, + storage_get_security_status, + storage_save_server, + storage_get_servers, + storage_delete_server, + storage_save_user, + storage_get_users, + storage_set_active_user, + storage_get_active_user, + storage_get_active_session, + storage_get_access_token, + storage_delete_user, + // Playback progress commands + storage_update_playback_progress, + storage_update_playback_context, + storage_mark_played, + storage_get_playback_progress, + storage_mark_synced, + storage_toggle_favorite, + // Download commands + download_item, + download_item_and_start, + download_album, + download_video, + download_series, + download_season, + get_downloads, + pause_download, + resume_download, + cancel_download, + delete_download, + delete_all_downloads, + delete_album_downloads, + clear_stale_downloads, + get_download_storage_stats, + mark_download_completed, + mark_download_failed, + start_download, + get_download_manager_stats, + set_max_concurrent_downloads, + get_smart_cache_stats, + update_smart_cache_config, + get_smart_cache_config, + get_album_recommendations, + get_album_affinity_status, + // Pinning commands + pin_item, + unpin_item, + is_item_pinned, + // Offline commands + offline_is_available, + offline_get_items, + offline_search, + // Offline cache commands + storage_get_libraries, + storage_get_items, + storage_get_item, + storage_search_items, + storage_save_library, + storage_save_item, + storage_get_pending_sync_count, + // Sync queue commands + sync_queue_mutation, + sync_get_pending, + sync_mark_processing, + sync_mark_completed, + sync_mark_failed, + sync_get_pending_count, + sync_cleanup_completed, + sync_clear_user, + // Thumbnail cache and image commands + thumbnail_get_cached, + thumbnail_save, + thumbnail_get_stats, + thumbnail_set_limit, + thumbnail_clear_cache, + thumbnail_delete_item, + image_get_url, + // People cache commands + storage_save_person, + storage_get_person, + storage_save_item_people, + storage_get_item_people, + // Series audio preferences + storage_save_series_audio_preference, + storage_get_series_audio_preference, + // Repository commands + repository_create, + repository_destroy, + repository_get_libraries, + repository_get_items, + repository_get_item, + repository_get_latest_items, + repository_get_resume_items, + repository_get_next_up_episodes, + repository_get_recently_played_audio, + repository_get_resume_movies, + repository_get_genres, + repository_search, + repository_get_playback_info, + repository_get_video_stream_url, + repository_get_audio_stream_url, + repository_report_playback_start, + repository_report_playback_progress, + repository_report_playback_stopped, + repository_get_image_url, + repository_mark_favorite, + repository_unmark_favorite, + repository_get_person, + repository_get_items_by_person, + repository_get_similar_items, + // Playlist commands + playlist_create, + playlist_delete, + playlist_rename, + playlist_get_items, + playlist_add_items, + playlist_remove_items, + playlist_move_item, + // Conversion commands + format_time_seconds, + format_time_seconds_long, + convert_ticks_to_seconds, + calc_progress, + convert_percent_to_volume, + ]); + + // Export TypeScript bindings (types + typed invoke wrappers) in dev builds. + #[cfg(debug_assertions)] + builder + .export(specta_typescript::Typescript::default(), "../src/lib/api/bindings.ts") + .expect("Failed to export typescript bindings"); + tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_os::init()) @@ -596,232 +831,7 @@ pub fn run() { info!("[INIT] Application setup completed successfully"); Ok(()) }) - .invoke_handler(tauri::generate_handler![ - // Player commands - player_play_item, - player_play_queue, - player_play_album_track, - player_play_tracks, - player_play, - player_pause, - player_toggle, - player_stop, - player_next, - player_previous, - player_seek, - player_seek_video, - player_set_volume, - player_toggle_mute, - player_set_audio_track, - player_switch_audio_track, - player_set_subtitle_track, - player_toggle_shuffle, - player_cycle_repeat, - player_get_status, - player_get_queue, - player_add_to_queue, - player_add_track_by_id, - player_add_tracks_by_ids, - player_remove_from_queue, - player_move_in_queue, - player_skip_to, - player_set_audio_settings, - player_get_audio_settings, - player_set_video_settings, - player_get_video_settings, - // Sleep timer and autoplay commands - player_set_sleep_timer, - player_cancel_sleep_timer, - player_get_sleep_timer, - player_get_autoplay_settings, - player_set_autoplay_settings, - player_cancel_autoplay_countdown, - player_play_next_episode, - player_on_playback_ended, - // Preload commands - player_preload_upcoming, - player_set_cache_config, - player_get_cache_config, - // Jellyfin reporting commands - player_configure_jellyfin, - player_disable_jellyfin, - // Session management commands - player_get_session, - player_dismiss_session, - // Remote session control commands - remote_play_on_session, - remote_send_command, - remote_session_seek, - remote_session_set_volume, - remote_session_toggle_mute, - // Session polling commands - sessions_set_polling_hint, - sessions_poll_now, - // Playback mode commands - playback_mode_get_current, - playback_mode_set, - playback_mode_is_transferring, - playback_mode_transfer_to_remote, - playback_mode_get_remote_status, - playback_mode_transfer_to_local, - // Playback reporting commands - playback_reporter_init, - playback_reporter_destroy, - playback_report_start, - playback_report_progress, - playback_report_stopped, - playback_mark_played, - // Auth commands - auth_initialize, - auth_connect_to_server, - auth_login, - auth_verify_session, - auth_logout, - auth_get_session, - auth_set_session, - auth_start_verification, - auth_stop_verification, - auth_reauthenticate, - // Device commands - device_get_id, - device_set_id, - // Connectivity commands - connectivity_check_server, - connectivity_set_server_url, - connectivity_get_status, - connectivity_start_monitoring, - connectivity_stop_monitoring, - connectivity_mark_reachable, - connectivity_mark_unreachable, - // Storage commands - storage_init, - storage_get_path, - storage_get_size, - storage_get_security_status, - storage_save_server, - storage_get_servers, - storage_delete_server, - storage_save_user, - storage_get_users, - storage_set_active_user, - storage_get_active_user, - storage_get_active_session, - storage_get_access_token, - storage_delete_user, - // Playback progress commands - storage_update_playback_progress, - storage_update_playback_context, - storage_mark_played, - storage_get_playback_progress, - storage_mark_synced, - storage_toggle_favorite, - // Download commands - download_item, - download_item_and_start, - download_album, - download_video, - download_series, - download_season, - get_downloads, - pause_download, - resume_download, - cancel_download, - delete_download, - delete_all_downloads, - delete_album_downloads, - clear_stale_downloads, - get_download_storage_stats, - mark_download_completed, - mark_download_failed, - start_download, - get_download_manager_stats, - set_max_concurrent_downloads, - get_smart_cache_stats, - update_smart_cache_config, - get_smart_cache_config, - get_album_recommendations, - get_album_affinity_status, - // Pinning commands - pin_item, - unpin_item, - is_item_pinned, - // Offline commands - offline_is_available, - offline_get_items, - offline_search, - // Offline cache commands - storage_get_libraries, - storage_get_items, - storage_get_item, - storage_search_items, - storage_save_library, - storage_save_item, - storage_get_pending_sync_count, - // Sync queue commands - sync_queue_mutation, - sync_get_pending, - sync_mark_processing, - sync_mark_completed, - sync_mark_failed, - sync_get_pending_count, - sync_cleanup_completed, - sync_clear_user, - // Thumbnail cache and image commands - thumbnail_get_cached, - thumbnail_save, - thumbnail_get_stats, - thumbnail_set_limit, - thumbnail_clear_cache, - thumbnail_delete_item, - image_get_url, - // People cache commands - storage_save_person, - storage_get_person, - storage_save_item_people, - storage_get_item_people, - // Series audio preferences - storage_save_series_audio_preference, - storage_get_series_audio_preference, - // Repository commands - repository_create, - repository_destroy, - repository_get_libraries, - repository_get_items, - repository_get_item, - repository_get_latest_items, - repository_get_resume_items, - repository_get_next_up_episodes, - repository_get_recently_played_audio, - repository_get_resume_movies, - repository_get_genres, - repository_search, - repository_get_playback_info, - repository_get_video_stream_url, - repository_get_audio_stream_url, - repository_report_playback_start, - repository_report_playback_progress, - repository_report_playback_stopped, - repository_get_image_url, - repository_mark_favorite, - repository_unmark_favorite, - repository_get_person, - repository_get_items_by_person, - repository_get_similar_items, - // Playlist commands - playlist_create, - playlist_delete, - playlist_rename, - playlist_get_items, - playlist_add_items, - playlist_remove_items, - playlist_move_item, - // Conversion commands - format_time_seconds, - format_time_seconds_long, - convert_ticks_to_seconds, - calc_progress, - convert_percent_to_volume, - ]) + .invoke_handler(builder.invoke_handler()) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/src/playback_mode/mod.rs b/src-tauri/src/playback_mode/mod.rs index 1a2e93a..b592923 100644 --- a/src-tauri/src/playback_mode/mod.rs +++ b/src-tauri/src/playback_mode/mod.rs @@ -12,7 +12,7 @@ use crate::jellyfin::JellyfinClient; use crate::player::{PlayerController, QueueContext}; /// Playback mode - local device, remote session, or idle -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type", rename_all = "lowercase")] pub enum PlaybackMode { Local, diff --git a/src-tauri/src/player/autoplay.rs b/src-tauri/src/player/autoplay.rs index c3d48b1..9b3d2dd 100644 --- a/src-tauri/src/player/autoplay.rs +++ b/src-tauri/src/player/autoplay.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::repository::types::MediaItem; /// Autoplay decision result - determines what happens after playback ends -#[derive(Debug, Clone, Serialize)] +#[derive(specta::Type, Debug, Clone, Serialize)] #[serde(tag = "action", rename_all = "camelCase")] pub enum AutoplayDecision { /// Stop playback (no next item or timer expired) @@ -21,7 +21,7 @@ pub enum AutoplayDecision { } /// Autoplay settings (controls next episode behavior) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AutoplaySettings { /// Whether autoplay is enabled for next episodes diff --git a/src-tauri/src/player/media.rs b/src-tauri/src/player/media.rs index 87ae8e8..d2a5b72 100644 --- a/src-tauri/src/player/media.rs +++ b/src-tauri/src/player/media.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; /// Context for the current queue - where did the queue items come from? /// This is used for remote playback transfer to send album/playlist context. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[serde(tag = "type", rename_all = "lowercase")] pub enum QueueContext { /// Playing from a specific album @@ -23,7 +23,7 @@ pub enum QueueContext { } /// Represents a subtitle track -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct SubtitleTrack { /// Stream index in the media source pub index: i32, @@ -40,7 +40,7 @@ pub struct SubtitleTrack { /// Represents a media item that can be played /// /// TRACES: UR-003, UR-004 | DR-002 -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct MediaItem { /// Unique identifier @@ -106,7 +106,7 @@ pub struct MediaItem { pub server_id: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum MediaType { Audio, @@ -114,7 +114,7 @@ pub enum MediaType { } /// TRACES: UR-002, UR-003, UR-004, UR-011 | DR-003 -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type", rename_all = "lowercase")] pub enum MediaSource { /// Streaming from Jellyfin server diff --git a/src-tauri/src/player/queue.rs b/src-tauri/src/player/queue.rs index 34c1d48..378a8a8 100644 --- a/src-tauri/src/player/queue.rs +++ b/src-tauri/src/player/queue.rs @@ -6,7 +6,7 @@ use super::media::{MediaItem, MediaSource, QueueContext}; /// Repeat mode for the queue /// /// TRACES: UR-005 | DR-005 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(specta::Type, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum RepeatMode { #[default] @@ -18,7 +18,7 @@ pub enum RepeatMode { /// Queue manager for playlist functionality /// /// TRACES: UR-005, UR-015 | DR-005, DR-020 -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] pub struct QueueManager { /// All items in the queue items: Vec, diff --git a/src-tauri/src/player/session.rs b/src-tauri/src/player/session.rs index f9fa7cb..443affe 100644 --- a/src-tauri/src/player/session.rs +++ b/src-tauri/src/player/session.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use super::media::MediaItem; /// Media session type tracking the high-level playback context -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "type", rename_all = "snake_case")] pub enum MediaSessionType { /// No active session - browsing library diff --git a/src-tauri/src/player/sleep_timer.rs b/src-tauri/src/player/sleep_timer.rs index b934920..646f24f 100644 --- a/src-tauri/src/player/sleep_timer.rs +++ b/src-tauri/src/player/sleep_timer.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; /// Sleep timer mode - determines when playback should stop /// TRACES: UR-026 | DR-029 -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(tag = "kind", rename_all = "camelCase")] pub enum SleepTimerMode { /// Timer is off @@ -19,7 +19,7 @@ pub enum SleepTimerMode { } /// Sleep timer state -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SleepTimerState { pub mode: SleepTimerMode, diff --git a/src-tauri/src/player/state.rs b/src-tauri/src/player/state.rs index b1267ba..da13952 100644 --- a/src-tauri/src/player/state.rs +++ b/src-tauri/src/player/state.rs @@ -5,7 +5,7 @@ use super::media::MediaItem; /// Tracks why playback ended to determine autoplay behavior /// /// TRACES: UR-005 | DR-001 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum EndReason { /// Track played to completion (natural end) - trigger autoplay @@ -23,7 +23,7 @@ pub enum EndReason { /// Player state machine (6 states: Idle, Loading, Playing, Paused, Seeking, Error) /// /// TRACES: UR-005 | DR-001 -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, Default)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum PlayerState { #[default] diff --git a/src-tauri/src/repository/types.rs b/src-tauri/src/repository/types.rs index 5317c26..903e427 100644 --- a/src-tauri/src/repository/types.rs +++ b/src-tauri/src/repository/types.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; /// Error types for repository operations -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum RepoError { Network { message: String }, @@ -28,7 +28,7 @@ impl std::fmt::Display for RepoError { impl std::error::Error for RepoError {} /// Library (media collection) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Library { pub id: String, @@ -39,7 +39,7 @@ pub struct Library { } /// User-specific data for an item (playback state, favorites, etc.) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserData { #[serde(skip_serializing_if = "Option::is_none")] @@ -59,7 +59,7 @@ pub struct UserData { } /// Artist item with ID and name (for clickable artist links) -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] pub struct ArtistItem { pub id: String, @@ -67,7 +67,7 @@ pub struct ArtistItem { } /// Person (cast/crew member) - for movies, series, and episodes -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Person { /// Deserializes from API's "Id" field (PascalCase), serializes as "id" (camelCase to frontend) @@ -95,7 +95,7 @@ pub struct Person { } /// Media item -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MediaItem { pub id: String, @@ -158,7 +158,7 @@ pub struct MediaItem { } /// Media stream information (audio, video, subtitle tracks) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MediaStream { #[serde(rename = "type")] @@ -175,7 +175,7 @@ pub struct MediaStream { } /// Media source information -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MediaSource { pub id: String, @@ -194,7 +194,7 @@ pub struct MediaSource { } /// Search result with pagination -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SearchResult { pub items: Vec, @@ -202,7 +202,7 @@ pub struct SearchResult { } /// Options for querying items -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct GetItemsOptions { #[serde(skip_serializing_if = "Option::is_none")] @@ -224,7 +224,7 @@ pub struct GetItemsOptions { } /// Options for search queries -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct SearchOptions { #[serde(skip_serializing_if = "Option::is_none")] @@ -236,7 +236,7 @@ pub struct SearchOptions { } /// Playback information -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaybackInfo { pub media_source_id: String, @@ -247,7 +247,7 @@ pub struct PlaybackInfo { } /// Genre -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Genre { pub id: String, @@ -255,7 +255,7 @@ pub struct Genre { } /// Image type -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] pub enum ImageType { Primary, Backdrop, @@ -277,7 +277,7 @@ impl ImageType { } /// Image options -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct ImageOptions { #[serde(skip_serializing_if = "Option::is_none")] @@ -335,7 +335,7 @@ impl MeaningfulContent for PlaybackInfo { /// needed for remove/reorder operations (distinct from the media item's ID) /// /// @req: UR-014 - Make and edit playlists of music that sync back to Jellyfin -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistEntry { /// The playlist-scoped entry ID (Jellyfin's PlaylistItemId) @@ -348,7 +348,7 @@ pub struct PlaylistEntry { /// Result of creating a playlist /// /// @req: JA-019 - Get/create/update playlists -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistCreatedResult { pub id: String, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 902742b..8478814 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; /// Volume normalization levels matching Spotify's presets -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)] +#[derive(specta::Type, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum VolumeLevel { /// Louder output (-11 LUFS) @@ -27,7 +27,7 @@ impl VolumeLevel { } /// Audio playback settings -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AudioSettings { /// Crossfade duration in seconds (0 = disabled, max 12) @@ -60,7 +60,7 @@ impl AudioSettings { } /// Video playback settings -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(specta::Type, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoSettings { /// Enable auto-play of next episode (with countdown) diff --git a/src-tauri/src/thumbnail/mod.rs b/src-tauri/src/thumbnail/mod.rs index 4d0ba88..cedeff4 100644 --- a/src-tauri/src/thumbnail/mod.rs +++ b/src-tauri/src/thumbnail/mod.rs @@ -13,7 +13,7 @@ pub use cache::{CacheConfig, ThumbnailCache}; pub use worker::ThumbnailWorker; /// Statistics about the thumbnail cache -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(specta::Type, Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ThumbnailCacheStats { pub total_size_bytes: u64,