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