1use super::AddressPayload;
7use crate::db::addresses::{add_internal_address, update_internal_address};
8use crate::db::models::address::InternalAddress;
9use crate::db::schema::ADDRESS_COMMON_COLS;
10use crate::db::CounterUpdate;
11use crate::error::*;
12use crate::sync::address::name_utils::{join_name_parts, split_name, NameParts};
13use crate::sync::common::*;
14use crate::sync::{
15 IncomingBso, IncomingContent, IncomingEnvelope, IncomingKind, IncomingState, LocalRecordInfo,
16 ProcessIncomingRecordImpl, ServerTimestamp, SyncRecord,
17};
18use interrupt_support::Interruptee;
19use rusqlite::{named_params, Transaction};
20use sql_support::ConnExt;
21use sync_guid::Guid as SyncGuid;
22
23fn update_name(payload_content: &mut IncomingContent<AddressPayload>, local_name: String) {
40 let internal_address =
42 if let IncomingKind::Content(internal_address) = &mut payload_content.kind {
43 internal_address
44 } else {
45 return;
46 };
47
48 let entry = &mut internal_address.entry;
49
50 if !entry.name.is_empty()
52 || (entry.given_name.is_empty()
53 && entry.additional_name.is_empty()
54 && entry.family_name.is_empty())
55 {
56 return;
57 }
58
59 let NameParts {
61 given,
62 middle,
63 family,
64 } = split_name(&local_name);
65
66 let is_local_name_matching =
68 entry.given_name == given && entry.additional_name == middle && entry.family_name == family;
69
70 entry.name = if is_local_name_matching {
72 local_name
73 } else {
74 join_name_parts(&NameParts {
75 given: entry.given_name.clone(),
76 middle: entry.additional_name.clone(),
77 family: entry.family_name.clone(),
78 })
79 };
80}
81
82fn create_incoming_bso(id: SyncGuid, raw: String) -> IncomingContent<AddressPayload> {
83 let bso = IncomingBso {
84 envelope: IncomingEnvelope {
85 id,
86 modified: ServerTimestamp::default(),
87 sortindex: None,
88 ttl: None,
89 },
90 payload: raw,
91 };
92 bso.into_content::<AddressPayload>()
93}
94
95fn bso_to_incoming(
96 payload_content: IncomingContent<AddressPayload>,
97) -> Result<IncomingContent<InternalAddress>> {
98 Ok(match payload_content.kind {
99 IncomingKind::Content(content) => IncomingContent {
100 envelope: payload_content.envelope,
101 kind: IncomingKind::Content(InternalAddress::from_payload(content)?),
102 },
103 IncomingKind::Tombstone => IncomingContent {
104 envelope: payload_content.envelope,
105 kind: IncomingKind::Tombstone,
106 },
107 IncomingKind::Malformed => IncomingContent {
108 envelope: payload_content.envelope,
109 kind: IncomingKind::Malformed,
110 },
111 })
112}
113
114fn raw_payload_to_incoming(id: SyncGuid, raw: String) -> Result<IncomingContent<InternalAddress>> {
117 let payload_content = create_incoming_bso(id, raw);
118
119 Ok(match payload_content.kind {
120 IncomingKind::Content(content) => IncomingContent {
121 envelope: payload_content.envelope,
122 kind: IncomingKind::Content(InternalAddress::from_payload(content)?),
123 },
124 IncomingKind::Tombstone => IncomingContent {
125 envelope: payload_content.envelope,
126 kind: IncomingKind::Tombstone,
127 },
128 IncomingKind::Malformed => IncomingContent {
129 envelope: payload_content.envelope,
130 kind: IncomingKind::Malformed,
131 },
132 })
133}
134
135pub(super) struct IncomingAddressesImpl {}
136
137impl ProcessIncomingRecordImpl for IncomingAddressesImpl {
138 type Record = InternalAddress;
139
140 fn stage_incoming(
142 &self,
143 tx: &Transaction<'_>,
144 incoming: Vec<IncomingBso>,
145 signal: &dyn Interruptee,
146 ) -> Result<()> {
147 let to_stage = incoming
148 .into_iter()
149 .map(|bso| (bso.envelope.id, bso.payload, bso.envelope.modified))
151 .collect();
152 common_stage_incoming_records(tx, "addresses_sync_staging", to_stage, signal)
153 }
154
155 fn finish_incoming(&self, tx: &Transaction<'_>) -> Result<()> {
156 common_mirror_staged_records(tx, "addresses_sync_staging", "addresses_mirror")
157 }
158
159 fn fetch_incoming_states(
163 &self,
164 tx: &Transaction<'_>,
165 ) -> Result<Vec<IncomingState<Self::Record>>> {
166 let sql = "
167 SELECT
168 s.guid as guid,
169 l.guid as l_guid,
170 t.guid as t_guid,
171 s.payload as s_payload,
172 m.payload as m_payload,
173 l.name,
174 l.organization,
175 l.street_address,
176 l.address_level3,
177 l.address_level2,
178 l.address_level1,
179 l.postal_code,
180 l.country,
181 l.tel,
182 l.email,
183 l.time_created,
184 l.time_last_used,
185 l.time_last_modified,
186 l.times_used,
187 l.sync_change_counter
188 FROM temp.addresses_sync_staging s
189 LEFT JOIN addresses_mirror m ON s.guid = m.guid
190 LEFT JOIN addresses_data l ON s.guid = l.guid
191 LEFT JOIN addresses_tombstones t ON s.guid = t.guid";
192
193 tx.query_rows_and_then(sql, [], |row| -> Result<IncomingState<Self::Record>> {
194 let guid: SyncGuid = row.get("guid")?;
196
197 let mut payload_content = create_incoming_bso(guid.clone(), row.get("s_payload")?);
203 update_name(
204 &mut payload_content,
205 row.get("name").unwrap_or("".to_string()),
206 );
207 let incoming = bso_to_incoming(payload_content)?;
208
209 Ok(IncomingState {
210 incoming,
211 local: match row.get_unwrap::<_, Option<String>>("l_guid") {
212 Some(l_guid) => {
213 assert_eq!(l_guid, guid);
214 let record = InternalAddress::from_row(row)?;
216 let has_changes = record.metadata().sync_change_counter != 0;
217 if has_changes {
218 LocalRecordInfo::Modified { record }
219 } else {
220 LocalRecordInfo::Unmodified { record }
221 }
222 }
223 None => {
224 match row.get::<_, Option<String>>("t_guid")? {
226 Some(t_guid) => {
227 assert_eq!(guid, t_guid);
228 LocalRecordInfo::Tombstone { guid: guid.clone() }
229 }
230 None => LocalRecordInfo::Missing,
231 }
232 }
233 },
234 mirror: {
235 match row.get::<_, Option<String>>("m_payload")? {
236 Some(m_payload) => {
237 raw_payload_to_incoming(guid, m_payload)?.content()
239 }
240 None => None,
241 }
242 },
243 })
244 })
245 }
246
247 fn get_local_dupe(
251 &self,
252 tx: &Transaction<'_>,
253 incoming: &Self::Record,
254 ) -> Result<Option<Self::Record>> {
255 let sql = format!("
256 SELECT
257 {common_cols},
258 sync_change_counter
259 FROM addresses_data
260 WHERE
261 -- `guid <> :guid` is a pre-condition for this being called, but...
262 guid <> :guid
263 -- only non-synced records are candidates, which means can't already be in the mirror.
264 AND guid NOT IN (
265 SELECT guid
266 FROM addresses_mirror
267 )
268 -- and sql can check the field values.
269 AND name == :name
270 AND organization == :organization
271 AND street_address == :street_address
272 AND address_level3 == :address_level3
273 AND address_level2 == :address_level2
274 AND address_level1 == :address_level1
275 AND postal_code == :postal_code
276 AND country == :country
277 AND tel == :tel
278 AND email == :email", common_cols = ADDRESS_COMMON_COLS);
279
280 let params = named_params! {
281 ":guid": incoming.guid,
282 ":name": incoming.name,
283 ":organization": incoming.organization,
284 ":street_address": incoming.street_address,
285 ":address_level3": incoming.address_level3,
286 ":address_level2": incoming.address_level2,
287 ":address_level1": incoming.address_level1,
288 ":postal_code": incoming.postal_code,
289 ":country": incoming.country,
290 ":tel": incoming.tel,
291 ":email": incoming.email,
292 };
293
294 let result = tx.query_row(&sql, params, |row| {
295 Ok(Self::Record::from_row(row).expect("wtf? '?' doesn't work :("))
296 });
297
298 match result {
299 Ok(r) => Ok(Some(r)),
300 Err(e) => match e {
301 rusqlite::Error::QueryReturnedNoRows => Ok(None),
302 _ => Err(Error::SqlError(e)),
303 },
304 }
305 }
306
307 fn update_local_record(
308 &self,
309 tx: &Transaction<'_>,
310 new_record: Self::Record,
311 flag_as_changed: bool,
312 ) -> Result<()> {
313 update_internal_address(
314 tx,
315 &new_record,
316 if flag_as_changed {
317 CounterUpdate::Increment
318 } else {
319 CounterUpdate::Leave
320 },
321 )?;
322 Ok(())
323 }
324
325 fn insert_local_record(&self, tx: &Transaction<'_>, new_record: Self::Record) -> Result<()> {
326 add_internal_address(tx, &new_record)?;
327 Ok(())
328 }
329
330 fn change_record_guid(
334 &self,
335 tx: &Transaction<'_>,
336 old_guid: &SyncGuid,
337 new_guid: &SyncGuid,
338 ) -> Result<()> {
339 common_change_guid(tx, "addresses_data", "addresses_mirror", old_guid, new_guid)
340 }
341
342 fn remove_record(&self, tx: &Transaction<'_>, guid: &SyncGuid) -> Result<()> {
343 common_remove_record(tx, "addresses_data", guid)
344 }
345
346 fn remove_tombstone(&self, tx: &Transaction<'_>, guid: &SyncGuid) -> Result<()> {
347 common_remove_record(tx, "addresses_tombstones", guid)
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::super::super::test::new_syncable_mem_db;
354 use super::*;
355 use crate::db::addresses::get_address;
356 use crate::sync::common::tests::*;
357
358 use error_support::info;
359 use interrupt_support::NeverInterrupts;
360 use serde_json::{json, Map, Value};
361 use sql_support::ConnExt;
362
363 impl InternalAddress {
364 fn into_test_incoming_bso(self) -> IncomingBso {
365 IncomingBso::from_test_content(self.into_payload().expect("is json"))
366 }
367 }
368
369 lazy_static::lazy_static! {
370 static ref TEST_JSON_RECORDS: Map<String, Value> = {
371 let val = json! {{
376 "A" : {
377 "id": expand_test_guid('A'),
378 "entry": {
379 "name": "john doe",
380 "given-name": "john",
381 "family-name": "doe",
382 "street-address": "1300 Broadway",
383 "address-level2": "New York, NY",
384 "country": "United States",
385 "version": 1,
386 }
387 },
388 "C" : {
389 "id": expand_test_guid('C'),
390 "entry": {
391 "name": "jane doe",
392 "given-name": "jane",
393 "family-name": "doe",
394 "street-address": "3050 South La Brea Ave",
395 "address-level2": "Los Angeles, CA",
396 "country": "United States",
397 "timeCreated": 0,
398 "timeLastUsed": 0,
399 "timeLastModified": 0,
400 "timesUsed": 0,
401 "version": 1,
402 }
403 },
404 "D" : {
405 "id": expand_test_guid('D'),
406 "entry": {
407 "name": "test1 test2",
408 "given-name": "test1",
409 "family-name": "test2",
410 "street-address": "85 Pike St",
411 "address-level2": "Seattle, WA",
412 "country": "United States",
413 "foo": "bar",
414 "baz": "qux",
415 "version": 1,
416 }
417 }
418 }};
419 val.as_object().expect("literal is an object").clone()
420 };
421 }
422
423 fn test_json_record(guid_prefix: char) -> Value {
424 TEST_JSON_RECORDS
425 .get(&guid_prefix.to_string())
426 .expect("should exist")
427 .clone()
428 }
429
430 fn test_record(guid_prefix: char) -> InternalAddress {
431 let json = test_json_record(guid_prefix);
432 let address_payload = serde_json::from_value(json).unwrap();
433 InternalAddress::from_payload(address_payload).expect("should be valid")
434 }
435
436 #[test]
437 fn test_stage_incoming() -> Result<()> {
438 error_support::init_for_tests();
439 let mut db = new_syncable_mem_db();
440 struct TestCase {
441 incoming_records: Vec<Value>,
442 mirror_records: Vec<Value>,
443 expected_record_count: usize,
444 expected_tombstone_count: usize,
445 }
446
447 let test_cases = vec![
448 TestCase {
449 incoming_records: vec![test_json_record('A')],
450 mirror_records: vec![],
451 expected_record_count: 1,
452 expected_tombstone_count: 0,
453 },
454 TestCase {
455 incoming_records: vec![test_json_tombstone('A')],
456 mirror_records: vec![],
457 expected_record_count: 0,
458 expected_tombstone_count: 1,
459 },
460 TestCase {
461 incoming_records: vec![
462 test_json_record('A'),
463 test_json_record('C'),
464 test_json_tombstone('B'),
465 ],
466 mirror_records: vec![],
467 expected_record_count: 2,
468 expected_tombstone_count: 1,
469 },
470 TestCase {
472 incoming_records: vec![test_json_tombstone('B')],
473 mirror_records: vec![test_json_tombstone('B')],
474 expected_record_count: 0,
475 expected_tombstone_count: 1,
476 },
477 ];
478
479 for tc in test_cases {
480 info!("starting new testcase");
481 let tx = db.transaction()?;
482
483 let mirror_sql = "INSERT OR REPLACE INTO addresses_mirror (guid, payload)
485 VALUES (:guid, :payload)";
486 for payload in tc.mirror_records {
487 tx.execute(
488 mirror_sql,
489 rusqlite::named_params! {
490 ":guid": payload["id"].as_str().unwrap(),
491 ":payload": payload.to_string(),
492 },
493 )
494 .expect("should insert mirror record");
495 }
496
497 let ri = IncomingAddressesImpl {};
498 ri.stage_incoming(
499 &tx,
500 array_to_incoming(tc.incoming_records),
501 &NeverInterrupts,
502 )?;
503
504 let records = tx.conn().query_rows_and_then(
505 "SELECT * FROM temp.addresses_sync_staging;",
506 [],
507 |row| -> Result<IncomingContent<InternalAddress>> {
508 let guid: SyncGuid = row.get_unwrap("guid");
509 let payload: String = row.get_unwrap("payload");
510 raw_payload_to_incoming(guid, payload)
511 },
512 )?;
513
514 let record_count = records
515 .iter()
516 .filter(|p| !matches!(p.kind, IncomingKind::Tombstone))
517 .count();
518 let tombstone_count = records.len() - record_count;
519
520 assert_eq!(record_count, tc.expected_record_count);
521 assert_eq!(tombstone_count, tc.expected_tombstone_count);
522
523 ri.fetch_incoming_states(&tx)?;
524
525 tx.execute("DELETE FROM temp.addresses_sync_staging;", [])?;
526 }
527 Ok(())
528 }
529
530 #[test]
531 fn test_change_record_guid() -> Result<()> {
532 let mut db = new_syncable_mem_db();
533 let tx = db.transaction()?;
534 let ri = IncomingAddressesImpl {};
535
536 ri.insert_local_record(&tx, test_record('C'))?;
537
538 ri.change_record_guid(
539 &tx,
540 &SyncGuid::new(&expand_test_guid('C')),
541 &SyncGuid::new(&expand_test_guid('B')),
542 )?;
543 tx.commit()?;
544 assert!(get_address(&db.writer, &expand_test_guid('C').into()).is_err());
545 assert!(get_address(&db.writer, &expand_test_guid('B').into()).is_ok());
546 Ok(())
547 }
548
549 #[test]
550 fn test_get_incoming() {
551 let mut db = new_syncable_mem_db();
552 let tx = db.transaction().expect("should get tx");
553 let ai = IncomingAddressesImpl {};
554 let record = test_record('C');
555 let bso = record.clone().into_test_incoming_bso();
556 do_test_incoming_same(&ai, &tx, record, bso);
557 }
558
559 #[test]
560 fn test_get_incoming_unknown_fields() {
561 let json = test_json_record('D');
562 let address_payload = serde_json::from_value::<AddressPayload>(json).unwrap();
563 assert_eq!(address_payload.entry.unknown_fields.len(), 2);
565 assert_eq!(
566 address_payload
567 .entry
568 .unknown_fields
569 .get("foo")
570 .unwrap()
571 .as_str()
572 .unwrap(),
573 "bar"
574 );
575 }
576
577 #[test]
578 fn test_incoming_tombstone() {
579 let mut db = new_syncable_mem_db();
580 let tx = db.transaction().expect("should get tx");
581 let ai = IncomingAddressesImpl {};
582 do_test_incoming_tombstone(&ai, &tx, test_record('C'));
583 }
584
585 #[test]
586 fn test_staged_to_mirror() {
587 let mut db = new_syncable_mem_db();
588 let tx = db.transaction().expect("should get tx");
589 let ai = IncomingAddressesImpl {};
590 let record = test_record('C');
591 let bso = record.clone().into_test_incoming_bso();
592 do_test_staged_to_mirror(&ai, &tx, record, bso, "addresses_mirror");
593 }
594}