1use crate::sync::engine::TabsEngine;
6use crate::TabsStore;
7use anyhow::Result;
8use std::sync::Arc;
9use sync15::engine::BridgedEngineAdaptor;
10use sync15::ServerTimestamp;
11
12impl TabsStore {
13 pub fn bridged_engine(self: Arc<Self>) -> Arc<TabsBridgedEngine> {
15 let engine = TabsEngine::new(self);
16 let bridged_engine = TabsBridgedEngineAdaptor { engine };
17 Arc::new(TabsBridgedEngine::new(Box::new(bridged_engine)))
18 }
19}
20
21struct TabsBridgedEngineAdaptor {
27 engine: TabsEngine,
28}
29
30impl BridgedEngineAdaptor for TabsBridgedEngineAdaptor {
31 fn last_sync(&self) -> Result<i64> {
32 Ok(self.engine.get_last_sync()?.unwrap_or_default().as_millis())
33 }
34
35 fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
36 self.engine
37 .set_last_sync(ServerTimestamp::from_millis(last_sync_millis))
38 }
39
40 fn engine(&self) -> &dyn sync15::engine::SyncEngine {
41 &self.engine
42 }
43}
44
45sync15::uniffi_bridged_engine!(TabsBridgedEngine, sync_guid::Guid);
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::storage::{RemoteTab, TABS_CLIENT_TTL};
56 use crate::sync::record::TabsRecordTab;
57 use serde_json::json;
58 use std::collections::HashMap;
59 use sync15::{ClientData, DeviceType, RemoteClient};
60
61 #[test]
63 fn test_sync_via_bridge() {
64 error_support::init_for_tests();
65
66 let store = Arc::new(TabsStore::new_with_mem_path("test-bridge_incoming"));
67
68 let my_tabs = vec![
70 RemoteTab {
71 title: "my first tab".to_string(),
72 url_history: vec!["http://1.com".to_string()],
73 last_used: 2,
74 ..Default::default()
75 },
76 RemoteTab {
77 title: "my second tab".to_string(),
78 url_history: vec!["http://2.com".to_string()],
79 last_used: 1,
80 ..Default::default()
81 },
82 ];
83 store.set_local_tabs(my_tabs.clone());
84
85 let bridge = store.bridged_engine();
86
87 let client_data = ClientData {
88 local_client_id: "my-device".to_string(),
89 recent_clients: HashMap::from([
90 (
91 "my-device".to_string(),
92 RemoteClient {
93 fxa_device_id: None,
94 device_name: "my device".to_string(),
95 device_type: sync15::DeviceType::Unknown,
96 },
97 ),
98 (
99 "device-no-tabs".to_string(),
100 RemoteClient {
101 fxa_device_id: None,
102 device_name: "device with no tabs".to_string(),
103 device_type: DeviceType::Unknown,
104 },
105 ),
106 (
107 "device-with-a-tab".to_string(),
108 RemoteClient {
109 fxa_device_id: None,
110 device_name: "device with a tab".to_string(),
111 device_type: DeviceType::Unknown,
112 },
113 ),
114 ]),
115 };
116 bridge
117 .prepare_for_sync(&serde_json::to_string(&client_data).unwrap())
118 .expect("should work");
119
120 let records = vec![
121 json!({
124 "id": "my-device",
125 "clientName": "my device",
126 "tabs": [{
127 "title": "the title",
128 "urlHistory": [
129 "https://mozilla.org/"
130 ],
131 "icon": "https://mozilla.org/icon",
132 "lastUsed": 1643764207
133 }]
134 }),
135 json!({
136 "id": "device-no-tabs",
137 "clientName": "device with no tabs",
138 "tabs": [],
139 }),
140 json!({
141 "id": "device-with-a-tab",
142 "clientName": "device with a tab",
143 "tabs": [{
144 "title": "the title",
145 "urlHistory": [
146 "https://mozilla.org/"
147 ],
148 "icon": "https://mozilla.org/icon",
149 "lastUsed": 1643764207
150 }]
151 }),
152 json!({
154 "id": "device-with-invalid-tab",
155 "clientName": "device with a tab",
156 "tabs": [{
157 "foo": "bar",
158 }]
159 }),
160 json!({
162 "id": "invalid-tab",
163 "foo": "bar"
164 }),
165 ];
166
167 let mut incoming = Vec::new();
168 for record in records {
169 let envelope = json!({
172 "id": record.get("id"),
173 "modified": 0,
174 "payload": serde_json::to_string(&record).unwrap(),
175 });
176 incoming.push(serde_json::to_string(&envelope).unwrap());
177 }
178
179 bridge.store_incoming(incoming).expect("should store");
180
181 let out = bridge.apply().expect("should apply");
182
183 assert_eq!(out.len(), 1);
184 let ours = serde_json::from_str::<serde_json::Value>(&out[0]).unwrap();
185 let expected_tabs: Vec<TabsRecordTab> = my_tabs.into_iter().map(Into::into).collect();
188 let expected = json!({
189 "id": "my-device".to_string(),
190 "payload": json!({
191 "id": "my-device".to_string(),
192 "clientName": "my device",
193 "tabs": serde_json::to_value(expected_tabs).unwrap(),
194 }).to_string(),
195 "ttl": TABS_CLIENT_TTL,
196 });
197
198 assert_eq!(ours, expected);
199 bridge.set_uploaded(1234, vec![]).unwrap();
200 assert_eq!(bridge.last_sync().unwrap(), 1234);
201 }
202
203 #[test]
204 fn test_sync_meta() {
205 error_support::init_for_tests();
206
207 let store = Arc::new(TabsStore::new_with_mem_path("test-meta"));
208 let bridge = store.bridged_engine();
209
210 assert_eq!(bridge.last_sync().unwrap(), 0);
212 bridge.set_last_sync(3).unwrap();
213 assert_eq!(bridge.last_sync().unwrap(), 3);
214
215 assert!(bridge.sync_id().unwrap().is_none());
216
217 bridge.ensure_current_sync_id("some_guid").unwrap();
218 assert_eq!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
219 assert_eq!(bridge.last_sync().unwrap(), 0);
221 bridge.set_last_sync(3).unwrap();
222
223 bridge.reset_sync_id().unwrap();
224 assert_ne!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
226 assert_eq!(bridge.last_sync().unwrap(), 0);
228 bridge.set_last_sync(3).unwrap();
229
230 bridge.reset().unwrap();
232 assert_eq!(bridge.last_sync().unwrap(), 0);
233 assert!(bridge.sync_id().unwrap().is_none());
234 }
235}