Fixed calendar metadata sync
This commit is contained in:
parent
f2dd528d33
commit
aa02fa182d
9 changed files with 92 additions and 68 deletions
52
src/cache.rs
52
src/cache.rs
|
@ -136,11 +136,11 @@ impl Cache {
|
||||||
/// Compares two Caches to check they have the same current content
|
/// Compares two Caches to check they have the same current content
|
||||||
///
|
///
|
||||||
/// This is not a complete equality test: some attributes (sync status...) may differ
|
/// This is not a complete equality test: some attributes (sync status...) may differ
|
||||||
pub async fn has_same_contents_than(&self, other: &Self) -> Result<bool, Box<dyn Error>> {
|
pub async fn has_same_observable_content_as(&self, other: &Self) -> Result<bool, Box<dyn Error>> {
|
||||||
let calendars_l = self.get_calendars().await?;
|
let calendars_l = self.get_calendars().await?;
|
||||||
let calendars_r = other.get_calendars().await?;
|
let calendars_r = other.get_calendars().await?;
|
||||||
|
|
||||||
if keys_are_the_same(&calendars_l, &calendars_r) == false {
|
if crate::utils::keys_are_the_same(&calendars_l, &calendars_r) == false {
|
||||||
log::debug!("Different keys for calendars");
|
log::debug!("Different keys for calendars");
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
@ -153,54 +153,18 @@ impl Cache {
|
||||||
None => return Err("should not happen, we've just tested keys are the same".into()),
|
None => return Err("should not happen, we've just tested keys are the same".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let items_l = cal_l.get_items().await?;
|
// TODO: check calendars have the same names/ID/whatever
|
||||||
let items_r = cal_r.get_items().await?;
|
if cal_l.has_same_observable_content_as(&cal_r).await? == false {
|
||||||
|
log::debug!("Different calendars");
|
||||||
|
return Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
if keys_are_the_same(&items_l, &items_r) == false {
|
|
||||||
log::debug!("Different keys for items");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
for (id_l, item_l) in items_l {
|
|
||||||
let item_r = match items_r.get(&id_l) {
|
|
||||||
Some(c) => c,
|
|
||||||
None => return Err("should not happen, we've just tested keys are the same".into()),
|
|
||||||
};
|
|
||||||
if item_l.has_same_observable_content(&item_r) == false {
|
|
||||||
log::debug!("Different items for id {}:", id_l);
|
|
||||||
log::debug!("{:#?}", item_l);
|
|
||||||
log::debug!("{:#?}", item_r);
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keys_are_the_same<T, U, V>(left: &HashMap<T, U>, right: &HashMap<T, V>) -> bool
|
|
||||||
where
|
|
||||||
T: Hash + Eq + Clone + std::fmt::Display,
|
|
||||||
{
|
|
||||||
if left.len() != right.len() {
|
|
||||||
log::debug!("Count of keys mismatch: {} and {}", left.len(), right.len());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys_l: HashSet<T> = left.keys().cloned().collect();
|
|
||||||
let keys_r: HashSet<T> = right.keys().cloned().collect();
|
|
||||||
let result = keys_l == keys_r;
|
|
||||||
if result == false {
|
|
||||||
log::debug!("Keys of a map mismatch");
|
|
||||||
for key in keys_l {
|
|
||||||
log::debug!(" left: {}", key);
|
|
||||||
}
|
|
||||||
log::debug!("RIGHT:");
|
|
||||||
for key in keys_r {
|
|
||||||
log::debug!(" right: {}", key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl CalDavSource<CachedCalendar> for Cache {
|
impl CalDavSource<CachedCalendar> for Cache {
|
||||||
|
@ -258,7 +222,7 @@ mod tests {
|
||||||
|
|
||||||
let retrieved_cache = Cache::from_folder(&cache_path).unwrap();
|
let retrieved_cache = Cache::from_folder(&cache_path).unwrap();
|
||||||
assert_eq!(cache.backing_folder, retrieved_cache.backing_folder);
|
assert_eq!(cache.backing_folder, retrieved_cache.backing_folder);
|
||||||
let test = cache.has_same_contents_than(&retrieved_cache).await;
|
let test = cache.has_same_observable_content_as(&retrieved_cache).await;
|
||||||
println!("Equal? {:?}", test);
|
println!("Equal? {:?}", test);
|
||||||
assert_eq!(test.unwrap(), true);
|
assert_eq!(test.unwrap(), true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,39 @@ impl CachedCalendar {
|
||||||
self.items.insert(item.id().clone(), item);
|
self.items.insert(item.id().clone(), item);
|
||||||
Ok(ss_clone)
|
Ok(ss_clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Some kind of equality check
|
||||||
|
pub async fn has_same_observable_content_as(&self, other: &CachedCalendar) -> Result<bool, Box<dyn Error>> {
|
||||||
|
if self.name != other.name
|
||||||
|
|| self.id != other.id
|
||||||
|
|| self.supported_components != other.supported_components {
|
||||||
|
log::debug!("Calendar properties mismatch");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let items_l = self.get_items().await?;
|
||||||
|
let items_r = other.get_items().await?;
|
||||||
|
|
||||||
|
if crate::utils::keys_are_the_same(&items_l, &items_r) == false {
|
||||||
|
log::debug!("Different keys for items");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
for (id_l, item_l) in items_l {
|
||||||
|
let item_r = match items_r.get(&id_l) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err("should not happen, we've just tested keys are the same".into()),
|
||||||
|
};
|
||||||
|
if item_l.has_same_observable_content_as(&item_r) == false {
|
||||||
|
log::debug!("Different items for id {}:", id_l);
|
||||||
|
log::debug!("{:#?}", item_l);
|
||||||
|
log::debug!("{:#?}", item_r);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl Event {
|
||||||
self.sync_status = new_status;
|
self.sync_status = new_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_same_observable_content(&self, _other: &Event) -> bool {
|
pub fn has_same_observable_content_as(&self, _other: &Event) -> bool {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,10 +80,10 @@ impl Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_same_observable_content(&self, other: &Item) -> bool {
|
pub fn has_same_observable_content_as(&self, other: &Item) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Item::Event(s), Item::Event(o)) => s.has_same_observable_content(o),
|
(Item::Event(s), Item::Event(o)) => s.has_same_observable_content_as(o),
|
||||||
(Item::Task(s), Item::Task(o)) => s.has_same_observable_content(o),
|
(Item::Task(s), Item::Task(o)) => s.has_same_observable_content_as(o),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ where
|
||||||
// Sync every remote calendar
|
// Sync every remote calendar
|
||||||
let cals_remote = self.remote.get_calendars().await?;
|
let cals_remote = self.remote.get_calendars().await?;
|
||||||
for (cal_id, cal_remote) in cals_remote {
|
for (cal_id, cal_remote) in cals_remote {
|
||||||
let cal_local = self.get_or_insert_local_counterpart_calendar(&cal_id).await;
|
let cal_local = self.get_or_insert_local_counterpart_calendar(&cal_id, cal_remote.clone()).await;
|
||||||
|
|
||||||
if let Err(err) = Self::sync_calendar_pair(cal_local, cal_remote).await {
|
if let Err(err) = Self::sync_calendar_pair(cal_local, cal_remote).await {
|
||||||
log::warn!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err);
|
log::warn!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err);
|
||||||
|
@ -78,7 +78,7 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cal_remote = self.get_or_insert_remote_counterpart_calendar(&cal_id).await;
|
let cal_remote = self.get_or_insert_remote_counterpart_calendar(&cal_id, cal_local.clone()).await;
|
||||||
|
|
||||||
if let Err(err) = Self::sync_calendar_pair(cal_local, cal_remote).await {
|
if let Err(err) = Self::sync_calendar_pair(cal_local, cal_remote).await {
|
||||||
log::warn!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err);
|
log::warn!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err);
|
||||||
|
@ -89,7 +89,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn get_or_insert_local_counterpart_calendar(&mut self, cal_id: &CalendarId) -> Arc<Mutex<T>> {
|
async fn get_or_insert_local_counterpart_calendar(&mut self, cal_id: &CalendarId, source_cal: Arc<Mutex<U>>) -> Arc<Mutex<T>> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(cal) = self.local.get_calendar(&cal_id).await {
|
if let Some(cal) = self.local.get_calendar(&cal_id).await {
|
||||||
break cal;
|
break cal;
|
||||||
|
@ -97,10 +97,13 @@ where
|
||||||
|
|
||||||
// This calendar does not exist locally yet, let's add it
|
// This calendar does not exist locally yet, let's add it
|
||||||
log::debug!("Adding a local calendar {}", cal_id);
|
log::debug!("Adding a local calendar {}", cal_id);
|
||||||
|
let src = source_cal.lock().unwrap();
|
||||||
|
let name = src.name().to_string();
|
||||||
|
let supported_comps = src.supported_components();
|
||||||
if let Err(err) = self.local.create_calendar(
|
if let Err(err) = self.local.create_calendar(
|
||||||
cal_id.clone(),
|
cal_id.clone(),
|
||||||
String::from("new calendar"),
|
name,
|
||||||
SupportedComponents::TODO,
|
supported_comps,
|
||||||
).await {
|
).await {
|
||||||
log::warn!("Unable to create local calendar {}: {}. Skipping it.", cal_id, err);
|
log::warn!("Unable to create local calendar {}: {}. Skipping it.", cal_id, err);
|
||||||
continue;
|
continue;
|
||||||
|
@ -108,7 +111,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_or_insert_remote_counterpart_calendar(&mut self, cal_id: &CalendarId) -> Arc<Mutex<U>> {
|
async fn get_or_insert_remote_counterpart_calendar(&mut self, cal_id: &CalendarId, source_cal: Arc<Mutex<T>>) -> Arc<Mutex<U>> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(cal) = self.remote.get_calendar(&cal_id).await {
|
if let Some(cal) = self.remote.get_calendar(&cal_id).await {
|
||||||
break cal;
|
break cal;
|
||||||
|
@ -116,10 +119,13 @@ where
|
||||||
|
|
||||||
// This calendar does not exist in the remote yet, let's add it
|
// This calendar does not exist in the remote yet, let's add it
|
||||||
log::debug!("Adding a remote calendar {}", cal_id);
|
log::debug!("Adding a remote calendar {}", cal_id);
|
||||||
|
let src = source_cal.lock().unwrap();
|
||||||
|
let name = src.name().to_string();
|
||||||
|
let supported_comps = src.supported_components();
|
||||||
if let Err(err) = self.remote.create_calendar(
|
if let Err(err) = self.remote.create_calendar(
|
||||||
cal_id.clone(),
|
cal_id.clone(),
|
||||||
String::from("new calendar"),
|
name,
|
||||||
SupportedComponents::TODO,
|
supported_comps,
|
||||||
).await {
|
).await {
|
||||||
log::warn!("Unable to create remote calendar {}: {}. Skipping it.", cal_id, err);
|
log::warn!("Unable to create remote calendar {}: {}. Skipping it.", cal_id, err);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl Task {
|
||||||
pub fn completed(&self) -> bool { self.completed }
|
pub fn completed(&self) -> bool { self.completed }
|
||||||
pub fn sync_status(&self) -> &SyncStatus { &self.sync_status }
|
pub fn sync_status(&self) -> &SyncStatus { &self.sync_status }
|
||||||
|
|
||||||
pub fn has_same_observable_content(&self, other: &Task) -> bool {
|
pub fn has_same_observable_content_as(&self, other: &Task) -> bool {
|
||||||
self.id == other.id
|
self.id == other.id
|
||||||
&& self.name == other.name
|
&& self.name == other.name
|
||||||
&& self.completed == other.completed
|
&& self.completed == other.completed
|
||||||
|
|
30
src/utils.rs
30
src/utils.rs
|
@ -1,7 +1,8 @@
|
||||||
///! Some utility functions
|
///! Some utility functions
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
use minidom::Element;
|
use minidom::Element;
|
||||||
|
|
||||||
|
@ -111,3 +112,30 @@ pub fn print_task(item: &Item) {
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Compare keys of two hashmaps for equality
|
||||||
|
pub fn keys_are_the_same<T, U, V>(left: &HashMap<T, U>, right: &HashMap<T, V>) -> bool
|
||||||
|
where
|
||||||
|
T: Hash + Eq + Clone + std::fmt::Display,
|
||||||
|
{
|
||||||
|
if left.len() != right.len() {
|
||||||
|
log::debug!("Count of keys mismatch: {} and {}", left.len(), right.len());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys_l: HashSet<T> = left.keys().cloned().collect();
|
||||||
|
let keys_r: HashSet<T> = right.keys().cloned().collect();
|
||||||
|
let result = keys_l == keys_r;
|
||||||
|
if result == false {
|
||||||
|
log::debug!("Keys of a map mismatch");
|
||||||
|
for key in keys_l {
|
||||||
|
log::debug!(" left: {}", key);
|
||||||
|
}
|
||||||
|
log::debug!("RIGHT:");
|
||||||
|
for key in keys_r {
|
||||||
|
log::debug!(" right: {}", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
|
@ -83,13 +83,6 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
let main_cal = CalendarId::from("https://some.calend.ar/main/".parse().unwrap());
|
let main_cal = CalendarId::from("https://some.calend.ar/main/".parse().unwrap());
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// TODO: add new calendars, with or without taks in them
|
|
||||||
//
|
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(),
|
id: ItemId::random(),
|
||||||
|
|
|
@ -57,15 +57,15 @@ impl TestFlavour {
|
||||||
print_provider(&provider, "after sync").await;
|
print_provider(&provider, "after sync").await;
|
||||||
|
|
||||||
// Check the contents of both sources are the same after sync
|
// Check the contents of both sources are the same after sync
|
||||||
assert!(provider.remote().has_same_contents_than(provider.local()).await.unwrap());
|
assert!(provider.remote().has_same_observable_content_as(provider.local()).await.unwrap());
|
||||||
|
|
||||||
// But also explicitely check that every item is expected
|
// But also explicitely check that every item is expected
|
||||||
let expected_provider = scenarii::populate_test_provider_after_sync(&self.scenarii).await;
|
let expected_provider = scenarii::populate_test_provider_after_sync(&self.scenarii).await;
|
||||||
println!("\n");
|
println!("\n");
|
||||||
print_provider(&expected_provider, "expected after sync").await;
|
print_provider(&expected_provider, "expected after sync").await;
|
||||||
|
|
||||||
assert!(provider.local() .has_same_contents_than(expected_provider.local() ).await.unwrap());
|
assert!(provider.local() .has_same_observable_content_as(expected_provider.local() ).await.unwrap());
|
||||||
assert!(provider.remote().has_same_contents_than(expected_provider.remote()).await.unwrap());
|
assert!(provider.remote().has_same_observable_content_as(expected_provider.remote()).await.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue