matrix_sdk_ffi/
sync_service.rs1use std::{fmt::Debug, sync::Arc, time::Duration};
16
17use futures_util::pin_mut;
18use matrix_sdk::{crypto::types::events::UtdCause, Client};
19use matrix_sdk_ui::{
20 sync_service::{
21 State as MatrixSyncServiceState, SyncService as MatrixSyncService,
22 SyncServiceBuilder as MatrixSyncServiceBuilder,
23 },
24 unable_to_decrypt_hook::{
25 UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo, UtdHookManager,
26 },
27};
28use tracing::error;
29
30use crate::{
31 error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService, TaskHandle,
32 RUNTIME,
33};
34
35#[derive(uniffi::Enum)]
36pub enum SyncServiceState {
37 Idle,
38 Running,
39 Terminated,
40 Error,
41 Offline,
42}
43
44impl From<MatrixSyncServiceState> for SyncServiceState {
45 fn from(value: MatrixSyncServiceState) -> Self {
46 match value {
47 MatrixSyncServiceState::Idle => Self::Idle,
48 MatrixSyncServiceState::Running => Self::Running,
49 MatrixSyncServiceState::Terminated => Self::Terminated,
50 MatrixSyncServiceState::Error => Self::Error,
51 MatrixSyncServiceState::Offline => Self::Offline,
52 }
53 }
54}
55
56#[matrix_sdk_ffi_macros::export(callback_interface)]
57pub trait SyncServiceStateObserver: Send + Sync + Debug {
58 fn on_update(&self, state: SyncServiceState);
59}
60
61#[derive(uniffi::Object)]
62pub struct SyncService {
63 pub(crate) inner: Arc<MatrixSyncService>,
64 utd_hook: Option<Arc<UtdHookManager>>,
65}
66
67#[matrix_sdk_ffi_macros::export]
68impl SyncService {
69 pub fn room_list_service(&self) -> Arc<RoomListService> {
70 Arc::new(RoomListService {
71 inner: self.inner.room_list_service(),
72 utd_hook: self.utd_hook.clone(),
73 })
74 }
75
76 pub async fn start(&self) {
77 self.inner.start().await
78 }
79
80 pub async fn stop(&self) {
81 self.inner.stop().await
82 }
83
84 pub fn state(&self, listener: Box<dyn SyncServiceStateObserver>) -> Arc<TaskHandle> {
85 let state_stream = self.inner.state();
86
87 Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
88 pin_mut!(state_stream);
89
90 while let Some(state) = state_stream.next().await {
91 listener.on_update(state.into());
92 }
93 })))
94 }
95}
96
97#[derive(Clone, uniffi::Object)]
98pub struct SyncServiceBuilder {
99 client: Client,
100 builder: MatrixSyncServiceBuilder,
101
102 utd_hook: Option<Arc<UtdHookManager>>,
103}
104
105impl SyncServiceBuilder {
106 pub(crate) fn new(client: Client) -> Arc<Self> {
107 Arc::new(Self {
108 client: client.clone(),
109 builder: MatrixSyncService::builder(client),
110 utd_hook: None,
111 })
112 }
113}
114
115#[matrix_sdk_ffi_macros::export]
116impl SyncServiceBuilder {
117 pub fn with_cross_process_lock(self: Arc<Self>) -> Arc<Self> {
118 let this = unwrap_or_clone_arc(self);
119 let builder = this.builder.with_cross_process_lock();
120 Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
121 }
122
123 pub fn with_offline_mode(self: Arc<Self>) -> Arc<Self> {
125 let this = unwrap_or_clone_arc(self);
126 let builder = this.builder.with_offline_mode();
127 Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
128 }
129
130 pub async fn with_utd_hook(
131 self: Arc<Self>,
132 delegate: Box<dyn UnableToDecryptDelegate>,
133 ) -> Arc<Self> {
134 const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
137
138 let this = unwrap_or_clone_arc(self);
139
140 let mut utd_hook = UtdHookManager::new(Arc::new(UtdHook { delegate }), this.client.clone())
141 .with_max_delay(UTD_HOOK_GRACE_PERIOD);
142
143 if let Err(e) = utd_hook.reload_from_store().await {
144 error!("Unable to reload UTD hook data from data store: {}", e);
145 }
148
149 Arc::new(Self {
150 client: this.client,
151 builder: this.builder,
152 utd_hook: Some(Arc::new(utd_hook)),
153 })
154 }
155
156 pub async fn finish(self: Arc<Self>) -> Result<Arc<SyncService>, ClientError> {
157 let this = unwrap_or_clone_arc(self);
158 Ok(Arc::new(SyncService {
159 inner: Arc::new(this.builder.build().await?),
160 utd_hook: this.utd_hook,
161 }))
162 }
163}
164
165#[matrix_sdk_ffi_macros::export(callback_interface)]
166pub trait UnableToDecryptDelegate: Sync + Send {
167 fn on_utd(&self, info: UnableToDecryptInfo);
168}
169
170struct UtdHook {
171 delegate: Box<dyn UnableToDecryptDelegate>,
172}
173
174impl std::fmt::Debug for UtdHook {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.debug_struct("UtdHook").finish_non_exhaustive()
177 }
178}
179
180impl UnableToDecryptHook for UtdHook {
181 fn on_utd(&self, info: SdkUnableToDecryptInfo) {
182 const IGNORE_UTD_PERIOD: Duration = Duration::from_secs(4);
183
184 if let Some(duration) = &info.time_to_decrypt {
187 if *duration < IGNORE_UTD_PERIOD {
188 return;
189 }
190 }
191
192 self.delegate.on_utd(info.into());
194 }
195}
196
197#[derive(uniffi::Record)]
198pub struct UnableToDecryptInfo {
199 event_id: String,
201
202 pub time_to_decrypt_ms: Option<u64>,
209
210 pub cause: UtdCause,
213
214 pub event_local_age_millis: i64,
218
219 pub user_trusts_own_identity: bool,
222
223 pub sender_homeserver: String,
225
226 pub own_homeserver: Option<String>,
229}
230
231impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
232 fn from(value: SdkUnableToDecryptInfo) -> Self {
233 Self {
234 event_id: value.event_id.to_string(),
235 time_to_decrypt_ms: value.time_to_decrypt.map(|ttd| ttd.as_millis() as u64),
236 cause: value.cause,
237 event_local_age_millis: value.event_local_age_millis,
238 user_trusts_own_identity: value.user_trusts_own_identity,
239 sender_homeserver: value.sender_homeserver.to_string(),
240 own_homeserver: value.own_homeserver.map(String::from),
241 }
242 }
243}