kitchen-freezer/src/provider.rs

163 lines
6.3 KiB
Rust
Raw Normal View History

2021-02-28 00:19:00 +01:00
//! This modules abstracts data sources and merges them in a single virtual one
2021-03-05 23:32:42 +01:00
use std::error::Error;
2021-03-19 00:08:16 +01:00
use std::collections::HashSet;
2021-03-05 23:32:42 +01:00
use std::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;
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>,
2021-03-21 19:19:49 +01:00
U: PartialCalendar + Sync + Send,
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>,
2021-03-21 19:19:49 +01:00
U: PartialCalendar + Sync + Send,
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`.
///
2021-03-20 20:12:48 +01:00
/// This bidirectional sync applies additions/deletions made on a source to the other source.
2021-02-28 18:02:01 +01:00
/// 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
2021-03-20 20:12:48 +01:00
let cals_server = self.server.get_calendars().await?;
2021-03-19 00:08:16 +01:00
for (id, cal_server) in cals_server {
2021-03-18 23:59:06 +01:00
let mut cal_server = cal_server.lock().unwrap();
2021-03-21 19:27:55 +01:00
let cal_local = match self.local.get_calendar(&id).await {
2021-02-28 00:19:00 +01:00
None => {
log::error!("TODO: implement here");
continue;
},
Some(cal) => cal,
};
2021-03-18 23:59:06 +01:00
let mut cal_local = cal_local.lock().unwrap();
2021-02-28 00:19:00 +01:00
2021-03-20 20:12:48 +01:00
// Step 1 - "Server always wins", so a delteion from the server must be applied locally, even if it was locally modified.
let mut local_dels = match last_sync {
None => HashSet::new(),
2021-03-21 19:05:22 +01:00
Some(date) => cal_local.get_items_deleted_since(date).await,
2021-02-28 18:00:37 +01:00
};
2021-03-20 20:12:48 +01:00
if last_sync.is_some() {
2021-03-21 19:19:49 +01:00
let server_deletions = cal_server.find_deletions_from(cal_local.get_item_ids().await).await;
2021-03-20 20:12:48 +01:00
for server_del_id in server_deletions {
// Even in case of conflicts, "the server always wins", so it is safe to remove tasks from the local cache as soon as now
2021-03-21 19:19:49 +01:00
if let Err(err) = cal_local.delete_item(&server_del_id).await {
2021-03-20 20:12:48 +01:00
log::error!("Unable to remove local item {}: {}", server_del_id, err);
}
if local_dels.contains(&server_del_id) {
local_dels.remove(&server_del_id);
2021-02-28 00:19:00 +01:00
}
}
2021-03-20 20:12:48 +01:00
}
2021-02-28 12:21:29 +01:00
2021-03-20 20:12:48 +01:00
// Step 2 - Compare both changesets...
2021-03-21 19:05:22 +01:00
let server_mods = cal_server.get_items_modified_since(last_sync, None).await;
let mut local_mods = cal_local.get_items_modified_since(last_sync, None).await;
2021-03-20 20:12:48 +01:00
// ...import remote changes,...
let mut conflicting_tasks = Vec::new();
let mut tasks_to_add = Vec::new();
for (server_mod_id, server_mod) in server_mods {
if local_mods.contains_key(&server_mod_id) {
log::warn!("Conflict for task {} (modified in both sources). Using the server version", server_mod_id);
conflicting_tasks.push(server_mod_id.clone());
local_mods.remove(&server_mod_id);
}
if local_dels.contains(&server_mod_id) {
log::warn!("Conflict for task {} (modified in the server, deleted locally). Reverting to the server version", server_mod_id);
local_dels.remove(&server_mod_id);
}
tasks_to_add.push(server_mod.clone());
}
2021-02-28 12:21:29 +01:00
2021-03-20 20:12:48 +01:00
// ...upload local deletions,...
for local_del_id in local_dels {
2021-03-21 19:19:49 +01:00
if let Err(err) = cal_server.delete_item(&local_del_id).await {
2021-03-20 20:12:48 +01:00
log::error!("Unable to remove remote item {}: {}", local_del_id, err);
2021-02-28 12:21:29 +01:00
}
}
2021-03-05 23:32:42 +01:00
2021-03-20 20:12:48 +01:00
// ...and upload local changes
for (local_mod_id, local_mod) in local_mods {
// Conflicts are no longer in local_mods
2021-03-21 19:19:49 +01:00
if let Err(err) = cal_server.delete_item(&local_mod_id).await {
2021-03-20 20:12:48 +01:00
log::error!("Unable to remove remote item (before an update) {}: {}", local_mod_id, err);
2021-02-28 00:19:00 +01:00
}
2021-03-20 20:12:48 +01:00
// TODO: should I add a .update_item()?
2021-03-21 19:19:49 +01:00
cal_server.add_item(local_mod.clone()).await;
2021-02-28 00:19:00 +01:00
}
2021-03-21 19:19:49 +01:00
remove_from_calendar(&conflicting_tasks, &mut (*cal_local)).await;
move_to_calendar(&mut tasks_to_add, &mut (*cal_local)).await;
2021-02-28 00:19:00 +01:00
}
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-21 19:19:49 +01:00
async fn move_to_calendar<C: PartialCalendar>(items: &mut Vec<Item>, calendar: &mut C) {
while items.len() > 0 {
let item = items.remove(0);
2021-03-20 20:12:48 +01:00
log::warn!("Moving {} to calendar", item.name());
2021-03-21 19:19:49 +01:00
calendar.add_item(item).await;
2021-02-28 00:19:00 +01:00
}
}
2021-03-21 19:19:49 +01:00
async fn remove_from_calendar<C: PartialCalendar>(ids: &Vec<ItemId>, calendar: &mut C) {
2021-02-28 00:19:00 +01:00
for id in ids {
2021-03-20 20:12:48 +01:00
log::info!(" Removing {:?} from calendar", id);
2021-03-21 19:19:49 +01:00
if let Err(err) = calendar.delete_item(id).await {
2021-03-19 00:08:16 +01:00
log::warn!("Unable to delete item {:?} from calendar: {}", id, err);
}
2021-02-28 00:19:00 +01:00
}
}