Turning calendars into Arc<Mutex>

This commit is contained in:
daladim 2021-03-18 23:59:06 +01:00
parent d07a2b9493
commit 060f33b4bd
8 changed files with 199 additions and 91 deletions

View file

@ -6,6 +6,7 @@ use std::error::Error;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash; use std::hash::Hash;
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use async_trait::async_trait; use async_trait::async_trait;
@ -28,7 +29,7 @@ pub struct Cache {
#[derive(Default, Debug, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
struct CachedData { struct CachedData {
calendars: HashMap<CalendarId, CachedCalendar>, calendars: HashMap<CalendarId, Arc<Mutex<CachedCalendar>>>,
last_sync: Option<DateTime<Utc>>, last_sync: Option<DateTime<Utc>>,
} }
@ -81,8 +82,9 @@ impl Cache {
} }
pub fn add_calendar(&mut self, calendar: CachedCalendar) { pub fn add_calendar(&mut self, calendar: Arc<Mutex<CachedCalendar>>) {
self.data.calendars.insert(calendar.id().clone(), calendar); let id = calendar.lock().unwrap().id().clone();
self.data.calendars.insert(id, calendar);
} }
/// Compares two Caches to check they have the same current content /// Compares two Caches to check they have the same current content
@ -97,8 +99,9 @@ impl Cache {
} }
for (id, cal_l) in calendars_l { for (id, cal_l) in calendars_l {
let cal_r = match calendars_r.get(id) { let cal_l = cal_l.lock().unwrap();
Some(c) => c, let cal_r = match calendars_r.get(&id) {
Some(c) => c.lock().unwrap(),
None => return Err("should not happen, we've just tested keys are the same".into()), None => return Err("should not happen, we've just tested keys are the same".into()),
}; };
@ -138,23 +141,15 @@ where
#[async_trait] #[async_trait]
impl CalDavSource<CachedCalendar> for Cache { impl CalDavSource<CachedCalendar> for Cache {
async fn get_calendars(&self) -> Result<&HashMap<CalendarId, CachedCalendar>, Box<dyn Error>> { async fn get_calendars(&self) -> Result<HashMap<CalendarId, Arc<Mutex<CachedCalendar>>>, Box<dyn Error>> {
Ok(&self.data.calendars) Ok(self.data.calendars.iter()
.map(|(id, cal)| (id.clone(), cal.clone()))
.collect()
)
} }
async fn get_calendars_mut(&mut self) -> Result<HashMap<CalendarId, &mut CachedCalendar>, Box<dyn Error>> { async fn get_calendar(&self, id: CalendarId) -> Option<Arc<Mutex<CachedCalendar>>> {
let mut hm = HashMap::new(); self.data.calendars.get(&id).map(|arc| arc.clone())
for (id, val) in self.data.calendars.iter_mut() {
hm.insert(id.clone(), val);
}
Ok(hm)
}
async fn get_calendar(&self, id: CalendarId) -> Option<&CachedCalendar> {
self.data.calendars.get(&id)
}
async fn get_calendar_mut(&mut self, id: CalendarId) -> Option<&mut CachedCalendar> {
self.data.calendars.get_mut(&id)
} }
} }
@ -184,7 +179,7 @@ mod tests {
let cal1 = CachedCalendar::new("shopping list".to_string(), let cal1 = CachedCalendar::new("shopping list".to_string(),
Url::parse("https://caldav.com/shopping").unwrap(), Url::parse("https://caldav.com/shopping").unwrap(),
SupportedComponents::TODO); SupportedComponents::TODO);
cache.add_calendar(cal1); cache.add_calendar(Arc::new(Mutex::new(cal1)));
cache.save_to_file(); cache.save_to_file();

View file

@ -1,4 +1,5 @@
pub mod cached_calendar; pub mod cached_calendar;
pub mod remote_calendar;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::error::Error; use std::error::Error;

View file

@ -1,21 +1,39 @@
use std::collections::{HashMap, HashSet};
use std::error::Error;
use url::Url;
use chrono::{DateTime, Utc};
use crate::traits::PartialCalendar; use crate::traits::PartialCalendar;
use crate::calendar::SupportedComponents;
use crate::calendar::CalendarId;
use crate::item::ItemId;
use crate::item::Item;
/// A CalDAV calendar created by a [`Client`](crate::client::Client). /// A CalDAV calendar created by a [`Client`](crate::client::Client).
#[derive(Clone)]
pub struct RemoteCalendar { pub struct RemoteCalendar {
name: String, name: String,
url: Url, url: Url,
supported_components: SupportedComponents supported_components: SupportedComponents
} }
impl PartialCalendar for RemoteCalendar { impl RemoteCalendar {
fn name(&self) -> &str { pub fn new(name: String, url: Url, supported_components: SupportedComponents) -> Self {
&self.name Self {
name, url, supported_components
}
} }
}
impl PartialCalendar for RemoteCalendar {
fn name(&self) -> &str { &self.name }
fn id(&self) -> &CalendarId { &self.url }
fn supported_components(&self) -> crate::calendar::SupportedComponents { fn supported_components(&self) -> crate::calendar::SupportedComponents {
self.supported_components self.supported_components
} }
/// Returns the items that have been last-modified after `since`
fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<crate::calendar::SearchFilter>) fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<crate::calendar::SearchFilter>)
-> HashMap<ItemId, &Item> -> HashMap<ItemId, &Item>
{ {
@ -23,16 +41,28 @@ impl PartialCalendar for RemoteCalendar {
HashMap::new() HashMap::new()
} }
/// Get the IDs of all current items in this calendar
fn get_item_ids(&mut self) -> HashSet<ItemId> {
log::error!("Not implemented");
HashSet::new()
}
/// Returns a particular item
fn get_item_by_id_mut(&mut self, id: &ItemId) -> Option<&mut Item> { fn get_item_by_id_mut(&mut self, id: &ItemId) -> Option<&mut Item> {
log::error!("Not implemented"); log::error!("Not implemented");
None None
} }
/// Add an item into this calendar
fn add_item(&mut self, item: Item) { fn add_item(&mut self, item: Item) {
log::error!("Not implemented"); log::error!("Not implemented");
} }
fn delete_item(&mut self, item_id: &ItemId) { /// Remove an item from this calendar
fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
log::error!("Not implemented"); log::error!("Not implemented");
Ok(())
} }
} }

View file

@ -3,15 +3,18 @@
use std::error::Error; use std::error::Error;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use reqwest::Method; use reqwest::Method;
use reqwest::header::CONTENT_TYPE; use reqwest::header::CONTENT_TYPE;
use minidom::Element; use minidom::Element;
use url::Url; use url::Url;
use crate::utils::{find_elem, find_elems}; use crate::utils::{find_elem, find_elems};
use crate::calendar::cached_calendar::CachedCalendar; use crate::calendar::remote_calendar::RemoteCalendar;
use crate::calendar::CalendarId; use crate::calendar::CalendarId;
use crate::traits::CalDavSource;
use crate::traits::PartialCalendar; use crate::traits::PartialCalendar;
@ -71,9 +74,17 @@ pub struct Client {
username: String, username: String,
password: String, password: String,
/// The interior mutable part of a Client.
/// This data may be retrieved once and then cached
cached_replies: Mutex<CachedReplies>,
}
#[derive(Default)]
struct CachedReplies {
principal: Option<Url>, principal: Option<Url>,
calendar_home_set: Option<Url>, calendar_home_set: Option<Url>,
calendars: Option<HashMap<CalendarId, CachedCalendar>>, calendars: Option<HashMap<CalendarId, Arc<Mutex<RemoteCalendar>>>>,
} }
impl Client { impl Client {
@ -85,9 +96,7 @@ impl Client {
url, url,
username: username.to_string(), username: username.to_string(),
password: password.to_string(), password: password.to_string(),
principal: None, cached_replies: Mutex::new(CachedReplies::default()),
calendar_home_set: None,
calendars: None,
}) })
} }
@ -111,33 +120,30 @@ impl Client {
let text = self.sub_request(url, body, 0).await?; let text = self.sub_request(url, body, 0).await?;
let mut current_element: &Element = &text.parse().unwrap(); let mut current_element: &Element = &text.parse().unwrap();
items.iter() for item in items {
.map(|item| {
current_element = find_elem(&current_element, item).unwrap(); current_element = find_elem(&current_element, item).unwrap();
}) }
.collect::<()>();
Ok(current_element.text()) Ok(current_element.text())
} }
/// Return the Principal URL, or fetch it from server if not known yet /// Return the Principal URL, or fetch it from server if not known yet
async fn get_principal(&mut self) -> Result<Url, Box<dyn Error>> { async fn get_principal(&self) -> Result<Url, Box<dyn Error>> {
if let Some(p) = &self.principal { if let Some(p) = &self.cached_replies.lock().unwrap().principal {
return Ok(p.clone()); return Ok(p.clone());
} }
let href = self.sub_request_and_process(&self.url, DAVCLIENT_BODY.into(), &["current-user-principal", "href"]).await?; let href = self.sub_request_and_process(&self.url, DAVCLIENT_BODY.into(), &["current-user-principal", "href"]).await?;
let mut principal_url = self.url.clone(); let mut principal_url = self.url.clone();
principal_url.set_path(&href); principal_url.set_path(&href);
self.principal = Some(principal_url.clone()); self.cached_replies.lock().unwrap().principal = Some(principal_url.clone());
log::debug!("Principal URL is {}", href); log::debug!("Principal URL is {}", href);
return Ok(principal_url); return Ok(principal_url);
} }
/// Return the Homeset URL, or fetch it from server if not known yet /// Return the Homeset URL, or fetch it from server if not known yet
async fn get_cal_home_set(&mut self) -> Result<Url, Box<dyn Error>> { async fn get_cal_home_set(&self) -> Result<Url, Box<dyn Error>> {
if let Some(h) = &self.calendar_home_set { if let Some(h) = &self.cached_replies.lock().unwrap().calendar_home_set {
return Ok(h.clone()); return Ok(h.clone());
} }
let principal_url = self.get_principal().await?; let principal_url = self.get_principal().await?;
@ -145,16 +151,88 @@ impl Client {
let href = self.sub_request_and_process(&principal_url, HOMESET_BODY.into(), &["calendar-home-set", "href"]).await?; let href = self.sub_request_and_process(&principal_url, HOMESET_BODY.into(), &["calendar-home-set", "href"]).await?;
let mut chs_url = self.url.clone(); let mut chs_url = self.url.clone();
chs_url.set_path(&href); chs_url.set_path(&href);
self.calendar_home_set = Some(chs_url.clone()); self.cached_replies.lock().unwrap().calendar_home_set = Some(chs_url.clone());
log::debug!("Calendar home set URL is {:?}", chs_url.path()); log::debug!("Calendar home set URL is {:?}", chs_url.path());
Ok(chs_url) Ok(chs_url)
} }
async fn populate_calendars(&self) -> Result<(), Box<dyn Error>> {
let cal_home_set = self.get_cal_home_set().await?;
let text = self.sub_request(&cal_home_set, CAL_BODY.into(), 1).await?;
let root: Element = text.parse().unwrap();
let reps = find_elems(&root, "response");
let mut calendars = HashMap::new();
for rep in reps {
let display_name = find_elem(rep, "displayname").map(|e| e.text()).unwrap_or("<no name>".to_string());
log::debug!("Considering calendar {}", display_name);
// We filter out non-calendar items
let resource_types = match find_elem(rep, "resourcetype") {
None => continue,
Some(rt) => rt,
};
let mut found_calendar_type = false;
for resource_type in resource_types.children() {
if resource_type.name() == "calendar" {
found_calendar_type = true;
break;
}
}
if found_calendar_type == false {
continue;
}
// We filter out the root calendar collection, that has an empty supported-calendar-component-set
let el_supported_comps = match find_elem(rep, "supported-calendar-component-set") {
None => continue,
Some(comps) => comps,
};
if el_supported_comps.children().count() == 0 {
continue;
}
let calendar_href = match find_elem(rep, "href") {
None => {
log::warn!("Calendar {} has no URL! Ignoring it.", display_name);
continue;
},
Some(h) => h.text(),
};
let mut this_calendar_url = self.url.clone();
this_calendar_url.set_path(&calendar_href);
let supported_components = match crate::calendar::SupportedComponents::try_from(el_supported_comps.clone()) {
Err(err) => {
log::warn!("Calendar {} has invalid supported components ({})! Ignoring it.", display_name, err);
continue;
},
Ok(sc) => sc,
};
let this_calendar = RemoteCalendar::new(display_name, this_calendar_url, supported_components);
log::info!("Found calendar {}", this_calendar.name());
calendars.insert(this_calendar.id().clone(), Arc::new(Mutex::new(this_calendar)));
}
let mut replies = self.cached_replies.lock().unwrap();
replies.calendars = Some(calendars);
Ok(())
}
}
#[async_trait]
impl CalDavSource<RemoteCalendar> for Client {
/// Return the list of calendars, or fetch from server if not known yet /// Return the list of calendars, or fetch from server if not known yet
pub async fn get_calendars(&mut self) -> Result<HashMap<CalendarId, CachedCalendar>, Box<dyn Error>> { /*
if let Some(c) = &self.calendars { async fn get_calendars(&self) -> Result<&HashMap<CalendarId, RemoteCalendar>, Box<dyn Error>> {
return Ok(c.clone()); let mut replies = self.cached_replies.lock().unwrap();
if let Some(c) = &replies.calendars {
return Ok(c);
} }
let cal_home_set = self.get_cal_home_set().await?; let cal_home_set = self.get_cal_home_set().await?;
@ -210,37 +288,36 @@ impl Client {
}, },
Ok(sc) => sc, Ok(sc) => sc,
}; };
let this_calendar = CachedCalendar::new(display_name, this_calendar_url, supported_components); let this_calendar = RemoteCalendar::new(display_name, this_calendar_url, supported_components);
log::info!("Found calendar {}", this_calendar.name()); log::info!("Found calendar {}", this_calendar.name());
calendars.insert(this_calendar.id().clone(), this_calendar); calendars.insert(this_calendar.id().clone(), this_calendar);
} }
self.calendars = Some(calendars.clone()); replies.calendars = Some(calendars);
Ok(calendars) Ok(&calendars)
}
*/
async fn get_calendars(&self) -> Result<HashMap<CalendarId, Arc<Mutex<RemoteCalendar>>>, Box<dyn Error>> {
self.populate_calendars().await?;
match &self.cached_replies.lock().unwrap().calendars {
Some(cals) => {
return Ok(cals.clone())
},
None => return Err("No calendars available".into())
};
} }
pub async fn get_tasks(&mut self, calendar: &CalendarId) -> Result<(), Box<dyn Error>> {
let method = Method::from_bytes(b"REPORT")
.expect("cannot create REPORT method.");
let res = reqwest::Client::new() async fn get_calendar(&self, id: CalendarId) -> Option<Arc<Mutex<RemoteCalendar>>> {
.request(method, calendar.as_str()) self.cached_replies.lock().unwrap()
.header("Depth", 1) .calendars
.header(CONTENT_TYPE, "application/xml") .as_ref()
.basic_auth(self.username.clone(), Some(self.password.clone())) .and_then(|cals| cals.get(&id))
.body(TASKS_BODY) .map(|cal| cal.clone())
.send()
.await?;
let text = res.text().await?;
let el: Element = text.parse().unwrap();
let responses = find_elems(&el, "response");
for _response in responses {
println!("(a response)\n");
} }
Ok(())
}
} }

