Duncan Tourolle ada3ed64ab 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.
2026-06-20 18:20:25 +02:00

123 lines
3.8 KiB
Rust

//! Tauri commands for playlist management
//! Uses handle-based system: UUID -> Arc<HybridRepository>
//!
//! TRACES: UR-014 | JA-019, JA-020
use log::debug;
use tauri::State;
use crate::repository::{MediaRepository, types::*};
use super::repository::RepositoryManagerWrapper;
/// Create a new playlist
#[tauri::command]
#[specta::specta]
pub async fn playlist_create(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
name: String,
item_ids: Option<Vec<String>>,
) -> Result<PlaylistCreatedResult, String> {
debug!("[PLAYLIST] create called: name={}", name);
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
let ids = item_ids.unwrap_or_default();
repo.as_ref().create_playlist(&name, &ids)
.await
.map_err(|e| format!("{:?}", e))
}
/// Delete a playlist
#[tauri::command]
#[specta::specta]
pub async fn playlist_delete(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
playlist_id: String,
) -> Result<(), String> {
debug!("[PLAYLIST] delete called: id={}", playlist_id);
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
repo.as_ref().delete_playlist(&playlist_id)
.await
.map_err(|e| format!("{:?}", e))
}
/// Rename a playlist
#[tauri::command]
#[specta::specta]
pub async fn playlist_rename(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
playlist_id: String,
name: String,
) -> Result<(), String> {
debug!("[PLAYLIST] rename called: id={}, name={}", playlist_id, name);
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
repo.as_ref().rename_playlist(&playlist_id, &name)
.await
.map_err(|e| format!("{:?}", e))
}
/// Get playlist items with PlaylistItemId
#[tauri::command]
#[specta::specta]
pub async fn playlist_get_items(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
playlist_id: String,
) -> Result<Vec<PlaylistEntry>, String> {
debug!("[PLAYLIST] get_items called: id={}", playlist_id);
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
repo.as_ref().get_playlist_items(&playlist_id)
.await
.map_err(|e| format!("{:?}", e))
}
/// Add items to a playlist
#[tauri::command]
#[specta::specta]
pub async fn playlist_add_items(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
playlist_id: String,
item_ids: Vec<String>,
) -> Result<(), String> {
debug!("[PLAYLIST] add_items called: id={}, count={}", playlist_id, item_ids.len());
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
repo.as_ref().add_to_playlist(&playlist_id, &item_ids)
.await
.map_err(|e| format!("{:?}", e))
}
/// 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,
playlist_id: String,
entry_ids: Vec<String>,
) -> Result<(), String> {
debug!("[PLAYLIST] remove_items called: id={}, count={}", playlist_id, entry_ids.len());
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
repo.as_ref().remove_from_playlist(&playlist_id, &entry_ids)
.await
.map_err(|e| format!("{:?}", e))
}
/// Move a playlist item to a new position
#[tauri::command]
#[specta::specta]
pub async fn playlist_move_item(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
playlist_id: String,
item_id: String,
new_index: u32,
) -> Result<(), String> {
debug!("[PLAYLIST] move_item called: playlist={}, item={}, index={}", playlist_id, item_id, new_index);
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
repo.as_ref().move_playlist_item(&playlist_id, &item_id, new_index)
.await
.map_err(|e| format!("{:?}", e))
}