[tests] Conflicts handling
This commit is contained in:
parent
89d2c3fcef
commit
91f62aa200
2 changed files with 132 additions and 124 deletions
210
src/provider.rs
210
src/provider.rs
|
@ -1,114 +1,96 @@
|
||||||
//! This modules abstracts data sources and merges them in a single virtual one
|
//! This modules abstracts data sources and merges them in a single virtual one
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use crate::traits::CalDavSource;
|
use crate::traits::CalDavSource;
|
||||||
use crate::Calendar;
|
use crate::Calendar;
|
||||||
use crate::Task;
|
use crate::Task;
|
||||||
use crate::task::TaskId;
|
use crate::task::TaskId;
|
||||||
|
|
||||||
|
|
||||||
pub struct Provider<S, L>
|
pub struct Provider<S, L>
|
||||||
where
|
where
|
||||||
S: CalDavSource,
|
S: CalDavSource,
|
||||||
L: CalDavSource,
|
L: CalDavSource,
|
||||||
{
|
{
|
||||||
/// The remote server
|
/// The remote server
|
||||||
server: S,
|
server: S,
|
||||||
/// The local cache
|
/// The local cache
|
||||||
local: L,
|
local: L,
|
||||||
|
|
||||||
/// The last time the provider successfully synchronized both sources
|
/// The last time the provider successfully synchronized both sources
|
||||||
last_sync: DateTime<Utc>,
|
last_sync: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S,L> Provider<S, L>
|
impl<S,L> Provider<S, L>
|
||||||
where
|
where
|
||||||
S: CalDavSource,
|
S: CalDavSource,
|
||||||
L: CalDavSource,
|
L: CalDavSource,
|
||||||
{
|
{
|
||||||
/// Create a provider that will merge both sources
|
/// Create a provider that will merge both sources
|
||||||
pub fn new(server: S, local: L, last_sync: DateTime<Utc>) -> Self {
|
pub fn new(server: S, local: L, last_sync: DateTime<Utc>) -> Self {
|
||||||
Self { server, local, last_sync }
|
Self { server, local, last_sync }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server(&self) -> &S { &self.server }
|
pub fn server(&self) -> &S { &self.server }
|
||||||
pub fn local(&self) -> &L { &self.local }
|
pub fn local(&self) -> &L { &self.local }
|
||||||
|
|
||||||
pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> {
|
pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
self.pull_items_from_server().await?;
|
let cals_server = self.server.get_calendars_mut().await?;
|
||||||
self.resolve_conflicts().await;
|
|
||||||
self.push_items_to_server().await;
|
for cal_server in cals_server {
|
||||||
|
let cal_local = match self.local.get_calendar_mut(cal_server.url().clone()).await {
|
||||||
// what to do with errors? Return Err directly? Go ahead as far as we can?
|
None => {
|
||||||
Ok(())
|
log::error!("TODO: implement here");
|
||||||
}
|
continue;
|
||||||
|
},
|
||||||
pub async fn pull_items_from_server(&mut self) -> Result<(), Box<dyn Error>> {
|
Some(cal) => cal,
|
||||||
let cals_server = self.server.get_calendars_mut().await?;
|
};
|
||||||
|
|
||||||
for cal_server in cals_server {
|
let server_mod = cal_server.get_tasks_modified_since(Some(self.last_sync), None);
|
||||||
let cal_local = match self.local.get_calendar_mut(cal_server.url().clone()).await {
|
let local_mod = cal_local.get_tasks_modified_since(Some(self.last_sync), None);
|
||||||
None => {
|
|
||||||
log::error!("TODO: implement here");
|
let mut tasks_to_add_to_local = Vec::new();
|
||||||
continue;
|
let mut tasks_id_to_remove_from_local = Vec::new();
|
||||||
},
|
for (new_id, new_item) in &server_mod {
|
||||||
Some(cal) => cal,
|
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());
|
||||||
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);
|
tasks_to_add_to_local.push((*new_item).clone());
|
||||||
|
}
|
||||||
let mut tasks_to_add_to_local = Vec::new();
|
|
||||||
let mut tasks_id_to_remove_from_local = Vec::new();
|
let mut tasks_to_add_to_server = Vec::new();
|
||||||
for (new_id, new_item) in &server_mod {
|
for (new_id, new_item) in &local_mod {
|
||||||
if server_mod.contains_key(new_id) {
|
if server_mod.contains_key(new_id) {
|
||||||
log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), 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());
|
continue;
|
||||||
}
|
}
|
||||||
tasks_to_add_to_local.push((*new_item).clone());
|
tasks_to_add_to_server.push((*new_item).clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tasks_to_add_to_server = Vec::new();
|
remove_from_calendar(&tasks_id_to_remove_from_local, cal_local);
|
||||||
for (new_id, new_item) in &local_mod {
|
move_to_calendar(&mut tasks_to_add_to_local, cal_local);
|
||||||
if server_mod.contains_key(new_id) {
|
move_to_calendar(&mut tasks_to_add_to_server, cal_server);
|
||||||
log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), new_id);
|
}
|
||||||
continue;
|
|
||||||
}
|
Ok(())
|
||||||
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);
|
fn move_to_calendar(tasks: &mut Vec<Task>, calendar: &mut Calendar) {
|
||||||
move_to_calendar(&mut tasks_to_add_to_server, cal_server);
|
while tasks.len() > 0 {
|
||||||
}
|
let task = tasks.remove(0);
|
||||||
|
calendar.add_task(task);
|
||||||
Ok(())
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve_conflicts(&mut self) {
|
fn remove_from_calendar(ids: &Vec<TaskId>, calendar: &mut Calendar) {
|
||||||
log::error!("We should do something here");
|
for id in ids {
|
||||||
}
|
log::info!(" Removing {:?} from local calendar", id);
|
||||||
|
calendar.delete_task(id);
|
||||||
pub async fn push_items_to_server(&mut self) {
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn move_to_calendar(tasks: &mut Vec<Task>, calendar: &mut Calendar) {
|
|
||||||
while tasks.len() > 0 {
|
|
||||||
let task = tasks.remove(0);
|
|
||||||
calendar.add_task(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_from_calendar(ids: &Vec<TaskId>, calendar: &mut Calendar) {
|
|
||||||
for id in ids {
|
|
||||||
log::info!(" Removing {:?} from local calendar", id);
|
|
||||||
calendar.delete_task(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -40,18 +40,18 @@ fn print_calendar_list(cals: &Vec<Calendar>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Populate sources with the following:
|
/// 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:
|
/// * Before the newer sync, this will be the content of the sources:
|
||||||
/// * server: A, C, D, E', F', G~, H , I
|
/// * server: A, C, D, E', F', G✓, H , I', K, L, M, N
|
||||||
/// * cache: A, B, D', E, F'', G , H~, J
|
/// * cache: A, B, D', E, F'', G , H✓, I✓, J✓, K, L, M, O
|
||||||
///
|
///
|
||||||
/// Hence, here is the expected result after the sync:
|
/// 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:
|
/// Notes:
|
||||||
/// * X': name has been modified since the last sync
|
/// * X': name has been modified since the last sync
|
||||||
/// * F'/F'': name conflict
|
/// * F'/F'': name conflict
|
||||||
/// * G~: task has been marked as completed
|
/// * G✓: task has been marked as completed
|
||||||
async fn populate_test_provider() -> Provider<Cache, Cache> {
|
async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||||
let mut server = Cache::new(&PathBuf::from(String::from("server.json")));
|
let mut server = Cache::new(&PathBuf::from(String::from("server.json")));
|
||||||
let mut local = Cache::new(&PathBuf::from(String::from("local.json")));
|
let mut local = Cache::new(&PathBuf::from(String::from("local.json")));
|
||||||
|
@ -64,8 +64,13 @@ async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||||
let task_f = Task::new("task F".into(), Utc.ymd(2000, 1, 6).and_hms(0, 0, 0));
|
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_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_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());
|
assert!(last_sync < Utc::now());
|
||||||
|
|
||||||
let task_b_id = task_b.id().clone();
|
let task_b_id = task_b.id().clone();
|
||||||
|
@ -75,6 +80,11 @@ async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||||
let task_f_id = task_f.id().clone();
|
let task_f_id = task_f.id().clone();
|
||||||
let task_g_id = task_g.id().clone();
|
let task_g_id = task_g.id().clone();
|
||||||
let task_h_id = task_h.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
|
// Step 1
|
||||||
// Build the calendar as it was at the time of the sync
|
// Build the calendar as it was at the time of the sync
|
||||||
|
@ -87,6 +97,11 @@ async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||||
calendar.add_task(task_f);
|
calendar.add_task(task_f);
|
||||||
calendar.add_task(task_g);
|
calendar.add_task(task_g);
|
||||||
calendar.add_task(task_h);
|
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());
|
server.add_calendar(calendar.clone());
|
||||||
local.add_calendar(calendar.clone());
|
local.add_calendar(calendar.clone());
|
||||||
|
@ -106,8 +121,13 @@ async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||||
cal_server.get_task_by_id_mut(&task_g_id).unwrap()
|
cal_server.get_task_by_id_mut(&task_g_id).unwrap()
|
||||||
.set_completed(true);
|
.set_completed(true);
|
||||||
|
|
||||||
let task_i = Task::new("task I".into(), Utc::now());
|
cal_server.get_task_by_id_mut(&task_i_id).unwrap()
|
||||||
cal_server.add_task(task_i);
|
.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
|
// Step 3
|
||||||
|
@ -125,8 +145,14 @@ async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||||
cal_local.get_task_by_id_mut(&task_h_id).unwrap()
|
cal_local.get_task_by_id_mut(&task_h_id).unwrap()
|
||||||
.set_completed(true);
|
.set_completed(true);
|
||||||
|
|
||||||
let task_j = Task::new("task J".into(), Utc::now());
|
cal_local.get_task_by_id_mut(&task_i_id).unwrap()
|
||||||
cal_local.add_task(task_j);
|
.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)
|
Provider::new(server, local, last_sync)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue