many changes
Traceability Validation / Check Requirement Traces (push) Failing after 1m18s
🏗️ Build and Test JellyTau / Build APK and Run Tests (push) Has been cancelled

This commit is contained in:
2026-02-14 00:09:47 +01:00
parent 6d1c618a3a
commit e3797f32ca
74 changed files with 6718 additions and 771 deletions
+128
View File
@@ -0,0 +1,128 @@
//! 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]
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]
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");
}
}
+1
View File
@@ -1530,6 +1530,7 @@ pub fn get_album_affinity_status(
Ok(statuses)
}
// TRACES: UR-011, UR-018 | DR-015, DR-018 | UT-042, UT-043
#[cfg(test)]
mod tests {
use super::*;
+5
View File
@@ -1,6 +1,10 @@
// Tauri commands exposed to frontend
// TRACES: UR-002, UR-003, UR-004, UR-005, UR-009, UR-011, UR-012, UR-017, UR-019, UR-025 |
// DR-015, DR-017, DR-021, DR-028
pub mod auth;
pub mod connectivity;
pub mod conversions;
pub mod device;
pub mod download;
pub mod offline;
pub mod playback_mode;
@@ -14,6 +18,7 @@ pub mod sync;
pub use auth::*;
pub use connectivity::*;
pub use conversions::*;
pub use device::*;
pub use download::*;
pub use offline::*;
pub use playback_mode::*;
+1
View File
@@ -130,6 +130,7 @@ pub async fn offline_search(
.map_err(|e| e.to_string())
}
// TRACES: UR-002, UR-011 | DR-017 | UT-044
#[cfg(test)]
mod tests {
use super::*;
+27
View File
@@ -367,6 +367,33 @@ pub fn repository_get_image_url(
Ok(repo.as_ref().get_image_url(&item_id, image_type, options))
}
/// Get subtitle URL for a media item
#[tauri::command]
pub fn repository_get_subtitle_url(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
item_id: String,
media_source_id: String,
stream_index: i32,
format: String,
) -> Result<String, String> {
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
Ok(repo.as_ref().get_subtitle_url(&item_id, &media_source_id, stream_index, &format))
}
/// Get video download URL with quality preset
#[tauri::command]
pub fn repository_get_video_download_url(
manager: State<'_, RepositoryManagerWrapper>,
handle: String,
item_id: String,
quality: String,
media_source_id: Option<String>,
) -> Result<String, String> {
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
Ok(repo.as_ref().get_video_download_url(&item_id, &quality, media_source_id.as_deref()))
}
/// Mark an item as favorite
#[tauri::command]
pub async fn repository_mark_favorite(
+1
View File
@@ -2,6 +2,7 @@
//!
//! The sync queue stores mutations (favorites, playback progress, etc.)
//! that need to be synced to the Jellyfin server when connectivity is restored.
//! TRACES: UR-002, UR-017, UR-025 | DR-014
use serde::{Deserialize, Serialize};
use std::sync::Arc;