|
|
|
@ -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(¤t_element, item).unwrap(); |
|
|
|
current_element = find_elem(¤t_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) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
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_calendars(&self) -> Result<HashMap<CalendarId, Arc<Mutex<RemoteCalendar>>>, Box<dyn Error>> { |
|
|
|
.request(method, calendar.as_str()) |
|
|
|
self.populate_calendars().await?; |
|
|
|
.header("Depth", 1) |
|
|
|
|
|
|
|
.header(CONTENT_TYPE, "application/xml") |
|
|
|
match &self.cached_replies.lock().unwrap().calendars { |
|
|
|
.basic_auth(self.username.clone(), Some(self.password.clone())) |
|
|
|
Some(cals) => { |
|
|
|
.body(TASKS_BODY) |
|
|
|
return Ok(cals.clone()) |
|
|
|
.send() |
|
|
|
}, |
|
|
|
.await?; |
|
|
|
None => return Err("No calendars available".into()) |
|
|
|
let text = res.text().await?; |
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let el: Element = text.parse().unwrap(); |
|
|
|
|
|
|
|
let responses = find_elems(&el, "response"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _response in responses { |
|
|
|
async fn get_calendar(&self, id: CalendarId) -> Option<Arc<Mutex<RemoteCalendar>>> { |
|
|
|
println!("(a response)\n"); |
|
|
|
self.cached_replies.lock().unwrap() |
|
|
|
|
|
|
|
.calendars |
|
|
|
|
|
|
|
.as_ref() |
|
|
|
|
|
|
|
.and_then(|cals| cals.get(&id)) |
|
|
|
|
|
|
|
.map(|cal| cal.clone()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|