//! Tauri-specific extensions for yaak-sync. //! //! This module provides the Tauri commands for sync functionality. use crate::error::Result; use crate::models_ext::{BlobManagerExt, QueryManagerExt}; use chrono::Utc; use log::warn; use std::path::Path; use tauri::{AppHandle, Listener, Runtime}; use tokio::sync::watch; use yaak_rpc_schema::WatchResult; use yaak_sync::error::Error::InvalidSyncDirectory; use yaak_sync::sync::{ FsCandidate, SyncOp, apply_sync_ops, apply_sync_state_ops, compute_sync_ops, get_db_candidates, get_fs_candidates, }; use yaak_sync::watch::{WatchEvent, watch_directory}; pub(crate) async fn cmd_sync_calculate( app_handle: AppHandle, workspace_id: &str, sync_dir: &Path, ) -> Result> { if !sync_dir.exists() { return Err(InvalidSyncDirectory(sync_dir.to_string_lossy().to_string()).into()); } let db = app_handle.db(); let version = app_handle.package_info().version.to_string(); let db_candidates = get_db_candidates(&db, &version, workspace_id, sync_dir)?; let fs_candidates = get_fs_candidates(sync_dir)? .into_iter() // Only keep items in the same workspace .filter(|fs| fs.model.workspace_id() == workspace_id) .collect::>(); Ok(compute_sync_ops(db_candidates, fs_candidates)) } pub(crate) async fn cmd_sync_calculate_fs(dir: &Path) -> Result> { let db_candidates = Vec::new(); let fs_candidates = get_fs_candidates(dir)?; Ok(compute_sync_ops(db_candidates, fs_candidates)) } pub(crate) async fn cmd_sync_apply( app_handle: AppHandle, sync_ops: Vec, sync_dir: &Path, workspace_id: &str, ) -> Result<()> { let db = app_handle.db(); let blobs = app_handle.blob_manager(); let sync_state_ops = apply_sync_ops(&db, &blobs, workspace_id, sync_dir, sync_ops)?; apply_sync_state_ops(&db, workspace_id, sync_dir, sync_state_ops)?; Ok(()) } pub(crate) async fn sync_watch( app_handle: AppHandle, sync_dir: &Path, workspace_id: &str, on_event: F, ) -> Result where R: Runtime, F: Fn(WatchEvent) + Send + Sync + 'static, { let (cancel_tx, cancel_rx) = watch::channel(()); watch_directory(&sync_dir, on_event, cancel_rx).await?; let app_handle_inner = app_handle.clone(); let unlisten_event = format!("watch-unlisten-{}-{}", workspace_id, Utc::now().timestamp_millis()); // TODO: Figure out a way to unlisten when the client app_handle refreshes or closes. Perhaps with // a heartbeat mechanism, or ensuring only a single subscription per workspace (at least // this won't create `n` subs). We could also maybe have a global fs watcher that we keep // adding to here. app_handle.listen_any(unlisten_event.clone(), move |event| { app_handle_inner.unlisten(event.id()); if let Err(e) = cancel_tx.send(()) { warn!("Failed to send cancel signal to watcher {e:?}"); } }); Ok(WatchResult { unlisten_event }) }