Duncan Tourolle e3797f32ca
Some checks failed
Traceability Validation / Check Requirement Traces (push) Failing after 1m18s
🏗️ Build and Test JellyTau / Build APK and Run Tests (push) Has been cancelled
many changes
2026-02-14 00:09:47 +01:00

156 lines
4.6 KiB
Rust

//! Tauri commands for offline data access
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tauri::State;
use super::DatabaseWrapper;
use crate::storage::db_service::{DatabaseService, Query, QueryParam};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OfflineItem {
pub id: String,
pub name: String,
pub item_type: String,
pub album_id: Option<String>,
pub album_name: Option<String>,
pub artists: Option<String>,
pub runtime_ticks: Option<i64>,
pub primary_image_tag: Option<String>,
}
/// Check if an item is available offline
#[tauri::command]
pub async fn offline_is_available(
db: State<'_, DatabaseWrapper>,
item_id: String,
) -> Result<bool, String> {
let db_service = {
let database = db.0.lock().map_err(|e| e.to_string())?;
Arc::new(database.service())
};
let query = Query::with_params(
"SELECT COUNT(*) FROM downloads WHERE item_id = ? AND status = 'completed'",
vec![QueryParam::String(item_id)],
);
let count: i64 = db_service
.query_one(query, |row| row.get(0))
.await
.map_err(|e| e.to_string())?;
Ok(count > 0)
}
/// Get all offline items for a user
#[tauri::command]
pub async fn offline_get_items(
db: State<'_, DatabaseWrapper>,
user_id: String,
) -> Result<Vec<OfflineItem>, String> {
let db_service = {
let database = db.0.lock().map_err(|e| e.to_string())?;
Arc::new(database.service())
};
let query = Query::with_params(
"SELECT i.id, i.name, i.item_type, i.album_id, i.album_name, i.artists,
i.runtime_ticks, i.primary_image_tag
FROM items i
INNER JOIN downloads d ON i.id = d.item_id
WHERE d.user_id = ? AND d.status = 'completed'
ORDER BY d.completed_at DESC",
vec![QueryParam::String(user_id)],
);
db_service
.query_many(query, |row| {
Ok(OfflineItem {
id: row.get(0)?,
name: row.get(1)?,
item_type: row.get(2)?,
album_id: row.get(3)?,
album_name: row.get(4)?,
artists: row.get(5)?,
runtime_ticks: row.get(6)?,
primary_image_tag: row.get(7)?,
})
})
.await
.map_err(|e| e.to_string())
}
/// Search offline items
#[tauri::command]
pub async fn offline_search(
db: State<'_, DatabaseWrapper>,
user_id: String,
query: String,
) -> Result<Vec<OfflineItem>, String> {
let db_service = {
let database = db.0.lock().map_err(|e| e.to_string())?;
Arc::new(database.service())
};
let search_query = format!("%{}%", query.to_lowercase());
let db_query = Query::with_params(
"SELECT i.id, i.name, i.item_type, i.album_id, i.album_name, i.artists,
i.runtime_ticks, i.primary_image_tag
FROM items i
INNER JOIN downloads d ON i.id = d.item_id
WHERE d.user_id = ? AND d.status = 'completed'
AND (LOWER(i.name) LIKE ? OR LOWER(i.artists) LIKE ? OR LOWER(i.album_name) LIKE ?)
ORDER BY i.name
LIMIT 50",
vec![
QueryParam::String(user_id),
QueryParam::String(search_query.clone()),
QueryParam::String(search_query.clone()),
QueryParam::String(search_query),
],
);
db_service
.query_many(db_query, |row| {
Ok(OfflineItem {
id: row.get(0)?,
name: row.get(1)?,
item_type: row.get(2)?,
album_id: row.get(3)?,
album_name: row.get(4)?,
artists: row.get(5)?,
runtime_ticks: row.get(6)?,
primary_image_tag: row.get(7)?,
})
})
.await
.map_err(|e| e.to_string())
}
// TRACES: UR-002, UR-011 | DR-017 | UT-044
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_offline_item_serialization() {
let item = OfflineItem {
id: "123".to_string(),
name: "Test Song".to_string(),
item_type: "Audio".to_string(),
album_id: Some("album1".to_string()),
album_name: Some("Test Album".to_string()),
artists: Some("Artist 1".to_string()),
runtime_ticks: Some(180000000),
primary_image_tag: Some("tag123".to_string()),
};
let json = serde_json::to_string(&item).unwrap();
assert!(json.contains("\"itemType\":\"Audio\""));
assert!(json.contains("\"albumName\":\"Test Album\""));
}
}