2021-02-28 00:19:00 +01:00
|
|
|
//! This modules abstracts data sources and merges them in a single virtual one
|
|
|
|
|
2021-03-01 23:39:16 +01:00
|
|
|
use std::{error::Error, marker::PhantomData};
|
2021-02-28 00:19:00 +01:00
|
|
|
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
|
2021-03-01 23:39:16 +01:00
|
|
|
use crate::traits::{CalDavSource, CompleteCalendar};
|
2021-02-28 18:00:37 +01:00
|
|
|
use crate::traits::SyncSlave;
|
2021-03-01 23:39:16 +01:00
|
|
|
use crate::traits::PartialCalendar;
|
2021-02-28 11:54:31 +01:00
|
|
|
use crate::Item;
|
|
|
|
use crate::item::ItemId;
|
2021-02-28 00:19:00 +01:00
|
|
|
|
|
|
|
|
2021-02-28 18:00:37 +01:00
|
|
|
/// A data source that combines two `CalDavSources` (usually a server and a local cache), which is able to sync both sources.
|
2021-03-01 23:39:16 +01:00
|
|
|
pub struct Provider<L, T, S, U>
|
2021-02-28 00:19:00 +01:00
|
|
|
where
|
2021-03-01 23:39:16 +01:00
|
|
|
L: CalDavSource<T> + SyncSlave,
|
|
|
|
T: CompleteCalendar,
|
|
|
|
S: CalDavSource<U>,
|
|
|
|
U: PartialCalendar,
|
2021-02-28 00:19:00 +01:00
|
|
|
{
|
|
|
|
/// The remote server
|
|
|
|
server: S,
|
|
|
|
/// The local cache
|
|
|
|
local: L,
|
2021-03-01 23:39:16 +01:00
|
|
|
|
|
|
|
phantom_t: PhantomData<T>,
|
|
|
|
phantom_u: PhantomData<U>,
|
2021-02-28 00:19:00 +01:00
|
|
|
}
|
|
|
|
|
2021-03-01 23:39:16 +01:00
|
|
|
impl<L, T, S, U> Provider<L, T, S, U>
|
2021-02-28 00:19:00 +01:00
|
|
|
where
|
2021-03-01 23:39:16 +01:00
|
|
|
L: CalDavSource<T> + SyncSlave,
|
|
|
|
T: CompleteCalendar,
|
|
|
|
S: CalDavSource<U>,
|
|
|
|
U: PartialCalendar,
|
2021-02-28 00:19:00 +01:00
|
|
|
{
|
2021-02-28 18:02:01 +01:00
|
|
|
/// Create a provider.
|
|
|
|
///
|
|
|
|
/// `server` is usually a [`Client`](crate::client::Client), `local` is usually a [`Cache`](crate::cache::Cache).
|
|
|
|
/// However, both can be interchangeable. The only difference is that `server` always wins in case of a sync conflict
|
2021-02-28 18:00:37 +01:00
|
|
|
pub fn new(server: S, local: L) -> Self {
|
2021-03-01 23:39:16 +01:00
|
|
|
Self { server, local,
|
|
|
|
phantom_t: PhantomData, phantom_u: PhantomData,
|
|
|
|
}
|
2021-02-28 00:19:00 +01:00
|
|
|
}
|
|
|
|
|
2021-02-28 18:02:01 +01:00
|
|
|
/// Returns the data source described as the `server`
|
2021-02-28 00:19:00 +01:00
|
|
|
pub fn server(&self) -> &S { &self.server }
|
2021-02-28 18:02:01 +01:00
|
|
|
/// Returns the data source described as the `local`
|
2021-02-28 00:19:00 +01:00
|
|
|
pub fn local(&self) -> &L { &self.local }
|
2021-02-28 18:00:37 +01:00
|
|
|
/// Returns the last time the `local` source has been synced
|
|
|
|
pub fn last_sync_timestamp(&self) -> Option<DateTime<Utc>> {
|
|
|
|
self.local.get_last_sync()
|
|
|
|
}
|
2021-02-28 00:19:00 +01:00
|
|
|
|
2021-02-28 18:02:01 +01:00
|
|
|
/// Performs a synchronisation between `local` and `server`.
|
|
|
|
///
|
|
|
|
/// This bidirectional sync applies additions/deleteions made on a source to the other source.
|
|
|
|
/// In case of conflicts (the same item has been modified on both ends since the last sync, `server` always wins)
|
2021-02-28 00:19:00 +01:00
|
|
|
pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> {
|
2021-02-28 18:00:37 +01:00
|
|
|
let last_sync = self.local.get_last_sync();
|
2021-03-02 00:20:47 +01:00
|
|
|
log::info!("Starting a sync. Last sync was at {:?}", last_sync);
|
2021-02-28 00:19:00 +01:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2021-03-01 23:39:16 +01:00
|
|
|
let server_mod = cal_server.get_items_modified_since(last_sync, None);
|
2021-02-28 18:00:37 +01:00
|
|
|
let server_del = match last_sync {
|
2021-03-01 23:50:19 +01:00
|
|
|
Some(_date) => cal_server.find_missing_items_compared_to(cal_local),
|
2021-02-28 18:00:37 +01:00
|
|
|
None => Vec::new(),
|
|
|
|
};
|
|
|
|
let local_del = match last_sync {
|
|
|
|
Some(date) => cal_local.get_items_deleted_since(date),
|
|
|
|
None => Vec::new(),
|
|
|
|
};
|
2021-02-28 00:19:00 +01:00
|
|
|
|
2021-02-28 12:21:29 +01:00
|
|
|
// Pull remote changes from the server
|
2021-02-28 00:19:00 +01:00
|
|
|
let mut tasks_to_add_to_local = Vec::new();
|
|
|
|
let mut tasks_id_to_remove_from_local = Vec::new();
|
2021-02-28 11:02:16 +01:00
|
|
|
for deleted_id in server_del {
|
|
|
|
tasks_id_to_remove_from_local.push(deleted_id);
|
|
|
|
}
|
2021-02-28 00:19:00 +01:00
|
|
|
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());
|
|
|
|
}
|
2021-02-28 12:21:29 +01:00
|
|
|
// Even in case of conflicts, "the server always wins", so it is safe to remove tasks from the local cache as soon as now
|
|
|
|
remove_from_calendar(&tasks_id_to_remove_from_local, cal_local);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Push local changes to the server
|
2021-03-01 23:39:16 +01:00
|
|
|
let local_mod = cal_local.get_items_modified_since(last_sync, None);
|
2021-02-28 00:19:00 +01:00
|
|
|
|
|
|
|
let mut tasks_to_add_to_server = Vec::new();
|
2021-02-28 11:02:16 +01:00
|
|
|
let mut tasks_id_to_remove_from_server = Vec::new();
|
|
|
|
for deleted_id in local_del {
|
2021-02-28 12:21:29 +01:00
|
|
|
if server_mod.contains_key(&deleted_id) {
|
|
|
|
log::warn!("Conflict for task {}, that has been locally deleted and updated in the server. Using the server version.", deleted_id);
|
|
|
|
continue;
|
|
|
|
}
|
2021-02-28 11:02:16 +01:00
|
|
|
tasks_id_to_remove_from_server.push(deleted_id);
|
|
|
|
}
|
2021-02-28 00:19:00 +01:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2021-02-28 11:02:16 +01:00
|
|
|
remove_from_calendar(&tasks_id_to_remove_from_server, cal_server);
|
2021-02-28 00:19:00 +01:00
|
|
|
move_to_calendar(&mut tasks_to_add_to_local, cal_local);
|
|
|
|
move_to_calendar(&mut tasks_to_add_to_server, cal_server);
|
|
|
|
}
|
|
|
|
|
2021-02-28 18:00:37 +01:00
|
|
|
self.local.update_last_sync(None);
|
|
|
|
|
2021-02-28 00:19:00 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-01 23:39:16 +01:00
|
|
|
fn move_to_calendar<C: PartialCalendar>(items: &mut Vec<Item>, calendar: &mut C) {
|
2021-02-28 11:54:31 +01:00
|
|
|
while items.len() > 0 {
|
|
|
|
let item = items.remove(0);
|
|
|
|
calendar.add_item(item);
|
2021-02-28 00:19:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-01 23:39:16 +01:00
|
|
|
fn remove_from_calendar<C: PartialCalendar>(ids: &Vec<ItemId>, calendar: &mut C) {
|
2021-02-28 00:19:00 +01:00
|
|
|
for id in ids {
|
|
|
|
log::info!(" Removing {:?} from local calendar", id);
|
2021-02-28 11:54:31 +01:00
|
|
|
calendar.delete_item(id);
|
2021-02-28 00:19:00 +01:00
|
|
|
}
|
|
|
|
}
|