cooklang_sync_client/
context.rs

1use std::sync::Arc;
2use tokio_util::sync::CancellationToken;
3
4use crate::models::SyncStatus;
5
6/// Trait for receiving sync status updates
7/// Implementations of this trait in foreign languages (Swift, etc.) will receive
8/// real-time status updates during sync operations
9#[uniffi::export(with_foreign)]
10pub trait SyncStatusListener: Send + Sync {
11    fn on_status_changed(&self, status: SyncStatus);
12    fn on_complete(&self, success: bool, message: Option<String>);
13}
14
15/// Context for managing sync lifecycle, cancellation, and status updates
16#[derive(uniffi::Object)]
17pub struct SyncContext {
18    cancellation_token: CancellationToken,
19    status_listener: std::sync::Mutex<Option<Arc<dyn SyncStatusListener>>>,
20}
21
22#[uniffi::export]
23impl SyncContext {
24    /// Creates a new sync context
25    #[uniffi::constructor]
26    pub fn new() -> Arc<Self> {
27        Arc::new(Self {
28            cancellation_token: CancellationToken::new(),
29            status_listener: std::sync::Mutex::new(None),
30        })
31    }
32
33    /// Sets the status listener for this context
34    pub fn set_listener(&self, listener: Arc<dyn SyncStatusListener>) {
35        // Handle poisoned mutex by recovering the guard
36        let mut listener_lock = self
37            .status_listener
38            .lock()
39            .unwrap_or_else(|e| e.into_inner());
40        *listener_lock = Some(listener);
41    }
42
43    /// Cancels the sync operation
44    pub fn cancel(&self) {
45        self.cancellation_token.cancel();
46    }
47}
48
49impl SyncContext {
50    /// Notifies the status listener if one is set (internal use only)
51    pub fn notify_status(&self, status: SyncStatus) {
52        // Handle poisoned mutex by recovering the guard
53        let listener_lock = self
54            .status_listener
55            .lock()
56            .unwrap_or_else(|e| e.into_inner());
57        if let Some(listener) = listener_lock.as_ref() {
58            listener.on_status_changed(status);
59        }
60    }
61
62    /// Returns a child token for passing to async tasks (internal use only)
63    ///
64    /// This uses `child_token()` rather than `clone()` to enable hierarchical cancellation:
65    /// - When the parent context is cancelled, all child tokens are automatically cancelled
66    /// - This allows the sync operation to spawn multiple tasks that all respect cancellation
67    /// - Cancelling a child token does NOT cancel the parent or sibling tasks
68    ///
69    /// This pattern is recommended by tokio-util for managing cancellation across task hierarchies.
70    pub fn token(&self) -> CancellationToken {
71        self.cancellation_token.child_token()
72    }
73
74    /// Returns a clone of the status listener (internal use only)
75    pub fn listener(&self) -> Option<Arc<dyn SyncStatusListener>> {
76        // Handle poisoned mutex by recovering the guard
77        let listener_lock = self
78            .status_listener
79            .lock()
80            .unwrap_or_else(|e| e.into_inner());
81        listener_lock.clone()
82    }
83}