M vdirsyncer/src/config.rs => vdirsyncer/src/config.rs +22 -3
@@ 26,7 26,7 @@ use vstorage::{
base::{IcsItem, Item, Storage, VcardItem},
caldav::CalDavStorage,
carddav::CardDavStorage,
- sync::declare::{CollectionDescription, DeclaredMapping, StoragePair},
+ sync::declare::{CollectionDescription, ConflictAction, DeclaredMapping, StoragePair},
vdir::VdirStorage,
webcal::WebCalStorage,
CollectionId,
@@ 165,10 165,21 @@ struct PairSection {
collections: Collections,
#[allow(dead_code)]
metadata: Option<Vec<String>>,
- conflict_resolution: Option<VecDeque<String>>,
+ conflict_resolution: Option<ConflictResolution>,
// TODO: partial_sync
}
+#[derive(Deserialize, Debug)]
+#[serde(deny_unknown_fields)]
+pub(crate) enum ConflictResolution {
+ #[serde(rename = "a wins")]
+ AWins,
+ #[serde(rename = "b wins")]
+ BWins,
+ #[serde(untagged)]
+ Cmd(VecDeque<String>),
+}
+
impl PairSection {
fn try_into_named_pair<I: Item>(
self,
@@ 205,7 216,7 @@ impl PairSection {
}
let conflict_resolution = match self.conflict_resolution {
- Some(args) => {
+ Some(ConflictResolution::Cmd(args)) => {
let mut args: VecDeque<_> = args.into_iter().map(OsString::from).collect();
Some(RawCommand {
command: args
@@ 214,6 225,14 @@ impl PairSection {
args: args.into(),
})
}
+ Some(ConflictResolution::AWins) => {
+ pair = pair.with_conflict_action(ConflictAction::KeepA);
+ None
+ }
+ Some(ConflictResolution::BWins) => {
+ pair = pair.with_conflict_action(ConflictAction::KeepB);
+ None
+ }
None => None,
};
M vstorage/src/sync/declare.rs => vstorage/src/sync/declare.rs +16 -0
@@ 72,6 72,7 @@ pub struct StoragePair<I: Item> {
pub(super) mappings: Vec<DeclaredMapping>,
pub(super) all_from_a: bool,
pub(super) all_from_b: bool,
+ pub(super) conflict_action: ConflictAction,
}
impl<I: Item> StoragePair<I> {
@@ 87,6 88,7 @@ impl<I: Item> StoragePair<I> {
mappings: Vec::new(),
all_from_a: false,
all_from_b: false,
+ conflict_action: ConflictAction::Skip,
}
}
@@ 115,6 117,13 @@ impl<I: Item> StoragePair<I> {
self
}
+ /// In case of conflict, apply the supplied action.
+ #[must_use]
+ pub fn with_conflict_action(mut self, action: ConflictAction) -> Self {
+ self.conflict_action = action;
+ self
+ }
+
/// Returns a reference to storage a.
#[must_use]
pub fn storage_a(&self) -> &dyn Storage<I> {
@@ 127,3 136,10 @@ impl<I: Item> StoragePair<I> {
self.storage_b.as_ref()
}
}
+
+/// Action to automatically apply in case of conflict.
+pub enum ConflictAction {
+ Skip,
+ KeepA,
+ KeepB,
+}
M vstorage/src/sync/plan.rs => vstorage/src/sync/plan.rs +20 -2
@@ 16,7 16,7 @@ use crate::disco::{DiscoveredCollection, Discovery};
use crate::{base::Item, sync::declare::StoragePair};
use crate::{CollectionId, ErrorKind, Etag, Href};
-use super::declare::{CollectionDescription, DeclaredMapping};
+use super::declare::{CollectionDescription, ConflictAction, DeclaredMapping};
use super::status::{ItemState, MappingUid, Side, StatusDatabase, StatusError, StatusForItem};
/// Error that occurs when creating a [`Plan`].
@@ 520,7 520,13 @@ impl CollectionPlan {
_ => None,
};
- Ok(ItemAction::for_item(item_a, item_b, previous, uid))
+ Ok(ItemAction::for_item(
+ item_a,
+ item_b,
+ previous,
+ uid,
+ &pair.conflict_action,
+ ))
})
.filter_map(Result::transpose)
.collect::<Result<Vec<_>, PlanError>>()?;
@@ 604,6 610,7 @@ impl ItemAction {
current_b: Option<&ItemState>,
previous: Option<StatusForItem>,
uid: &str,
+ on_conflict: &ConflictAction,
) -> Option<ItemAction> {
match (current_a, current_b, previous) {
(None, None, None) => unreachable!("no action for item that doesn't exist anywhere"),
@@ 695,6 702,17 @@ impl ItemAction {
}
}
}
+
+ #[must_use]
+ fn for_conflicting() -> ItemAction {
+ // TODO: When I overwrite a conflicting item in B, I also need to udpate the state in A.
+ // This is a unique, new action...???
+ ItemAction::Conflict {
+ a: a.clone(),
+ b: b.clone(),
+ is_new: true,
+ }
+ }
}
impl std::fmt::Display for ItemAction {