pub mod incoming;
pub mod name_utils;
pub mod outgoing;
use super::engine::{ConfigSyncEngine, EngineConfig, SyncEngineStorageImpl};
use super::{
MergeResult, Metadata, ProcessIncomingRecordImpl, ProcessOutgoingRecordImpl, SyncRecord,
UnknownFields,
};
use crate::db::models::address::InternalAddress;
use crate::error::*;
use crate::sync_merge_field_check;
use incoming::IncomingAddressesImpl;
use name_utils::{split_name, NameParts};
use outgoing::OutgoingAddressesImpl;
use rusqlite::Transaction;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use sync_guid::Guid;
use types::Timestamp;
pub(crate) fn create_engine(store: Arc<crate::Store>) -> ConfigSyncEngine<InternalAddress> {
ConfigSyncEngine::new(
EngineConfig {
namespace: "addresses".to_string(),
collection: "addresses".into(),
},
store,
Box::new(AddressesEngineStorageImpl {}),
)
}
pub(super) struct AddressesEngineStorageImpl {}
impl SyncEngineStorageImpl<InternalAddress> for AddressesEngineStorageImpl {
fn get_incoming_impl(
&self,
enc_key: &Option<String>,
) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = InternalAddress>>> {
assert!(enc_key.is_none());
Ok(Box::new(IncomingAddressesImpl {}))
}
fn reset_storage(&self, tx: &Transaction<'_>) -> Result<()> {
tx.execute_batch(
"DELETE FROM addresses_mirror;
DELETE FROM addresses_tombstones;",
)?;
Ok(())
}
fn get_outgoing_impl(
&self,
enc_key: &Option<String>,
) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = InternalAddress>>> {
assert!(enc_key.is_none());
Ok(Box::new(OutgoingAddressesImpl {}))
}
}
#[derive(Default, Deserialize, Serialize)]
pub struct AddressPayload {
id: Guid,
entry: PayloadEntry,
}
#[derive(Default, Deserialize, Serialize)]
#[serde(default, rename_all = "kebab-case")]
struct PayloadEntry {
pub name: String,
pub given_name: String,
pub additional_name: String,
pub family_name: String,
pub organization: String,
pub street_address: String,
pub address_level3: String,
pub address_level2: String,
pub address_level1: String,
pub postal_code: String,
pub country: String,
pub tel: String,
pub email: String,
#[serde(rename = "timeCreated")]
pub time_created: Timestamp,
#[serde(rename = "timeLastUsed")]
pub time_last_used: Timestamp,
#[serde(rename = "timeLastModified")]
pub time_last_modified: Timestamp,
#[serde(rename = "timesUsed")]
pub times_used: i64,
pub version: u32, #[serde(flatten)]
unknown_fields: UnknownFields,
}
impl InternalAddress {
fn from_payload(p: AddressPayload) -> Result<Self> {
if p.entry.version != 1 {
return Err(Error::InvalidSyncPayload(format!(
"invalid version - {}",
p.entry.version
)));
}
Ok(InternalAddress {
guid: p.id,
name: p.entry.name,
organization: p.entry.organization,
street_address: p.entry.street_address,
address_level3: p.entry.address_level3,
address_level2: p.entry.address_level2,
address_level1: p.entry.address_level1,
postal_code: p.entry.postal_code,
country: p.entry.country,
tel: p.entry.tel,
email: p.entry.email,
metadata: Metadata {
time_created: p.entry.time_created,
time_last_used: p.entry.time_last_used,
time_last_modified: p.entry.time_last_modified,
times_used: p.entry.times_used,
sync_change_counter: 0,
},
})
}
fn into_payload(self) -> Result<AddressPayload> {
let NameParts {
given,
middle,
family,
} = split_name(&self.name);
Ok(AddressPayload {
id: self.guid,
entry: PayloadEntry {
name: self.name,
given_name: given,
additional_name: middle,
family_name: family,
organization: self.organization,
street_address: self.street_address,
address_level3: self.address_level3,
address_level2: self.address_level2,
address_level1: self.address_level1,
postal_code: self.postal_code,
country: self.country,
tel: self.tel,
email: self.email,
time_created: self.metadata.time_created,
time_last_used: self.metadata.time_last_used,
time_last_modified: self.metadata.time_last_modified,
times_used: self.metadata.times_used,
unknown_fields: Default::default(),
version: 1,
},
})
}
}
impl SyncRecord for InternalAddress {
fn record_name() -> &'static str {
"Address"
}
fn id(&self) -> &Guid {
&self.guid
}
fn metadata(&self) -> &Metadata {
&self.metadata
}
fn metadata_mut(&mut self) -> &mut Metadata {
&mut self.metadata
}
#[allow(clippy::cognitive_complexity)] fn merge(incoming: &Self, local: &Self, mirror: &Option<Self>) -> MergeResult<Self> {
let mut merged_record: Self = Default::default();
assert_eq!(incoming.guid, local.guid);
if let Some(m) = mirror {
assert_eq!(incoming.guid, m.guid)
};
merged_record.guid = incoming.guid.clone();
sync_merge_field_check!(name, incoming, local, mirror, merged_record);
sync_merge_field_check!(organization, incoming, local, mirror, merged_record);
sync_merge_field_check!(street_address, incoming, local, mirror, merged_record);
sync_merge_field_check!(address_level3, incoming, local, mirror, merged_record);
sync_merge_field_check!(address_level2, incoming, local, mirror, merged_record);
sync_merge_field_check!(address_level1, incoming, local, mirror, merged_record);
sync_merge_field_check!(postal_code, incoming, local, mirror, merged_record);
sync_merge_field_check!(country, incoming, local, mirror, merged_record);
sync_merge_field_check!(tel, incoming, local, mirror, merged_record);
sync_merge_field_check!(email, incoming, local, mirror, merged_record);
merged_record.metadata = incoming.metadata;
merged_record
.metadata
.merge(&local.metadata, mirror.as_ref().map(|m| m.metadata()));
MergeResult::Merged {
merged: merged_record,
}
}
}
fn get_forked_record(local_record: InternalAddress) -> InternalAddress {
let mut local_record_data = local_record;
local_record_data.guid = Guid::random();
local_record_data.metadata.time_created = Timestamp::now();
local_record_data.metadata.time_last_used = Timestamp::now();
local_record_data.metadata.time_last_modified = Timestamp::now();
local_record_data.metadata.times_used = 0;
local_record_data.metadata.sync_change_counter = 1;
local_record_data
}