Skip to main content

#
jellytau_lib/commands/
device.rs

    1//! Device identification commands
    2//!
    3//! Handles persistent device ID generation and retrieval for Jellyfin server communication.
    4//! TRACES: UR-009 | DR-011
    5
    6use std::sync::Arc;
    7
    8use log::info;
    9use tauri::State;
    10use uuid::Uuid;
    11
    12use crate::commands::storage::DatabaseWrapper;
    13use crate::storage::db_service::{DatabaseService, Query, QueryParam};
    14
    15/// Get or create the device ID.
    16/// Device ID is a UUID v4 that persists across app restarts.
    17/// On first call, generates and stores a new UUID.
    18/// On subsequent calls, retrieves the stored UUID.
    19///
    20/// # Returns
    21/// - `Ok(String)` - The device ID (UUID v4)
    22/// - `Err(String)` - If database operation fails
    23///
    24/// TRACES: UR-009 | DR-011
    25#[tauri::command]
    26#[specta::specta]
    27pub async fn device_get_id(db: State<'_, DatabaseWrapper>) -> Result<String, String> {
    28    let db_service = {
    29        let database = db.0.lock().map_err(|e| e.to_string())?;
    30        Arc::new(database.service())
    31    };
    32
    33    // Try to get existing device ID from database
    34    let query = Query::with_params(
    35        "SELECT value FROM app_settings WHERE key = ?",
    36        vec![QueryParam::String("device_id".to_string())],
    37    );
    38
    39    let existing_id: Option<String> = db_service
    40        .query_one(query, |row| row.get(0))
    41        .await
    42        .ok()
    43        .flatten();
    44
    45    if let Some(device_id) = existing_id {
    46        info!("[Device] Retrieved existing device ID");
    47        return Ok(device_id);
    48    }
    49
    50    // Generate new device ID
    51    let device_id = Uuid::new_v4().to_string();
    52
    53    // Store it in database
    54    let insert_query = Query::with_params(
    55        "INSERT INTO app_settings (key, value) VALUES (?, ?)",
    56        vec![
    57            QueryParam::String("device_id".to_string()),
    58            QueryParam::String(device_id.clone()),
    59        ],
    60    );
    61
    62    db_service
    63        .execute(insert_query)
    64        .await
    65        .map_err(|e| e.to_string())?;
    66
    67    info!("[Device] Generated and stored new device ID");
    68    Ok(device_id)
    69}
    70
    71/// Set the device ID (primarily for testing or recovery).
    72/// Overwrites any existing device ID.
    73///
    74/// # Arguments
    75/// * `device_id` - The device ID to store (should be UUID v4 format)
    76///
    77/// # Returns
    78/// - `Ok(())` - If device ID was stored successfully
    79/// - `Err(String)` - If database operation fails
    80///
    81/// TRACES: UR-009 | DR-011
    82#[tauri::command]
    83#[specta::specta]
    84pub async fn device_set_id(
    85    device_id: String,
    86    db: State<'_, DatabaseWrapper>,
    87) -> Result<(), String> {
    88    let db_service = {
    89        let database = db.0.lock().map_err(|e| e.to_string())?;
    90        Arc::new(database.service())
    91    };
    92
    93    let query = Query::with_params(
    94        "INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)",
    95        vec![
    96            QueryParam::String("device_id".to_string()),
    97            QueryParam::String(device_id),
    98        ],
    99    );
    100
    101    db_service.execute(query).await.map_err(|e| e.to_string())?;
    102
    103    info!("[Device] Device ID set");
    104    Ok(())
    105}
    106
    107#[cfg(test)]
    108mod tests {
    109    use super::*;
    110
    111    #[test]
    112    fn test_device_id_is_valid_uuid() {
    113        let id = Uuid::new_v4().to_string();
    114        // Should parse as UUID
    115        let parsed = Uuid::parse_str(&id);
    116        assert!(parsed.is_ok(), "Device ID should be a valid UUID");
    117    }
    118
    119    #[test]
    120    fn test_device_id_format() {
    121        let id = Uuid::new_v4().to_string();
    122        // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (36 chars with hyphens)
    123        assert_eq!(id.len(), 36, "Device ID should be 36 characters");
    124        assert!(id.contains('-'), "Device ID should contain hyphens");
    125    }
    126
    127    #[test]
    128    fn test_device_ids_are_unique() {
    129        let id1 = Uuid::new_v4().to_string();
    130        let id2 = Uuid::new_v4().to_string();
    131        assert_ne!(id1, id2, "Generated device IDs should be unique");
    132    }
    133}