From 91f62aa2001155678d42245fb27808e9fa1ac5fe Mon Sep 17 00:00:00 2001 From: daladim Date: Sun, 28 Feb 2021 00:19:00 +0100 Subject: [PATCH] [tests] Conflicts handling --- src/provider.rs | 210 ++++++++++++++++++++++-------------------------- tests/sync.rs | 46 ++++++++--- 2 files changed, 132 insertions(+), 124 deletions(-) diff --git a/src/provider.rs b/src/provider.rs index 935bb27..80f6567 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,114 +1,96 @@ -//! This modules abstracts data sources and merges them in a single virtual one - -use std::error::Error; - -use chrono::{DateTime, Utc}; - -use crate::traits::CalDavSource; -use crate::Calendar; -use crate::Task; -use crate::task::TaskId; - - -pub struct Provider -where - S: CalDavSource, - L: CalDavSource, -{ - /// The remote server - server: S, - /// The local cache - local: L, - - /// The last time the provider successfully synchronized both sources - last_sync: DateTime, -} - -impl Provider -where - S: CalDavSource, - L: CalDavSource, -{ - /// Create a provider that will merge both sources - pub fn new(server: S, local: L, last_sync: DateTime) -> Self { - Self { server, local, last_sync } - } - - pub fn server(&self) -> &S { &self.server } - pub fn local(&self) -> &L { &self.local } - - pub async fn sync(&mut self) -> Result<(), Box> { - self.pull_items_from_server().await?; - self.resolve_conflicts().await; - self.push_items_to_server().await; - - // what to do with errors? Return Err directly? Go ahead as far as we can? - Ok(()) - } - - pub async fn pull_items_from_server(&mut self) -> Result<(), Box> { - let cals_server = self.server.get_calendars_mut().await?; - - for cal_server in cals_server { - let cal_local = match self.local.get_calendar_mut(cal_server.url().clone()).await { - None => { - log::error!("TODO: implement here"); - continue; - }, - Some(cal) => cal, - }; - - let server_mod = cal_server.get_tasks_modified_since(Some(self.last_sync), None); - let local_mod = cal_local.get_tasks_modified_since(Some(self.last_sync), None); - - let mut tasks_to_add_to_local = Vec::new(); - let mut tasks_id_to_remove_from_local = Vec::new(); - for (new_id, new_item) in &server_mod { - if server_mod.contains_key(new_id) { - log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), new_id); - tasks_id_to_remove_from_local.push(new_id.clone()); - } - tasks_to_add_to_local.push((*new_item).clone()); - } - - let mut tasks_to_add_to_server = Vec::new(); - for (new_id, new_item) in &local_mod { - if server_mod.contains_key(new_id) { - log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), new_id); - continue; - } - tasks_to_add_to_server.push((*new_item).clone()); - } - - remove_from_calendar(&tasks_id_to_remove_from_local, cal_local); - move_to_calendar(&mut tasks_to_add_to_local, cal_local); - move_to_calendar(&mut tasks_to_add_to_server, cal_server); - } - - Ok(()) - } - - pub async fn resolve_conflicts(&mut self) { - log::error!("We should do something here"); - } - - pub async fn push_items_to_server(&mut self) { - - } - -} - - -fn move_to_calendar(tasks: &mut Vec, calendar: &mut Calendar) { - while tasks.len() > 0 { - let task = tasks.remove(0); - calendar.add_task(task); - } -} - -fn remove_from_calendar(ids: &Vec, calendar: &mut Calendar) { - for id in ids { - log::info!(" Removing {:?} from local calendar", id); - calendar.delete_task(id); - } -} +//! This modules abstracts data sources and merges them in a single virtual one + +use std::error::Error; + +use chrono::{DateTime, Utc}; + +use crate::traits::CalDavSource; +use crate::Calendar; +use crate::Task; +use crate::task::TaskId; + + +pub struct Provider +where + S: CalDavSource, + L: CalDavSource, +{ + /// The remote server + server: S, + /// The local cache + local: L, + + /// The last time the provider successfully synchronized both sources + last_sync: DateTime, +} + +impl Provider +where + S: CalDavSource, + L: CalDavSource, +{ + /// Create a provider that will merge both sources + pub fn new(server: S, local: L, last_sync: DateTime) -> Self { + Self { server, local, last_sync } + } + + pub fn server(&self) -> &S { &self.server } + pub fn local(&self) -> &L { &self.local } + + pub async fn sync(&mut self) -> Result<(), Box> { + let cals_server = self.server.get_calendars_mut().await?; + + for cal_server in cals_server { + let cal_local = match self.local.get_calendar_mut(cal_server.url().clone()).await { + None => { + log::error!("TODO: implement here"); + continue; + }, + Some(cal) => cal, + }; + + let server_mod = cal_server.get_tasks_modified_since(Some(self.last_sync), None); + let local_mod = cal_local.get_tasks_modified_since(Some(self.last_sync), None); + + let mut tasks_to_add_to_local = Vec::new(); + let mut tasks_id_to_remove_from_local = Vec::new(); + for (new_id, new_item) in &server_mod { + if server_mod.contains_key(new_id) { + log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), new_id); + tasks_id_to_remove_from_local.push(new_id.clone()); + } + tasks_to_add_to_local.push((*new_item).clone()); + } + + let mut tasks_to_add_to_server = Vec::new(); + for (new_id, new_item) in &local_mod { + if server_mod.contains_key(new_id) { + log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), new_id); + continue; + } + tasks_to_add_to_server.push((*new_item).clone()); + } + + remove_from_calendar(&tasks_id_to_remove_from_local, cal_local); + move_to_calendar(&mut tasks_to_add_to_local, cal_local); + move_to_calendar(&mut tasks_to_add_to_server, cal_server); + } + + Ok(()) + } +} + + +fn move_to_calendar(tasks: &mut Vec, calendar: &mut Calendar) { + while tasks.len() > 0 { + let task = tasks.remove(0); + calendar.add_task(task); + } +} + +fn remove_from_calendar(ids: &Vec, calendar: &mut Calendar) { + for id in ids { + log::info!(" Removing {:?} from local calendar", id); + calendar.delete_task(id); + } +} diff --git a/tests/sync.rs b/tests/sync.rs index 36fc893..55b48fb 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -40,18 +40,18 @@ fn print_calendar_list(cals: &Vec) { } /// Populate sources with the following: -/// * At the last sync: both sources had A, B, C, D, E, F, G, H at last sync +/// * At the last sync: both sources had A, B, C, D, E, F, G, H, I, J, K, L, M at last sync /// * Before the newer sync, this will be the content of the sources: -/// * server: A, C, D, E', F', G~, H , I -/// * cache: A, B, D', E, F'', G , H~, J +/// * server: A, C, D, E', F', G✓, H , I', K, L, M, N +/// * cache: A, B, D', E, F'', G , H✓, I✓, J✓, K, L, M, O /// /// Hence, here is the expected result after the sync: -/// * both: A, D', E', F', G~, H~, I, J +/// * both: A, D', E', F', G✓, H✓, I', K, L, M, N, O /// /// Notes: /// * X': name has been modified since the last sync /// * F'/F'': name conflict -/// * G~: task has been marked as completed +/// * G✓: task has been marked as completed async fn populate_test_provider() -> Provider { let mut server = Cache::new(&PathBuf::from(String::from("server.json"))); let mut local = Cache::new(&PathBuf::from(String::from("local.json"))); @@ -64,8 +64,13 @@ async fn populate_test_provider() -> Provider { let task_f = Task::new("task F".into(), Utc.ymd(2000, 1, 6).and_hms(0, 0, 0)); let task_g = Task::new("task G".into(), Utc.ymd(2000, 1, 7).and_hms(0, 0, 0)); let task_h = Task::new("task H".into(), Utc.ymd(2000, 1, 8).and_hms(0, 0, 0)); + let task_i = Task::new("task I".into(), Utc.ymd(2000, 1, 9).and_hms(0, 0, 0)); + let task_j = Task::new("task J".into(), Utc.ymd(2000, 1, 10).and_hms(0, 0, 0)); + let task_k = Task::new("task K".into(), Utc.ymd(2000, 1, 11).and_hms(0, 0, 0)); + let task_l = Task::new("task L".into(), Utc.ymd(2000, 1, 12).and_hms(0, 0, 0)); + let task_m = Task::new("task M".into(), Utc.ymd(2000, 1, 12).and_hms(0, 0, 0)); - let last_sync = task_h.last_modified(); + let last_sync = task_m.last_modified(); assert!(last_sync < Utc::now()); let task_b_id = task_b.id().clone(); @@ -75,6 +80,11 @@ async fn populate_test_provider() -> Provider { let task_f_id = task_f.id().clone(); let task_g_id = task_g.id().clone(); let task_h_id = task_h.id().clone(); + let task_i_id = task_i.id().clone(); + let task_j_id = task_j.id().clone(); + let task_k_id = task_k.id().clone(); + let task_l_id = task_l.id().clone(); + let task_m_id = task_m.id().clone(); // Step 1 // Build the calendar as it was at the time of the sync @@ -87,6 +97,11 @@ async fn populate_test_provider() -> Provider { calendar.add_task(task_f); calendar.add_task(task_g); calendar.add_task(task_h); + calendar.add_task(task_i); + calendar.add_task(task_j); + calendar.add_task(task_k); + calendar.add_task(task_l); + calendar.add_task(task_m); server.add_calendar(calendar.clone()); local.add_calendar(calendar.clone()); @@ -106,8 +121,13 @@ async fn populate_test_provider() -> Provider { cal_server.get_task_by_id_mut(&task_g_id).unwrap() .set_completed(true); - let task_i = Task::new("task I".into(), Utc::now()); - cal_server.add_task(task_i); + cal_server.get_task_by_id_mut(&task_i_id).unwrap() + .set_name("I renamed in the server".into()); + + cal_server.delete_task(&task_j_id); + + let task_n = Task::new("task N (new from server)".into(), Utc::now()); + cal_server.add_task(task_n); // Step 3 @@ -125,8 +145,14 @@ async fn populate_test_provider() -> Provider { cal_local.get_task_by_id_mut(&task_h_id).unwrap() .set_completed(true); - let task_j = Task::new("task J".into(), Utc::now()); - cal_local.add_task(task_j); + cal_local.get_task_by_id_mut(&task_i_id).unwrap() + .set_completed(true); + + cal_local.get_task_by_id_mut(&task_j_id).unwrap() + .set_completed(true); + + let task_o = Task::new("task O (new from local)".into(), Utc::now()); + cal_local.add_task(task_o); Provider::new(server, local, last_sync) }