First working POC
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
//! Tauri commands for sync queue operations
|
||||
//!
|
||||
//! The sync queue stores mutations (favorites, playback progress, etc.)
|
||||
//! that need to be synced to the Jellyfin server when connectivity is restored.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tauri::State;
|
||||
|
||||
use super::storage::DatabaseWrapper;
|
||||
use crate::storage::db_service::{DatabaseService, Query, QueryParam};
|
||||
|
||||
/// Sync queue item returned to frontend
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SyncQueueItem {
|
||||
pub id: i64,
|
||||
pub user_id: String,
|
||||
pub operation: String,
|
||||
pub item_id: Option<String>,
|
||||
pub payload: Option<String>,
|
||||
pub status: String,
|
||||
pub retry_count: i32,
|
||||
pub created_at: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
/// Queue a mutation for sync to server
|
||||
#[tauri::command]
|
||||
pub async fn sync_queue_mutation(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
operation: String,
|
||||
item_id: Option<String>,
|
||||
payload: Option<String>,
|
||||
) -> Result<i64, 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 INTO sync_queue (user_id, operation, item_id, payload, status, created_at)
|
||||
VALUES (?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP)",
|
||||
vec![
|
||||
QueryParam::String(user_id),
|
||||
QueryParam::String(operation),
|
||||
item_id.map(QueryParam::String).unwrap_or(QueryParam::Null),
|
||||
payload.map(QueryParam::String).unwrap_or(QueryParam::Null),
|
||||
],
|
||||
);
|
||||
|
||||
db_service.execute(query).await.map_err(|e| e.to_string())?;
|
||||
let id = db_service.last_insert_rowid().await.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Get all pending sync operations for a user
|
||||
#[tauri::command]
|
||||
pub async fn sync_get_pending(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
limit: Option<i32>,
|
||||
) -> Result<Vec<SyncQueueItem>, String> {
|
||||
let db_service = {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
Arc::new(database.service())
|
||||
};
|
||||
|
||||
let sql = if let Some(l) = limit {
|
||||
format!(
|
||||
"SELECT id, user_id, operation, item_id, payload, status, retry_count, created_at, error_message
|
||||
FROM sync_queue
|
||||
WHERE user_id = ? AND status IN ('pending', 'failed')
|
||||
ORDER BY created_at ASC
|
||||
LIMIT {}",
|
||||
l
|
||||
)
|
||||
} else {
|
||||
"SELECT id, user_id, operation, item_id, payload, status, retry_count, created_at, error_message
|
||||
FROM sync_queue
|
||||
WHERE user_id = ? AND status IN ('pending', 'failed')
|
||||
ORDER BY created_at ASC".to_string()
|
||||
};
|
||||
|
||||
let query = Query::with_params(sql, vec![QueryParam::String(user_id)]);
|
||||
|
||||
db_service
|
||||
.query_many(query, |row| {
|
||||
Ok(SyncQueueItem {
|
||||
id: row.get(0)?,
|
||||
user_id: row.get(1)?,
|
||||
operation: row.get(2)?,
|
||||
item_id: row.get(3)?,
|
||||
payload: row.get(4)?,
|
||||
status: row.get(5)?,
|
||||
retry_count: row.get(6)?,
|
||||
created_at: row.get(7)?,
|
||||
error_message: row.get(8)?,
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Mark a sync operation as in progress
|
||||
#[tauri::command]
|
||||
pub async fn sync_mark_processing(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: i64,
|
||||
) -> 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(
|
||||
"UPDATE sync_queue SET status = 'processing' WHERE id = ?",
|
||||
vec![QueryParam::Int64(id)],
|
||||
);
|
||||
|
||||
db_service.execute(query).await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark a sync operation as completed
|
||||
#[tauri::command]
|
||||
pub async fn sync_mark_completed(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: i64,
|
||||
) -> 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(
|
||||
"UPDATE sync_queue SET status = 'completed', processed_at = CURRENT_TIMESTAMP WHERE id = ?",
|
||||
vec![QueryParam::Int64(id)],
|
||||
);
|
||||
|
||||
db_service.execute(query).await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark a sync operation as failed with error message
|
||||
#[tauri::command]
|
||||
pub async fn sync_mark_failed(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
id: i64,
|
||||
error: String,
|
||||
) -> 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(
|
||||
"UPDATE sync_queue
|
||||
SET status = 'failed',
|
||||
retry_count = retry_count + 1,
|
||||
error_message = ?,
|
||||
processed_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?",
|
||||
vec![QueryParam::String(error), QueryParam::Int64(id)],
|
||||
);
|
||||
|
||||
db_service.execute(query).await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get count of pending sync operations for a user
|
||||
#[tauri::command]
|
||||
pub async fn sync_get_pending_count(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
) -> Result<i32, 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 sync_queue WHERE user_id = ? AND status IN ('pending', 'failed')",
|
||||
vec![QueryParam::String(user_id)],
|
||||
);
|
||||
|
||||
db_service
|
||||
.query_one(query, |row| row.get(0))
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Delete completed sync operations older than specified days
|
||||
#[tauri::command]
|
||||
pub async fn sync_cleanup_completed(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
days_old: i32,
|
||||
) -> Result<i32, String> {
|
||||
let db_service = {
|
||||
let database = db.0.lock().map_err(|e| e.to_string())?;
|
||||
Arc::new(database.service())
|
||||
};
|
||||
|
||||
let query = Query::with_params(
|
||||
"DELETE FROM sync_queue
|
||||
WHERE status = 'completed'
|
||||
AND processed_at < datetime('now', ?)",
|
||||
vec![QueryParam::String(format!("-{} days", days_old))],
|
||||
);
|
||||
|
||||
let deleted = db_service.execute(query).await.map_err(|e| e.to_string())?;
|
||||
Ok(deleted as i32)
|
||||
}
|
||||
|
||||
/// Delete all sync operations for a user (used during logout)
|
||||
#[tauri::command]
|
||||
pub async fn sync_clear_user(
|
||||
db: State<'_, DatabaseWrapper>,
|
||||
user_id: String,
|
||||
) -> 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(
|
||||
"DELETE FROM sync_queue WHERE user_id = ?",
|
||||
vec![QueryParam::String(user_id)],
|
||||
);
|
||||
|
||||
db_service.execute(query).await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user