View file

@ -63,16 +63,19 @@ where
pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> { pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> {
let last_sync = self.local.get_last_sync(); let last_sync = self.local.get_last_sync();
log::info!("Starting a sync. Last sync was at {:?}", last_sync); log::info!("Starting a sync. Last sync was at {:?}", last_sync);
let cals_server = self.server.get_calendars_mut().await?; let cals_server = self.server.get_calendars().await?;
for (id, cal_server) in cals_server { for (id, mut cal_server) in cals_server {
let cal_local = match self.local.get_calendar_mut(id).await { let mut cal_server = cal_server.lock().unwrap();
let cal_local = match self.local.get_calendar(id).await {
None => { None => {
log::error!("TODO: implement here"); log::error!("TODO: implement here");
continue; continue;
}, },
Some(cal) => cal, Some(cal) => cal,
}; };
let mut cal_local = cal_local.lock().unwrap();
// Pull remote changes from the server // Pull remote changes from the server
let mut tasks_id_to_remove_from_local = match last_sync { let mut tasks_id_to_remove_from_local = match last_sync {
@ -93,7 +96,7 @@ where
tasks_to_add_to_local.push((*new_item).clone()); tasks_to_add_to_local.push((*new_item).clone());
} }
// Even in case of conflicts, "the server always wins", so it is safe to remove tasks from the local cache as soon as now // 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); remove_from_calendar(&tasks_id_to_remove_from_local, &mut *cal_local);
@ -121,9 +124,9 @@ where
tasks_to_add_to_server.push((*new_item).clone()); tasks_to_add_to_server.push((*new_item).clone());
} }
remove_from_calendar(&tasks_id_to_remove_from_server, cal_server); remove_from_calendar(&tasks_id_to_remove_from_server, &mut *cal_server);
move_to_calendar(&mut tasks_to_add_to_local, cal_local); move_to_calendar(&mut tasks_to_add_to_local, &mut *cal_local);
move_to_calendar(&mut tasks_to_add_to_server, cal_server); move_to_calendar(&mut tasks_to_add_to_server, &mut *cal_server);
} }
self.local.update_last_sync(None); self.local.update_last_sync(None);

