You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
11 KiB
313 lines
11 KiB
use std::collections::{HashMap, HashSet}; |
|
use std::error::Error; |
|
|
|
use serde::{Deserialize, Serialize}; |
|
use async_trait::async_trait; |
|
use csscolorparser::Color; |
|
|
|
use crate::item::SyncStatus; |
|
use crate::traits::{BaseCalendar, CompleteCalendar}; |
|
use crate::calendar::{CalendarId, SupportedComponents}; |
|
use crate::Item; |
|
use crate::item::ItemId; |
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
use std::sync::{Arc, Mutex}; |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
use crate::mock_behaviour::MockBehaviour; |
|
|
|
|
|
/// A calendar used by the [`cache`](crate::cache) module |
|
/// |
|
/// Most of its methods are part of traits implementations |
|
#[derive(Clone, Debug, Serialize, Deserialize)] |
|
pub struct CachedCalendar { |
|
name: String, |
|
id: CalendarId, |
|
supported_components: SupportedComponents, |
|
color: Option<Color>, |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
#[serde(skip)] |
|
mock_behaviour: Option<Arc<Mutex<MockBehaviour>>>, |
|
|
|
items: HashMap<ItemId, Item>, |
|
} |
|
|
|
impl CachedCalendar { |
|
/// Activate the "mocking remote calendar" feature (i.e. ignore sync statuses, since this is what an actual CalDAV sever would do) |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
pub fn set_mock_behaviour(&mut self, mock_behaviour: Option<Arc<Mutex<MockBehaviour>>>) { |
|
self.mock_behaviour = mock_behaviour; |
|
} |
|
|
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
fn add_item_maybe_mocked(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
if self.mock_behaviour.is_some() { |
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_add_item())?; |
|
self.add_or_update_item_force_synced(item) |
|
} else { |
|
self.regular_add_or_update_item(item) |
|
} |
|
} |
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
fn update_item_maybe_mocked(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
if self.mock_behaviour.is_some() { |
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_update_item())?; |
|
self.add_or_update_item_force_synced(item) |
|
} else { |
|
self.regular_add_or_update_item(item) |
|
} |
|
} |
|
|
|
/// Add or update an item |
|
fn regular_add_or_update_item(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
let ss_clone = item.sync_status().clone(); |
|
log::debug!("Adding or updating an item with {:?}", ss_clone); |
|
self.items.insert(item.id().clone(), item); |
|
Ok(ss_clone) |
|
} |
|
|
|
/// Add or update an item, but force a "synced" SyncStatus. This is the normal behaviour that would happen on a server |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
fn add_or_update_item_force_synced(&mut self, mut item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
log::debug!("Adding or updating an item, but forces a synced SyncStatus"); |
|
match item.sync_status() { |
|
SyncStatus::Synced(_) => (), |
|
_ => item.set_sync_status(SyncStatus::random_synced()), |
|
}; |
|
let ss_clone = item.sync_status().clone(); |
|
self.items.insert(item.id().clone(), item); |
|
Ok(ss_clone) |
|
} |
|
|
|
/// Some kind of equality check |
|
#[cfg(any(test, feature = "integration_tests"))] |
|
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 |
|
|| self.color != other.color |
|
{ |
|
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) |
|
} |
|
|
|
/// The non-async version of [`Self::get_item_ids`] |
|
pub fn get_item_ids_sync(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> { |
|
Ok(self.items.iter() |
|
.map(|(id, _)| id.clone()) |
|
.collect() |
|
) |
|
} |
|
|
|
/// The non-async version of [`Self::get_items`] |
|
pub fn get_items_sync(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> { |
|
Ok(self.items.iter() |
|
.map(|(id, item)| (id.clone(), item)) |
|
.collect() |
|
) |
|
} |
|
|
|
/// The non-async version of [`Self::get_item_by_id`] |
|
pub fn get_item_by_id_sync<'a>(&'a self, id: &ItemId) -> Option<&'a Item> { |
|
self.items.get(id) |
|
} |
|
|
|
/// The non-async version of [`Self::get_item_by_id_mut`] |
|
pub fn get_item_by_id_mut_sync<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> { |
|
self.items.get_mut(id) |
|
} |
|
|
|
/// The non-async version of [`Self::add_item`] |
|
pub fn add_item_sync(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
if self.items.contains_key(item.id()) { |
|
return Err(format!("Item {:?} cannot be added, it exists already", item.id()).into()); |
|
} |
|
#[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] |
|
return self.regular_add_or_update_item(item); |
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
return self.add_item_maybe_mocked(item); |
|
} |
|
|
|
/// The non-async version of [`Self::update_item`] |
|
pub fn update_item_sync(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
if self.items.contains_key(item.id()) == false { |
|
return Err(format!("Item {:?} cannot be updated, it does not already exist", item.id()).into()); |
|
} |
|
#[cfg(not(feature = "local_calendar_mocks_remote_calendars"))] |
|
return self.regular_add_or_update_item(item); |
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
return self.update_item_maybe_mocked(item); |
|
} |
|
|
|
} |
|
|
|
|
|
#[async_trait] |
|
impl BaseCalendar for CachedCalendar { |
|
fn name(&self) -> &str { |
|
&self.name |
|
} |
|
|
|
fn id(&self) -> &CalendarId { |
|
&self.id |
|
} |
|
|
|
fn supported_components(&self) -> SupportedComponents { |
|
self.supported_components |
|
} |
|
|
|
fn color(&self) -> Option<&Color> { |
|
self.color.as_ref() |
|
} |
|
|
|
async fn add_item(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
self.add_item_sync(item) |
|
} |
|
|
|
async fn update_item(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> { |
|
self.update_item_sync(item) |
|
} |
|
} |
|
|
|
#[async_trait] |
|
impl CompleteCalendar for CachedCalendar { |
|
fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option<Color>) -> Self { |
|
Self { |
|
name, id, supported_components, color, |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
mock_behaviour: None, |
|
items: HashMap::new(), |
|
} |
|
} |
|
|
|
async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> { |
|
self.get_item_ids_sync() |
|
} |
|
|
|
async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> { |
|
self.get_items_sync() |
|
} |
|
|
|
async fn get_item_by_id<'a>(&'a self, id: &ItemId) -> Option<&'a Item> { |
|
self.get_item_by_id_sync(id) |
|
} |
|
|
|
async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> { |
|
self.get_item_by_id_mut_sync(id) |
|
} |
|
|
|
async fn mark_for_deletion(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> { |
|
match self.items.get_mut(item_id) { |
|
None => Err("no item for this key".into()), |
|
Some(item) => { |
|
match item.sync_status() { |
|
SyncStatus::Synced(prev_ss) => { |
|
let prev_ss = prev_ss.clone(); |
|
item.set_sync_status( SyncStatus::LocallyDeleted(prev_ss)); |
|
}, |
|
SyncStatus::LocallyModified(prev_ss) => { |
|
let prev_ss = prev_ss.clone(); |
|
item.set_sync_status( SyncStatus::LocallyDeleted(prev_ss)); |
|
}, |
|
SyncStatus::LocallyDeleted(prev_ss) => { |
|
let prev_ss = prev_ss.clone(); |
|
item.set_sync_status( SyncStatus::LocallyDeleted(prev_ss)); |
|
}, |
|
SyncStatus::NotSynced => { |
|
// This was never synced to the server, we can safely delete it as soon as now |
|
self.items.remove(item_id); |
|
}, |
|
}; |
|
Ok(()) |
|
} |
|
} |
|
} |
|
|
|
async fn immediately_delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> { |
|
match self.items.remove(item_id) { |
|
None => Err(format!("Item {} is absent from this calendar", item_id).into()), |
|
Some(_) => Ok(()) |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
// This class can be used to mock a remote calendar for integration tests |
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
use crate::{item::VersionTag, |
|
traits::DavCalendar, |
|
resource::Resource}; |
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
#[async_trait] |
|
impl DavCalendar for CachedCalendar { |
|
fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option<Color>) -> Self { |
|
crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components, color) |
|
} |
|
|
|
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> { |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_version_tags())?; |
|
|
|
use crate::item::SyncStatus; |
|
|
|
let mut result = HashMap::new(); |
|
|
|
for (id, item) in self.items.iter() { |
|
let vt = match item.sync_status() { |
|
SyncStatus::Synced(vt) => vt.clone(), |
|
_ => { |
|
panic!("Mock calendars must contain only SyncStatus::Synced. Got {:?}", item); |
|
} |
|
}; |
|
result.insert(id.clone(), vt); |
|
} |
|
|
|
Ok(result) |
|
} |
|
|
|
async fn get_item_by_id(&self, id: &ItemId) -> Result<Option<Item>, Box<dyn Error>> { |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_by_id())?; |
|
|
|
Ok(self.items.get(id).cloned()) |
|
} |
|
|
|
async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> { |
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")] |
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_delete_item())?; |
|
|
|
self.immediately_delete_item(item_id).await |
|
} |
|
}
|
|
|