- 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.
131 lines
3.7 KiB
Rust
131 lines
3.7 KiB
Rust
//! Device identification commands
|
|
//!
|
|
//! Handles persistent device ID generation and retrieval for Jellyfin server communication.
|
|
//! TRACES: UR-009 | DR-011
|
|
|
|
use std::sync::Arc;
|
|
|
|
use log::info;
|
|
use tauri::State;
|
|
use uuid::Uuid;
|
|
|
|
use crate::commands::storage::DatabaseWrapper;
|
|
use crate::storage::db_service::{DatabaseService, Query, QueryParam};
|
|
|
|
/// Get or create the device ID.
|
|
/// Device ID is a UUID v4 that persists across app restarts.
|
|
/// On first call, generates and stores a new UUID.
|
|
/// On subsequent calls, retrieves the stored UUID.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(String)` - The device ID (UUID v4)
|
|
/// - `Err(String)` - If database operation fails
|
|
///
|
|
/// 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())?;
|
|
Arc::new(database.service())
|
|
};
|
|
|
|
// Try to get existing device ID from database
|
|
let query = Query::with_params(
|
|
"SELECT value FROM app_settings WHERE key = ?",
|
|
vec![QueryParam::String("device_id".to_string())],
|
|
);
|
|
|
|
let existing_id: Option<String> = db_service
|
|
.query_one(query, |row| row.get(0))
|
|
.await
|
|
.ok()
|
|
.flatten();
|
|
|
|
if let Some(device_id) = existing_id {
|
|
info!("[Device] Retrieved existing device ID");
|
|
return Ok(device_id);
|
|
}
|
|
|
|
// Generate new device ID
|
|
let device_id = Uuid::new_v4().to_string();
|
|
|
|
// Store it in database
|
|
let insert_query = Query::with_params(
|
|
"INSERT INTO app_settings (key, value) VALUES (?, ?)",
|
|
vec![
|
|
QueryParam::String("device_id".to_string()),
|
|
QueryParam::String(device_id.clone()),
|
|
],
|
|
);
|
|
|
|
db_service
|
|
.execute(insert_query)
|
|
.await
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
info!("[Device] Generated and stored new device ID");
|
|
Ok(device_id)
|
|
}
|
|
|
|
/// Set the device ID (primarily for testing or recovery).
|
|
/// Overwrites any existing device ID.
|
|
///
|
|
/// # Arguments
|
|
/// * `device_id` - The device ID to store (should be UUID v4 format)
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(())` - If device ID was stored successfully
|
|
/// - `Err(String)` - If database operation fails
|
|
///
|
|
/// 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())?;
|
|
Arc::new(database.service())
|
|
};
|
|
|
|
let query = Query::with_params(
|
|
"INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)",
|
|
vec![
|
|
QueryParam::String("device_id".to_string()),
|
|
QueryParam::String(device_id),
|
|
],
|
|
);
|
|
|
|
db_service.execute(query).await.map_err(|e| e.to_string())?;
|
|
|
|
info!("[Device] Device ID set");
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_device_id_is_valid_uuid() {
|
|
let id = Uuid::new_v4().to_string();
|
|
// Should parse as UUID
|
|
let parsed = Uuid::parse_str(&id);
|
|
assert!(parsed.is_ok(), "Device ID should be a valid UUID");
|
|
}
|
|
|
|
#[test]
|
|
fn test_device_id_format() {
|
|
let id = Uuid::new_v4().to_string();
|
|
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (36 chars with hyphens)
|
|
assert_eq!(id.len(), 36, "Device ID should be 36 characters");
|
|
assert!(id.contains('-'), "Device ID should contain hyphens");
|
|
}
|
|
|
|
#[test]
|
|
fn test_device_ids_are_unique() {
|
|
let id1 = Uuid::new_v4().to_string();
|
|
let id2 = Uuid::new_v4().to_string();
|
|
assert_ne!(id1, id2, "Generated device IDs should be unique");
|
|
}
|
|
}
|