1use anyhow::Result;
6use rusqlite::Transaction;
7use std::sync::{Arc, Weak};
8use sync15::bso::IncomingBso;
9use sync15::engine::{ApplyResults, BridgedEngine as Sync15BridgedEngine};
10use sync_guid::Guid as SyncGuid;
11
12use crate::db::{delete_meta, get_meta, put_meta, ThreadSafeStorageDb};
13use crate::schema;
14use crate::sync::incoming::{apply_actions, get_incoming, plan_incoming, stage_incoming};
15use crate::sync::outgoing::{get_outgoing, record_uploaded, stage_outgoing};
16use crate::WebExtStorageStore;
17
18const LAST_SYNC_META_KEY: &str = "last_sync_time";
19const SYNC_ID_META_KEY: &str = "sync_id";
20
21impl WebExtStorageStore {
22 pub fn bridged_engine(self: Arc<Self>) -> Arc<WebExtStorageBridgedEngine> {
24 let engine = Box::new(BridgedEngine::new(&self.db));
25 Arc::new(WebExtStorageBridgedEngine::new(engine))
26 }
27}
28
29pub struct BridgedEngine {
39 db: Weak<ThreadSafeStorageDb>,
40}
41
42impl BridgedEngine {
43 pub fn new(db: &Arc<ThreadSafeStorageDb>) -> Self {
45 BridgedEngine {
46 db: Arc::downgrade(db),
47 }
48 }
49
50 fn do_reset(&self, tx: &Transaction<'_>) -> Result<()> {
51 tx.execute_batch(
52 "DELETE FROM storage_sync_mirror;
53 UPDATE storage_sync_data SET sync_change_counter = 1;",
54 )?;
55 delete_meta(tx, LAST_SYNC_META_KEY)?;
56 Ok(())
57 }
58
59 fn thread_safe_storage_db(&self) -> Result<Arc<ThreadSafeStorageDb>> {
60 self.db
61 .upgrade()
62 .ok_or_else(|| crate::error::Error::DatabaseConnectionClosed.into())
63 }
64}
65
66impl Sync15BridgedEngine for BridgedEngine {
67 fn last_sync(&self) -> Result<i64> {
68 let shared_db = self.thread_safe_storage_db()?;
69 let db = shared_db.lock();
70 let conn = db.get_connection()?;
71 Ok(get_meta(conn, LAST_SYNC_META_KEY)?.unwrap_or(0))
72 }
73
74 fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
75 let shared_db = self.thread_safe_storage_db()?;
76 let db = shared_db.lock();
77 let conn = db.get_connection()?;
78 put_meta(conn, LAST_SYNC_META_KEY, &last_sync_millis)?;
79 Ok(())
80 }
81
82 fn sync_id(&self) -> Result<Option<String>> {
83 let shared_db = self.thread_safe_storage_db()?;
84 let db = shared_db.lock();
85 let conn = db.get_connection()?;
86 Ok(get_meta(conn, SYNC_ID_META_KEY)?)
87 }
88
89 fn reset_sync_id(&self) -> Result<String> {
90 let shared_db = self.thread_safe_storage_db()?;
91 let db = shared_db.lock();
92 let conn = db.get_connection()?;
93 let tx = conn.unchecked_transaction()?;
94 let new_id = SyncGuid::random().to_string();
95 self.do_reset(&tx)?;
96 put_meta(&tx, SYNC_ID_META_KEY, &new_id)?;
97 tx.commit()?;
98 Ok(new_id)
99 }
100
101 fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String> {
102 let shared_db = self.thread_safe_storage_db()?;
103 let db = shared_db.lock();
104 let conn = db.get_connection()?;
105 let current: Option<String> = get_meta(conn, SYNC_ID_META_KEY)?;
106 Ok(match current {
107 Some(current) if current == sync_id => current,
108 _ => {
109 let conn = db.get_connection()?;
110 let tx = conn.unchecked_transaction()?;
111 self.do_reset(&tx)?;
112 let result = sync_id.to_string();
113 put_meta(&tx, SYNC_ID_META_KEY, &result)?;
114 tx.commit()?;
115 result
116 }
117 })
118 }
119
120 fn sync_started(&self) -> Result<()> {
121 let shared_db = self.thread_safe_storage_db()?;
122 let db = shared_db.lock();
123 let conn = db.get_connection()?;
124 schema::create_empty_sync_temp_tables(conn)?;
125 Ok(())
126 }
127
128 fn store_incoming(&self, incoming_bsos: Vec<IncomingBso>) -> Result<()> {
129 let shared_db = self.thread_safe_storage_db()?;
130 let db = shared_db.lock();
131 let signal = db.begin_interrupt_scope()?;
132 let conn = db.get_connection()?;
133 let tx = conn.unchecked_transaction()?;
134 let incoming_content: Vec<_> = incoming_bsos
135 .into_iter()
136 .map(IncomingBso::into_content::<super::WebextRecord>)
137 .collect();
138 stage_incoming(&tx, &incoming_content, &signal)?;
139 tx.commit()?;
140 Ok(())
141 }
142
143 fn apply(&self) -> Result<ApplyResults> {
144 let shared_db = self.thread_safe_storage_db()?;
145 let db = shared_db.lock();
146 let signal = db.begin_interrupt_scope()?;
147 let conn = db.get_connection()?;
148 let tx = conn.unchecked_transaction()?;
149 let incoming = get_incoming(&tx)?;
150 let actions = incoming
151 .into_iter()
152 .map(|(item, state)| (item, plan_incoming(state)))
153 .collect();
154 apply_actions(&tx, actions, &signal)?;
155 stage_outgoing(&tx)?;
156 tx.commit()?;
157
158 Ok(get_outgoing(conn, &signal)?.into())
159 }
160
161 fn set_uploaded(&self, _server_modified_millis: i64, ids: &[SyncGuid]) -> Result<()> {
162 let shared_db = self.thread_safe_storage_db()?;
163 let db = shared_db.lock();
164 let conn = db.get_connection()?;
165 let signal = db.begin_interrupt_scope()?;
166 let tx = conn.unchecked_transaction()?;
167 record_uploaded(&tx, ids, &signal)?;
168 tx.commit()?;
169
170 Ok(())
171 }
172
173 fn sync_finished(&self) -> Result<()> {
174 let shared_db = self.thread_safe_storage_db()?;
175 let db = shared_db.lock();
176 let conn = db.get_connection()?;
177 schema::create_empty_sync_temp_tables(conn)?;
178 Ok(())
179 }
180
181 fn reset(&self) -> Result<()> {
182 let shared_db = self.thread_safe_storage_db()?;
183 let db = shared_db.lock();
184 let conn = db.get_connection()?;
185 let tx = conn.unchecked_transaction()?;
186 self.do_reset(&tx)?;
187 delete_meta(&tx, SYNC_ID_META_KEY)?;
188 tx.commit()?;
189 Ok(())
190 }
191
192 fn wipe(&self) -> Result<()> {
193 let shared_db = self.thread_safe_storage_db()?;
194 let db = shared_db.lock();
195 let conn = db.get_connection()?;
196 let tx = conn.unchecked_transaction()?;
197 tx.execute_batch(
199 "DELETE FROM storage_sync_data; DELETE FROM storage_sync_mirror; DELETE FROM meta;",
200 )?;
201 tx.commit()?;
202 Ok(())
203 }
204}
205
206sync15::uniffi_bridged_engine!(WebExtStorageBridgedEngine, sync_guid::Guid);
214
215impl From<anyhow::Error> for crate::error::Error {
216 fn from(value: anyhow::Error) -> Self {
217 crate::error::Error::SyncError(value.to_string())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::db::test::new_mem_thread_safe_storage_db;
225 use crate::db::StorageDb;
226 use sync15::engine::BridgedEngine;
227
228 fn query_count(db: &StorageDb, table: &str) -> u32 {
229 let conn = db.get_connection().expect("should retrieve connection");
230 conn.query_row_and_then(&format!("SELECT COUNT(*) FROM {};", table), [], |row| {
231 row.get::<_, u32>(0)
232 })
233 .expect("should work")
234 }
235
236 fn setup_mock_data(engine: &super::BridgedEngine) -> Result<()> {
238 {
239 let shared = engine.thread_safe_storage_db()?;
240 let db = shared.lock();
241 let conn = db.get_connection().expect("should retrieve connection");
242 conn.execute(
243 "INSERT INTO storage_sync_data (ext_id, data, sync_change_counter)
244 VALUES ('ext-a', 'invalid-json', 2)",
245 [],
246 )?;
247 conn.execute(
248 "INSERT INTO storage_sync_mirror (guid, ext_id, data)
249 VALUES ('guid', 'ext-a', '3')",
250 [],
251 )?;
252 }
253 engine.set_last_sync(1)?;
254
255 let shared = engine.thread_safe_storage_db()?;
256 let db = shared.lock();
257 assert_eq!(query_count(&db, "storage_sync_data"), 1);
259 assert_eq!(query_count(&db, "storage_sync_mirror"), 1);
260 assert_eq!(query_count(&db, "meta"), 1);
261 Ok(())
262 }
263
264 fn assert_reset(engine: &super::BridgedEngine) -> Result<()> {
266 let shared = engine.thread_safe_storage_db()?;
268 let db = shared.lock();
269 let conn = db.get_connection().expect("should retrieve connection");
270 assert_eq!(query_count(&db, "storage_sync_data"), 1);
271
272 let cc = conn.query_row_and_then(
274 "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext-a';",
275 [],
276 |row| row.get::<_, u32>(0),
277 )?;
278 assert_eq!(cc, 1);
279 assert_eq!(query_count(&db, "storage_sync_mirror"), 0);
281 assert!(get_meta::<i64>(conn, LAST_SYNC_META_KEY)?.is_none());
283 Ok(())
284 }
285
286 fn assert_not_reset(engine: &super::BridgedEngine) -> Result<()> {
288 let shared = engine.thread_safe_storage_db()?;
289 let db = shared.lock();
290 let conn = db.get_connection().expect("should retrieve connection");
291 assert_eq!(query_count(&db, "storage_sync_data"), 1);
292 let cc = conn.query_row_and_then(
293 "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext-a';",
294 [],
295 |row| row.get::<_, u32>(0),
296 )?;
297 assert_eq!(cc, 2);
298 assert_eq!(query_count(&db, "storage_sync_mirror"), 1);
299 assert!(get_meta::<i64>(conn, LAST_SYNC_META_KEY)?.is_some());
301 Ok(())
302 }
303
304 #[test]
305 fn test_wipe() -> Result<()> {
306 let strong = new_mem_thread_safe_storage_db();
307 let engine = super::BridgedEngine::new(&strong);
308
309 setup_mock_data(&engine)?;
310
311 engine.wipe()?;
312
313 let shared = engine.thread_safe_storage_db()?;
314 let db = shared.lock();
315
316 assert_eq!(query_count(&db, "storage_sync_data"), 0);
317 assert_eq!(query_count(&db, "storage_sync_mirror"), 0);
318 assert_eq!(query_count(&db, "meta"), 0);
319 Ok(())
320 }
321
322 #[test]
323 fn test_reset() -> Result<()> {
324 let strong = &new_mem_thread_safe_storage_db();
325 let engine = super::BridgedEngine::new(strong);
326
327 setup_mock_data(&engine)?;
328 {
329 let db = strong.lock();
330 let conn = db.get_connection()?;
331 put_meta(conn, SYNC_ID_META_KEY, &"sync-id".to_string())?;
332 }
333
334 engine.reset()?;
335 assert_reset(&engine)?;
336
337 {
338 let db = strong.lock();
339 let conn = db.get_connection()?;
340 assert_eq!(get_meta::<String>(conn, SYNC_ID_META_KEY)?, None);
342 }
343
344 Ok(())
345 }
346
347 #[test]
348 fn test_ensure_missing_sync_id() -> Result<()> {
349 let strong = new_mem_thread_safe_storage_db();
350 let engine = super::BridgedEngine::new(&strong);
351
352 setup_mock_data(&engine)?;
353
354 assert_eq!(engine.sync_id()?, None);
355 engine.ensure_current_sync_id("new-id")?;
357 assert_reset(&engine)?;
359 Ok(())
360 }
361
362 #[test]
363 fn test_ensure_new_sync_id() -> Result<()> {
364 let strong = new_mem_thread_safe_storage_db();
365 let engine = super::BridgedEngine::new(&strong);
366
367 setup_mock_data(&engine)?;
368
369 {
370 let storage_db = &engine.thread_safe_storage_db()?;
371 let db = storage_db.lock();
372 let conn = db.get_connection()?;
373 put_meta(conn, SYNC_ID_META_KEY, &"old-id".to_string())?;
374 }
375
376 assert_not_reset(&engine)?;
377 assert_eq!(engine.sync_id()?, Some("old-id".to_string()));
378
379 engine.ensure_current_sync_id("new-id")?;
380 assert_reset(&engine)?;
382 assert_eq!(engine.sync_id()?, Some("new-id".to_string()));
384 Ok(())
385 }
386
387 #[test]
388 fn test_ensure_same_sync_id() -> Result<()> {
389 let strong = new_mem_thread_safe_storage_db();
390 let engine = super::BridgedEngine::new(&strong);
391
392 setup_mock_data(&engine)?;
393 assert_not_reset(&engine)?;
394
395 {
396 let storage_db = &engine.thread_safe_storage_db()?;
397 let db = storage_db.lock();
398 let conn = db.get_connection()?;
399 put_meta(conn, SYNC_ID_META_KEY, &"sync-id".to_string())?;
400 }
401
402 engine.ensure_current_sync_id("sync-id")?;
403 assert_not_reset(&engine)?;
405 Ok(())
406 }
407
408 #[test]
409 fn test_reset_sync_id() -> Result<()> {
410 let strong = new_mem_thread_safe_storage_db();
411 let engine = super::BridgedEngine::new(&strong);
412
413 setup_mock_data(&engine)?;
414
415 {
416 let storage_db = &engine.thread_safe_storage_db()?;
417 let db = storage_db.lock();
418 let conn = db.get_connection()?;
419 put_meta(conn, SYNC_ID_META_KEY, &"sync-id".to_string())?;
420 }
421
422 assert_eq!(engine.sync_id()?, Some("sync-id".to_string()));
423 let new_id = engine.reset_sync_id()?;
424 assert_reset(&engine)?;
426 assert_eq!(engine.sync_id()?, Some(new_id));
427 Ok(())
428 }
429}