jellytau_lib/commands/
offline.rs1use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5use tauri::State;
6
7use super::DatabaseWrapper;
8use crate::storage::db_service::{DatabaseService, Query, QueryParam};
9
10#[derive(specta::Type, Debug, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct OfflineItem {
13 pub id: String,
14 pub name: String,
15 pub item_type: String,
16 pub album_id: Option<String>,
17 pub album_name: Option<String>,
18 pub artists: Option<String>,
19 pub runtime_ticks: Option<i64>,
20 pub primary_image_tag: Option<String>,
21}
22
23#[tauri::command]
25#[specta::specta]
26pub async fn offline_is_available(
27 db: State<'_, DatabaseWrapper>,
28 item_id: String,
29) -> Result<bool, String> {
30 let db_service = {
31 let database = db.0.lock().map_err(|e| e.to_string())?;
32 Arc::new(database.service())
33 };
34
35 let query = Query::with_params(
36 "SELECT COUNT(*) FROM downloads WHERE item_id = ? AND status = 'completed'",
37 vec![QueryParam::String(item_id)],
38 );
39
40 let count: i64 = db_service
41 .query_one(query, |row| row.get(0))
42 .await
43 .map_err(|e| e.to_string())?;
44
45 Ok(count > 0)
46}
47
48#[tauri::command]
50#[specta::specta]
51pub async fn offline_get_items(
52 db: State<'_, DatabaseWrapper>,
53 user_id: String,
54) -> Result<Vec<OfflineItem>, String> {
55 let db_service = {
56 let database = db.0.lock().map_err(|e| e.to_string())?;
57 Arc::new(database.service())
58 };
59
60 let query = Query::with_params(
61 "SELECT i.id, i.name, i.item_type, i.album_id, i.album_name, i.artists,
62 i.runtime_ticks, i.primary_image_tag
63 FROM items i
64 INNER JOIN downloads d ON i.id = d.item_id
65 WHERE d.user_id = ? AND d.status = 'completed'
66 ORDER BY d.completed_at DESC",
67 vec![QueryParam::String(user_id)],
68 );
69
70 db_service
71 .query_many(query, |row| {
72 Ok(OfflineItem {
73 id: row.get(0)?,
74 name: row.get(1)?,
75 item_type: row.get(2)?,
76 album_id: row.get(3)?,
77 album_name: row.get(4)?,
78 artists: row.get(5)?,
79 runtime_ticks: row.get(6)?,
80 primary_image_tag: row.get(7)?,
81 })
82 })
83 .await
84 .map_err(|e| e.to_string())
85}
86
87#[tauri::command]
89#[specta::specta]
90pub async fn offline_search(
91 db: State<'_, DatabaseWrapper>,
92 user_id: String,
93 query: String,
94) -> Result<Vec<OfflineItem>, String> {
95 let db_service = {
96 let database = db.0.lock().map_err(|e| e.to_string())?;
97 Arc::new(database.service())
98 };
99
100 let search_query = format!("%{}%", query.to_lowercase());
101
102 let db_query = Query::with_params(
103 "SELECT i.id, i.name, i.item_type, i.album_id, i.album_name, i.artists,
104 i.runtime_ticks, i.primary_image_tag
105 FROM items i
106 INNER JOIN downloads d ON i.id = d.item_id
107 WHERE d.user_id = ? AND d.status = 'completed'
108 AND (LOWER(i.name) LIKE ? OR LOWER(i.artists) LIKE ? OR LOWER(i.album_name) LIKE ?)
109 ORDER BY i.name
110 LIMIT 50",
111 vec![
112 QueryParam::String(user_id),
113 QueryParam::String(search_query.clone()),
114 QueryParam::String(search_query.clone()),
115 QueryParam::String(search_query),
116 ],
117 );
118
119 db_service
120 .query_many(db_query, |row| {
121 Ok(OfflineItem {
122 id: row.get(0)?,
123 name: row.get(1)?,
124 item_type: row.get(2)?,
125 album_id: row.get(3)?,
126 album_name: row.get(4)?,
127 artists: row.get(5)?,
128 runtime_ticks: row.get(6)?,
129 primary_image_tag: row.get(7)?,
130 })
131 })
132 .await
133 .map_err(|e| e.to_string())
134}
135
136#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_offline_item_serialization() {
143 let item = OfflineItem {
144 id: "123".to_string(),
145 name: "Test Song".to_string(),
146 item_type: "Audio".to_string(),
147 album_id: Some("album1".to_string()),
148 album_name: Some("Test Album".to_string()),
149 artists: Some("Artist 1".to_string()),
150 runtime_ticks: Some(180000000),
151 primary_image_tag: Some("tag123".to_string()),
152 };
153
154 let json = serde_json::to_string(&item).unwrap();
155 assert!(json.contains("\"itemType\":\"Audio\""));
156 assert!(json.contains("\"albumName\":\"Test Album\""));
157 }
158}