1use crate::sync::engine::TabsEngine;
6use crate::TabsStore;
7use anyhow::Result;
8use std::sync::Arc;
9use sync15::bso::{IncomingBso, OutgoingBso};
10use sync15::engine::{BridgedEngine, BridgedEngineAdaptor};
11use sync15::ServerTimestamp;
12use sync_guid::Guid as SyncGuid;
13
14impl TabsStore {
15 pub fn bridged_engine(self: Arc<Self>) -> Arc<TabsBridgedEngine> {
17 let engine = TabsEngine::new(self);
18 let bridged_engine = TabsBridgedEngineAdaptor { engine };
19 Arc::new(TabsBridgedEngine::new(Box::new(bridged_engine)))
20 }
21}
22
23struct TabsBridgedEngineAdaptor {
29 engine: TabsEngine,
30}
31
32impl BridgedEngineAdaptor for TabsBridgedEngineAdaptor {
33 fn last_sync(&self) -> Result<i64> {
34 Ok(self.engine.get_last_sync()?.unwrap_or_default().as_millis())
35 }
36
37 fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
38 self.engine
39 .set_last_sync(ServerTimestamp::from_millis(last_sync_millis))
40 }
41
42 fn engine(&self) -> &dyn sync15::engine::SyncEngine {
43 &self.engine
44 }
45}
46
47pub struct TabsBridgedEngine {
49 bridge_impl: Box<dyn BridgedEngine>,
50}
51
52impl TabsBridgedEngine {
53 pub fn new(bridge_impl: Box<dyn BridgedEngine>) -> Self {
54 Self { bridge_impl }
55 }
56
57 pub fn last_sync(&self) -> Result<i64> {
58 self.bridge_impl.last_sync()
59 }
60
61 pub fn set_last_sync(&self, last_sync: i64) -> Result<()> {
62 self.bridge_impl.set_last_sync(last_sync)
63 }
64
65 pub fn sync_id(&self) -> Result<Option<String>> {
66 self.bridge_impl.sync_id()
67 }
68
69 pub fn reset_sync_id(&self) -> Result<String> {
70 self.bridge_impl.reset_sync_id()
71 }
72
73 pub fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String> {
74 self.bridge_impl.ensure_current_sync_id(sync_id)
75 }
76
77 pub fn prepare_for_sync(&self, client_data: &str) -> Result<()> {
78 self.bridge_impl.prepare_for_sync(client_data)
79 }
80
81 pub fn sync_started(&self) -> Result<()> {
82 self.bridge_impl.sync_started()
83 }
84
85 fn convert_incoming_bsos(&self, incoming: Vec<String>) -> Result<Vec<IncomingBso>> {
87 let mut bsos = Vec::with_capacity(incoming.len());
88 for inc in incoming {
89 bsos.push(serde_json::from_str::<IncomingBso>(&inc)?);
90 }
91 Ok(bsos)
92 }
93
94 fn convert_outgoing_bsos(&self, outgoing: Vec<OutgoingBso>) -> Result<Vec<String>> {
96 let mut bsos = Vec::with_capacity(outgoing.len());
97 for e in outgoing {
98 bsos.push(serde_json::to_string(&e)?);
99 }
100 Ok(bsos)
101 }
102
103 pub fn store_incoming(&self, incoming: Vec<String>) -> Result<()> {
104 self.bridge_impl
105 .store_incoming(self.convert_incoming_bsos(incoming)?)
106 }
107
108 pub fn apply(&self) -> Result<Vec<String>> {
109 let apply_results = self.bridge_impl.apply()?;
110 self.convert_outgoing_bsos(apply_results.records)
111 }
112
113 pub fn set_uploaded(&self, server_modified_millis: i64, guids: Vec<SyncGuid>) -> Result<()> {
114 self.bridge_impl
115 .set_uploaded(server_modified_millis, &guids)
116 }
117
118 pub fn sync_finished(&self) -> Result<()> {
119 self.bridge_impl.sync_finished()
120 }
121
122 pub fn reset(&self) -> Result<()> {
123 self.bridge_impl.reset()
124 }
125
126 pub fn wipe(&self) -> Result<()> {
127 self.bridge_impl.wipe()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::storage::{RemoteTab, TABS_CLIENT_TTL};
135 use crate::sync::record::TabsRecordTab;
136 use serde_json::json;
137 use std::collections::HashMap;
138 use sync15::{ClientData, DeviceType, RemoteClient};
139
140 #[test]
142 fn test_sync_via_bridge() {
143 error_support::init_for_tests();
144
145 let store = Arc::new(TabsStore::new_with_mem_path("test-bridge_incoming"));
146
147 let my_tabs = vec![
149 RemoteTab {
150 title: "my first tab".to_string(),
151 url_history: vec!["http://1.com".to_string()],
152 last_used: 2,
153 ..Default::default()
154 },
155 RemoteTab {
156 title: "my second tab".to_string(),
157 url_history: vec!["http://2.com".to_string()],
158 last_used: 1,
159 ..Default::default()
160 },
161 ];
162 store.set_local_tabs(my_tabs.clone());
163
164 let bridge = store.bridged_engine();
165
166 let client_data = ClientData {
167 local_client_id: "my-device".to_string(),
168 recent_clients: HashMap::from([
169 (
170 "my-device".to_string(),
171 RemoteClient {
172 fxa_device_id: None,
173 device_name: "my device".to_string(),
174 device_type: sync15::DeviceType::Unknown,
175 },
176 ),
177 (
178 "device-no-tabs".to_string(),
179 RemoteClient {
180 fxa_device_id: None,
181 device_name: "device with no tabs".to_string(),
182 device_type: DeviceType::Unknown,
183 },
184 ),
185 (
186 "device-with-a-tab".to_string(),
187 RemoteClient {
188 fxa_device_id: None,
189 device_name: "device with a tab".to_string(),
190 device_type: DeviceType::Unknown,
191 },
192 ),
193 ]),
194 };
195 bridge
196 .prepare_for_sync(&serde_json::to_string(&client_data).unwrap())
197 .expect("should work");
198
199 let records = vec![
200 json!({
203 "id": "my-device",
204 "clientName": "my device",
205 "tabs": [{
206 "title": "the title",
207 "urlHistory": [
208 "https://mozilla.org/"
209 ],
210 "icon": "https://mozilla.org/icon",
211 "lastUsed": 1643764207
212 }]
213 }),
214 json!({
215 "id": "device-no-tabs",
216 "clientName": "device with no tabs",
217 "tabs": [],
218 }),
219 json!({
220 "id": "device-with-a-tab",
221 "clientName": "device with a tab",
222 "tabs": [{
223 "title": "the title",
224 "urlHistory": [
225 "https://mozilla.org/"
226 ],
227 "icon": "https://mozilla.org/icon",
228 "lastUsed": 1643764207
229 }]
230 }),
231 json!({
233 "id": "device-with-invalid-tab",
234 "clientName": "device with a tab",
235 "tabs": [{
236 "foo": "bar",
237 }]
238 }),
239 json!({
241 "id": "invalid-tab",
242 "foo": "bar"
243 }),
244 ];
245
246 let mut incoming = Vec::new();
247 for record in records {
248 let envelope = json!({
251 "id": record.get("id"),
252 "modified": 0,
253 "payload": serde_json::to_string(&record).unwrap(),
254 });
255 incoming.push(serde_json::to_string(&envelope).unwrap());
256 }
257
258 bridge.store_incoming(incoming).expect("should store");
259
260 let out = bridge.apply().expect("should apply");
261
262 assert_eq!(out.len(), 1);
263 let ours = serde_json::from_str::<serde_json::Value>(&out[0]).unwrap();
264 let expected_tabs: Vec<TabsRecordTab> =
267 my_tabs.into_iter().map(|t| t.to_record_tab()).collect();
268 let expected = json!({
269 "id": "my-device".to_string(),
270 "payload": json!({
271 "id": "my-device".to_string(),
272 "clientName": "my device",
273 "tabs": serde_json::to_value(expected_tabs).unwrap(),
274 }).to_string(),
275 "ttl": TABS_CLIENT_TTL,
276 });
277
278 assert_eq!(ours, expected);
279 bridge.set_uploaded(1234, vec![]).unwrap();
280 assert_eq!(bridge.last_sync().unwrap(), 1234);
281 }
282
283 #[test]
284 fn test_sync_meta() {
285 error_support::init_for_tests();
286
287 let store = Arc::new(TabsStore::new_with_mem_path("test-meta"));
288 let bridge = store.bridged_engine();
289
290 assert_eq!(bridge.last_sync().unwrap(), 0);
292 bridge.set_last_sync(3).unwrap();
293 assert_eq!(bridge.last_sync().unwrap(), 3);
294
295 assert!(bridge.sync_id().unwrap().is_none());
296
297 bridge.ensure_current_sync_id("some_guid").unwrap();
298 assert_eq!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
299 assert_eq!(bridge.last_sync().unwrap(), 0);
301 bridge.set_last_sync(3).unwrap();
302
303 bridge.reset_sync_id().unwrap();
304 assert_ne!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
306 assert_eq!(bridge.last_sync().unwrap(), 0);
308 bridge.set_last_sync(3).unwrap();
309
310 bridge.reset().unwrap();
312 assert_eq!(bridge.last_sync().unwrap(), 0);
313 assert!(bridge.sync_id().unwrap().is_none());
314 }
315}