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