use std::sync::Arc;
use crate::{
property::Property,
sync::{
items::SideState, mapping::ResolvedMapping, mode::Mode, operation::StatusAction,
status::StatusVersions,
},
};
use super::{
operation::{
DeleteItem, ItemOp, MappingUidSource, PropertyOp, PropertyOpKind, StatusOnly, StatusWrite,
StorageWrite, WriteItem, resource_name,
},
ordering::{DeletionBarrier, DeletionCompletionHandle},
status::{ItemPairStatus, MappingUid, Side},
};
pub struct OneWaySync;
impl Mode for OneWaySync {
fn decide_item_action(
&self,
side_a: Option<SideState>,
side_b: Option<SideState>,
previous: Option<ItemPairStatus>,
mapping: &Arc<ResolvedMapping>,
mapping_uid_source: MappingUidSource,
on_complete: Option<&DeletionBarrier>,
) -> Option<ItemOp> {
let on_complete_handle = on_complete.map(DeletionBarrier::completion_handle);
match previous {
None => Some(Self::plan_new_item(
side_a,
side_b,
mapping,
mapping_uid_source,
on_complete_handle,
)),
Some(status) => {
Self::plan_existing_item(side_a, side_b, status, mapping, on_complete_handle)
}
}
}
fn decide_property_action(
&self,
property: Property,
value_a: Option<String>,
value_b: Option<String>,
previous: Option<String>,
mapping: &Arc<ResolvedMapping>,
mapping_uid: MappingUid,
on_complete: Option<&DeletionBarrier>,
) -> Option<PropertyOp> {
let on_complete = on_complete.map(DeletionBarrier::completion_handle);
let mapping = mapping.clone();
let mapping_uid = MappingUidSource::Immediate(mapping_uid);
let kind = match (value_a, value_b, previous) {
(None, None, None) => unreachable!("property must exist somewhere"),
(None, None, Some(_)) => PropertyOpKind::ClearStatus,
(Some(value_a), None, _) => PropertyOpKind::Write {
value: value_a,
side: Side::B,
},
(None, Some(_), _) => PropertyOpKind::Delete { side: Side::B },
(Some(value_a), Some(value_b), previous) => {
if value_a == value_b {
return match previous {
Some(prev) if prev == value_a => None, _ => Some(PropertyOp {
property,
mapping,
mapping_uid,
on_complete,
kind: PropertyOpKind::UpdateStatus { value: value_a },
}),
};
}
PropertyOpKind::Write {
value: value_a,
side: Side::B,
}
}
};
Some(PropertyOp {
property,
mapping,
mapping_uid,
on_complete,
kind,
})
}
}
impl OneWaySync {
fn plan_new_item(
side_a: Option<SideState>,
side_b: Option<SideState>,
mapping: &Arc<ResolvedMapping>,
mapping_uid_source: MappingUidSource,
on_complete: Option<DeletionCompletionHandle>,
) -> ItemOp {
match (side_a, side_b) {
(None, None) => unreachable!("item does not exist anywhere"),
(Some(a), None) => {
let name = resource_name(&a.state().version.href);
ItemOp::Write(WriteItem {
source: a.into(),
target_side: Side::B,
storage_write: StorageWrite::Create {
collection: mapping.b().href().clone(),
resource_name: Some(name),
},
status_write: StatusWrite::Insert,
mapping_uid: mapping_uid_source,
on_complete,
})
}
(None, Some(b)) => ItemOp::Delete(DeleteItem {
uid: b.state().uid.clone(),
target: b.into(),
side: Side::B,
mapping_uid: mapping_uid_source,
on_complete,
}),
(Some(a), Some(b)) => {
if a.state().hash == b.state().hash {
ItemOp::StatusOnly(StatusOnly {
action: StatusAction::Insert {
uid: a.state().uid.clone(),
hash: a.state().hash.clone(),
versions: StatusVersions {
a: a.into(),
b: b.into(),
},
},
mapping_uid: mapping_uid_source,
on_complete,
})
} else {
ItemOp::Write(WriteItem {
source: a.into(),
target_side: Side::B,
storage_write: StorageWrite::Update { target: b.into() },
status_write: StatusWrite::Insert,
mapping_uid: mapping_uid_source,
on_complete,
})
}
}
}
}
fn plan_existing_item(
side_a: Option<SideState>,
side_b: Option<SideState>,
status: ItemPairStatus,
mapping: &Arc<ResolvedMapping>,
on_complete: Option<DeletionCompletionHandle>,
) -> Option<ItemOp> {
let mapping_uid = MappingUidSource::Immediate(status.mapping_uid);
match (side_a, side_b) {
(None, None) => Some(ItemOp::StatusOnly(StatusOnly {
action: StatusAction::Clear { uid: status.uid },
mapping_uid,
on_complete,
})),
(Some(a), None) => {
let name = resource_name(&a.state().version.href);
Some(ItemOp::Write(WriteItem {
source: a.into(),
target_side: Side::B,
storage_write: StorageWrite::Create {
collection: mapping.b().href().clone(),
resource_name: Some(name),
},
status_write: StatusWrite::Update { old: status.into() },
mapping_uid,
on_complete,
}))
}
(None, Some(b)) => Some(ItemOp::Delete(DeleteItem {
uid: b.state().uid.clone(),
target: b.into(),
side: Side::B,
mapping_uid,
on_complete,
})),
(Some(a), Some(b)) => {
Self::plan_existing_on_both(a, b, status, mapping_uid, on_complete)
}
}
}
fn plan_existing_on_both(
a: SideState,
b: SideState,
status: ItemPairStatus,
mapping_uid: MappingUidSource,
on_complete: Option<DeletionCompletionHandle>,
) -> Option<ItemOp> {
match (&a, b) {
(SideState::Unchanged { .. }, SideState::Unchanged { state: b_state }) => {
if b_state.version == status.b {
None
} else {
let old: StatusVersions = status.into();
let target = old.b.clone();
Some(ItemOp::Write(WriteItem {
source: a.into(),
target_side: Side::B,
storage_write: StorageWrite::Update { target },
status_write: StatusWrite::Update { old },
mapping_uid,
on_complete,
}))
}
}
(
SideState::Changed { state: a_state, .. },
SideState::Changed { state: b_state, .. } | SideState::Unchanged { state: b_state },
) if a_state.hash == b_state.hash => Some(ItemOp::StatusOnly(StatusOnly {
action: StatusAction::Update {
hash: a_state.hash.clone(),
old: status.into(),
new: StatusVersions {
a: a_state.version.clone(),
b: b_state.version,
},
},
mapping_uid,
on_complete,
})),
(
SideState::Unchanged { .. } | SideState::Changed { .. },
SideState::Changed { state: b_state, .. },
) => Some(ItemOp::Write(WriteItem {
source: a.into(),
target_side: Side::B,
storage_write: StorageWrite::Update {
target: b_state.version,
},
status_write: StatusWrite::Update { old: status.into() },
mapping_uid,
on_complete,
})),
(SideState::Changed { .. }, SideState::Unchanged { .. }) => {
let old: StatusVersions = status.into();
let target = old.b.clone();
Some(ItemOp::Write(WriteItem {
source: a.into(),
target_side: Side::B,
storage_write: StorageWrite::Update { target },
status_write: StatusWrite::Update { old },
mapping_uid,
on_complete,
}))
}
}
}
}