View file

@ -1,5 +1,6 @@
use std::error::Error; use std::error::Error;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -12,16 +13,9 @@ use crate::calendar::CalendarId;
pub trait CalDavSource<T: PartialCalendar> { pub trait CalDavSource<T: PartialCalendar> {
/// Returns the current calendars that this source contains /// Returns the current calendars that this source contains
/// This function may trigger an update (that can be a long process, or that can even fail, e.g. in case of a remote server) /// This function may trigger an update (that can be a long process, or that can even fail, e.g. in case of a remote server)
async fn get_calendars(&self) -> Result<&HashMap<CalendarId, T>, Box<dyn Error>>; async fn get_calendars(&self) -> Result<HashMap<CalendarId, Arc<Mutex<T>>>, Box<dyn Error>>;
/// Returns the current calendars that this source contains
/// This function may trigger an update (that can be a long process, or that can even fail, e.g. in case of a remote server)
async fn get_calendars_mut(&mut self) -> Result<HashMap<CalendarId, &mut T>, Box<dyn Error>>;
/// Returns the calendar matching the ID /// Returns the calendar matching the ID
async fn get_calendar(&self, id: CalendarId) -> Option<&T>; async fn get_calendar(&self, id: CalendarId) -> Option<Arc<Mutex<T>>>;
/// Returns the calendar matching the ID
async fn get_calendar_mut(&mut self, id: CalendarId) -> Option<&mut T>;
} }
pub trait SyncSlave { pub trait SyncSlave {

View file

@ -1,6 +1,7 @@
///! Some utility functions ///! Some utility functions
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use minidom::Element; use minidom::Element;
@ -56,10 +57,13 @@ pub fn print_xml(element: &Element) {
} }
/// A debug utility that pretty-prints calendars /// A debug utility that pretty-prints calendars
pub fn print_calendar_list<C: CompleteCalendar>(cals: &HashMap<CalendarId, C>) { pub fn print_calendar_list<C>(cals: &HashMap<CalendarId, Arc<Mutex<C>>>)
where
C: CompleteCalendar,
{
for (id, cal) in cals { for (id, cal) in cals {
println!("CAL {}", id); println!("CAL {}", id);
for (_, item) in cal.get_items() { for (_, item) in cal.lock().unwrap().get_items() {
let task = item.unwrap_task(); let task = item.unwrap_task();
let completion = if task.completed() {""} else {" "}; let completion = if task.completed() {""} else {" "};
println!(" {} {}\t{}", completion, task.name(), task.id()); println!(" {} {}\t{}", completion, task.name(), task.id());

View file

@ -1,4 +1,5 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use chrono::{Utc, TimeZone}; use chrono::{Utc, TimeZone};
use url::Url; use url::Url;
@ -20,10 +21,11 @@ async fn test_regular_sync() {
let mut provider = populate_test_provider().await; let mut provider = populate_test_provider().await;
provider.sync().await.unwrap(); provider.sync().await.unwrap();
let cals_server = provider.server().get_calendars().await.unwrap(); let cals_server = provider.server().get_calendars().await.unwrap();
my_tasks::utils::print_calendar_list(&cals_server);
let cals_local = provider.local().get_calendars().await.unwrap(); let cals_local = provider.local().get_calendars().await.unwrap();
my_tasks::utils::print_calendar_list(cals_local); my_tasks::utils::print_calendar_list(&cals_local);
my_tasks::utils::print_calendar_list(cals_server);
assert!(provider.server().has_same_contents_than(provider.local()).await.unwrap()); assert!(provider.server().has_same_contents_than(provider.local()).await.unwrap());
@ -94,12 +96,13 @@ async fn populate_test_provider() -> Provider<Cache, CachedCalendar, Cache, Cach
calendar.add_item(task_l); calendar.add_item(task_l);
calendar.add_item(task_m); calendar.add_item(task_m);
server.add_calendar(calendar.clone()); server.add_calendar(Arc::new(Mutex::new(calendar.clone())));
local.add_calendar(calendar.clone()); local.add_calendar(Arc::new(Mutex::new(calendar.clone())));
// Step 2 // Step 2
// Edit the server calendar // Edit the server calendar
let cal_server = server.get_calendar_mut(cal_id.clone()).await.unwrap(); let cal_server = server.get_calendar(cal_id.clone()).await.unwrap();
let mut cal_server = cal_server.lock().unwrap();
cal_server.delete_item(&task_b_id); cal_server.delete_item(&task_b_id);
@ -126,7 +129,8 @@ async fn populate_test_provider() -> Provider<Cache, CachedCalendar, Cache, Cach
// Step 3 // Step 3
// Edit the local calendar // Edit the local calendar
let cal_local = local.get_calendar_mut(cal_id).await.unwrap(); let cal_local = local.get_calendar(cal_id).await.unwrap();
let mut cal_local = cal_local.lock().unwrap();
cal_local.delete_item(&task_c_id); cal_local.delete_item(&task_c_id);