use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use crate::{
ErrorKind,
property::Property,
sync::{
analysis::{PropertyChange, ResolvedMapping},
declare::StoragePair,
mode::Mode,
operation::PropertyOp,
ordering::DeletionBarrier,
plan::PlanError,
status::{MappingUid, StatusDatabase},
},
};
pub(crate) fn detect_property_change(
property: Property,
value_a: Option<String>,
value_b: Option<String>,
previous: Option<String>,
) -> Option<PropertyChange> {
match (value_a, value_b, previous) {
(None, None, None) => None,
(None, None, Some(_)) => Some(PropertyChange::OnNeither { property }),
(None, Some(value_b), previous) => Some(PropertyChange::OnlyB {
property,
value_b,
previous,
}),
(Some(value_a), None, previous) => Some(PropertyChange::OnlyA {
property,
value_a,
previous,
}),
(Some(value_a), Some(value_b), previous) => Some(PropertyChange::OnBoth {
property,
value_a,
value_b,
previous,
}),
}
}
pub(crate) async fn load_and_create_property_operations(
mapping: &Arc<ResolvedMapping>,
mapping_uid: Option<MappingUid>,
pair: &StoragePair,
status: Option<&StatusDatabase>,
deletion_completion: Option<&DeletionBarrier>,
mode: &dyn Mode,
) -> Result<Vec<PropertyOp>, PlanError> {
let (props_a, props_b) = match tokio::try_join!(
pair.storage_a().list_properties(mapping.a().href()),
pair.storage_b().list_properties(mapping.b().href()),
) {
Ok((a, b)) => (a, b),
Err(error) => {
return if let ErrorKind::DoesNotExist | ErrorKind::Unsupported = error.kind {
Ok(Vec::new())
} else {
Err(error.into())
};
}
};
let props_status = match (status, mapping_uid) {
(Some(s), Some(u)) => s.list_properties_for_collection(u)?,
_ => Vec::with_capacity(0),
};
let Some(mapping_uid) = mapping_uid else {
return Ok(Vec::new());
};
let mut values_a: HashMap<Property, String> =
props_a.into_iter().map(|p| (p.property, p.value)).collect();
let mut values_b: HashMap<Property, String> =
props_b.into_iter().map(|p| (p.property, p.value)).collect();
let mut previous_values: HashMap<String, String> = props_status
.into_iter()
.map(|p| (p.property, p.value))
.collect();
let all_props: HashSet<Property> = values_a.keys().chain(values_b.keys()).copied().collect();
let operations: Vec<PropertyOp> = all_props
.into_iter()
.filter_map(|property: Property| {
let from_a = values_a.remove(&property);
let from_b = values_b.remove(&property);
let from_status = previous_values.remove(property.name());
detect_property_change(property, from_a, from_b, from_status)
})
.filter_map(|change| {
mode.decide_property_action(change, mapping, mapping_uid, deletion_completion)
})
.collect();
Ok(operations)
}