1use crate::sync::engine::LoginsSyncEngine;
6use crate::LoginStore;
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 LoginStore {
15 pub fn bridged_engine(self: Arc<Self>) -> Result<Arc<LoginsBridgedEngine>> {
22 let engine = LoginsSyncEngine::new(self)?;
23 let bridged_engine = LoginsBridgedEngineAdaptor { engine };
24 Ok(Arc::new(LoginsBridgedEngine::new(Box::new(bridged_engine))))
25 }
26}
27
28struct LoginsBridgedEngineAdaptor {
37 engine: LoginsSyncEngine,
38}
39
40impl BridgedEngineAdaptor for LoginsBridgedEngineAdaptor {
42 fn last_sync(&self) -> Result<i64> {
43 let db = self.engine.store.lock_db()?;
47 Ok(self
48 .engine
49 .get_last_sync(&db)?
50 .unwrap_or_default()
51 .as_millis())
52 }
53
54 fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
55 let db = self.engine.store.lock_db()?;
56 self.engine
57 .set_last_sync(&db, ServerTimestamp::from_millis(last_sync_millis))?;
58 Ok(())
59 }
60
61 fn engine(&self) -> &dyn sync15::engine::SyncEngine {
62 &self.engine
63 }
64}
65
66pub struct LoginsBridgedEngine {
71 bridge_impl: Box<dyn BridgedEngine>,
72}
73
74impl LoginsBridgedEngine {
75 pub fn new(bridge_impl: Box<dyn BridgedEngine>) -> Self {
76 Self { bridge_impl }
77 }
78
79 pub fn last_sync(&self) -> Result<i64> {
80 self.bridge_impl.last_sync()
81 }
82
83 pub fn set_last_sync(&self, last_sync: i64) -> Result<()> {
84 self.bridge_impl.set_last_sync(last_sync)
85 }
86
87 pub fn sync_id(&self) -> Result<Option<String>> {
88 self.bridge_impl.sync_id()
89 }
90
91 pub fn reset_sync_id(&self) -> Result<String> {
92 self.bridge_impl.reset_sync_id()
93 }
94
95 pub fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String> {
96 self.bridge_impl.ensure_current_sync_id(sync_id)
97 }
98
99 pub fn sync_started(&self) -> Result<()> {
100 self.bridge_impl.sync_started()
101 }
102
103 fn convert_incoming_bsos(&self, incoming: Vec<String>) -> Result<Vec<IncomingBso>> {
105 let mut bsos = Vec::with_capacity(incoming.len());
106 for inc in incoming {
107 bsos.push(serde_json::from_str::<IncomingBso>(&inc)?);
108 }
109 Ok(bsos)
110 }
111
112 fn convert_outgoing_bsos(&self, outgoing: Vec<OutgoingBso>) -> Result<Vec<String>> {
114 let mut bsos = Vec::with_capacity(outgoing.len());
115 for e in outgoing {
116 bsos.push(serde_json::to_string(&e)?);
117 }
118 Ok(bsos)
119 }
120
121 pub fn store_incoming(&self, incoming: Vec<String>) -> Result<()> {
122 self.bridge_impl
123 .store_incoming(self.convert_incoming_bsos(incoming)?)
124 }
125
126 pub fn apply(&self) -> Result<Vec<String>> {
127 let apply_results = self.bridge_impl.apply()?;
128 self.convert_outgoing_bsos(apply_results.records)
129 }
130
131 pub fn set_uploaded(&self, server_modified_millis: i64, guids: Vec<String>) -> Result<()> {
132 let guids: Vec<SyncGuid> = guids.into_iter().map(SyncGuid::from).collect();
134 self.bridge_impl
135 .set_uploaded(server_modified_millis, &guids)
136 }
137
138 pub fn sync_finished(&self) -> Result<()> {
139 self.bridge_impl.sync_finished()
140 }
141
142 pub fn reset(&self) -> Result<()> {
143 self.bridge_impl.reset()
144 }
145
146 pub fn wipe(&self) -> Result<()> {
147 self.bridge_impl.wipe()
148 }
149}
150
151#[cfg(not(feature = "keydb"))]
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::db::test_utils::insert_login;
156 use nss_as::ensure_initialized;
157 use std::collections::HashMap;
158
159 #[test]
163 fn test_sync_meta() {
164 ensure_initialized();
165 error_support::init_for_tests();
166
167 let store = Arc::new(LoginStore::new_in_memory());
168 let bridge = store.bridged_engine().expect("should create bridge");
169
170 assert_eq!(bridge.last_sync().unwrap(), 0);
172 bridge.set_last_sync(3).unwrap();
173 assert_eq!(bridge.last_sync().unwrap(), 3);
174
175 assert!(bridge.sync_id().unwrap().is_none());
176
177 bridge.ensure_current_sync_id("some_guid").unwrap();
178 assert_eq!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
179 assert_eq!(bridge.last_sync().unwrap(), 0);
181 bridge.set_last_sync(3).unwrap();
182
183 bridge.reset_sync_id().unwrap();
184 assert_ne!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
186 assert_eq!(bridge.last_sync().unwrap(), 0);
188 bridge.set_last_sync(3).unwrap();
189
190 bridge.reset().unwrap();
192 assert_eq!(bridge.last_sync().unwrap(), 0);
193 assert!(bridge.sync_id().unwrap().is_none());
194 }
195
196 #[test]
202 fn test_sync_via_bridge() {
203 ensure_initialized();
204 error_support::init_for_tests();
205
206 let store = Arc::new(LoginStore::new_in_memory());
207
208 insert_login(
211 &store.lock_db().unwrap(),
212 "local-only-aaaa",
213 Some("local-password"),
214 None,
215 );
216
217 let bridge = store
218 .clone()
219 .bridged_engine()
220 .expect("should create bridge");
221
222 bridge.sync_started().unwrap();
223
224 let incoming = vec![serde_json::json!({
227 "id": "remote-only-bbbb",
228 "modified": 0,
229 "payload": serde_json::json!({
230 "id": "remote-only-bbbb",
231 "hostname": "https://remote.example.com",
232 "formSubmitURL": "https://remote.example.com",
233 "username": "remote-user",
234 "password": "remote-password",
235 })
236 .to_string(),
237 })
238 .to_string()];
239 bridge
240 .store_incoming(incoming)
241 .expect("should store incoming");
242
243 let outgoing = bridge.apply().expect("should apply");
246 let changes: HashMap<String, serde_json::Value> = outgoing
247 .into_iter()
248 .map(|s| {
249 let bso: serde_json::Value = serde_json::from_str(&s).unwrap();
250 let payload: serde_json::Value =
251 serde_json::from_str(bso["payload"].as_str().unwrap()).unwrap();
252 (payload["id"].as_str().unwrap().to_string(), payload)
253 })
254 .collect();
255
256 assert_eq!(changes.len(), 1);
259 assert_eq!(changes["local-only-aaaa"]["password"], "local-password");
260
261 let stored = store
263 .get("remote-only-bbbb")
264 .unwrap()
265 .expect("remote login should have been stored");
266 assert_eq!(stored.password, "remote-password");
267
268 bridge
270 .set_uploaded(1234, vec!["local-only-aaaa".to_string()])
271 .unwrap();
272 bridge.sync_finished().unwrap();
273 assert_eq!(bridge.last_sync().unwrap(), 1234);
274 }
275}