//! Sync state helpers: key-value store and device registration. use std::collections::HashMap; use goingson_core::CoreError; use sqlx::SqlitePool; use synckit_client::SyncKitClient; use tracing::info; use uuid::Uuid; pub async fn get_sync_state(pool: &SqlitePool, key: &str) -> Result { let row: Option<(String,)> = sqlx::query_as("SELECT value FROM sync_state WHERE key = ?") .bind(key) .fetch_optional(pool) .await .map_err(CoreError::database)?; Ok(row.map(|r| r.0).unwrap_or_default()) } pub async fn get_sync_states_batch(pool: &SqlitePool, keys: &[&str]) -> Result, CoreError> { if keys.is_empty() { return Ok(HashMap::new()); } let placeholders = keys.iter().map(|_| "?").collect::>().join(","); let query = format!("SELECT key, value FROM sync_state WHERE key IN ({})", placeholders); let mut q = sqlx::query_as::<_, (String, String)>(&query); for key in keys { q = q.bind(*key); } let rows = q.fetch_all(pool).await.map_err(CoreError::database)?; Ok(rows.into_iter().collect()) } pub async fn set_sync_state(pool: &SqlitePool, key: &str, value: &str) -> Result<(), CoreError> { sqlx::query("INSERT OR REPLACE INTO sync_state (key, value) VALUES (?, ?)") .bind(key) .bind(value) .execute(pool) .await .map_err(CoreError::database)?; Ok(()) } pub async fn ensure_device_registered( pool: &SqlitePool, client: &SyncKitClient, ) -> Result { let stored = get_sync_state(pool, "device_id").await?; if !stored.is_empty() { return stored .parse::() .map_err(|e| CoreError::parse(format!("invalid stored device_id: {}", e))); } let hostname = std::env::var("HOSTNAME") .or_else(|_| std::env::var("COMPUTERNAME")) .unwrap_or_else(|_| "GoingsOn Desktop".to_string()); let platform = std::env::consts::OS.to_string(); let device = client .register_device(&hostname, &platform) .await .map_err(|e| CoreError::sync(format!("device registration failed: {}", e)))?; set_sync_state(pool, "device_id", &device.id.to_string()).await?; info!("Registered device: {} ({})", device.device_name, device.id); Ok(device.id) }