kitchen-freezer/src/client.rs

248 lines
8.1 KiB
Rust
Raw Normal View History

2021-02-22 00:13:29 +01:00
//! This module provides a client to connect to a CalDAV server
2021-02-20 00:10:05 +01:00
use std::error::Error;
2021-02-21 00:16:40 +01:00
use std::convert::TryFrom;
2021-03-01 23:39:16 +01:00
use std::sync::Mutex;
2021-02-20 00:10:05 +01:00
use reqwest::Method;
use reqwest::header::CONTENT_TYPE;
use minidom::Element;
use url::Url;
2021-03-01 23:39:16 +01:00
use async_trait::async_trait;
2021-02-20 00:10:05 +01:00
2021-02-21 00:29:22 +01:00
use crate::utils::{find_elem, find_elems};
2021-03-01 23:39:16 +01:00
use crate::calendar::cached_calendar::CachedCalendar;
use crate::traits::PartialCalendar;
use crate::traits::CalDavSource;
2021-02-21 00:16:40 +01:00
2021-02-20 00:10:05 +01:00
static DAVCLIENT_BODY: &str = r#"
<d:propfind xmlns:d="DAV:">
<d:prop>
<d:current-user-principal />
</d:prop>
</d:propfind>
"#;
static HOMESET_BODY: &str = r#"
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" >
<d:self/>
<d:prop>
<c:calendar-home-set />
</d:prop>
</d:propfind>
"#;
2021-02-20 00:46:20 +01:00
static CAL_BODY: &str = r#"
<d:propfind xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" >
<d:prop>
<d:displayname />
<d:resourcetype />
<c:supported-calendar-component-set />
</d:prop>
</d:propfind>
"#;
2021-02-21 23:29:21 +01:00
static TASKS_BODY: &str = r#"
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag/>
<C:calendar-data/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VTODO">
<C:prop-filter name="COMPLETED">
<C:is-not-defined/>
</C:prop-filter>
<C:prop-filter name="STATUS">
<C:text-match
negate-condition="yes">CANCELLED</C:text-match>
</C:prop-filter>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>
"#;
2021-02-21 23:30:09 +01:00
2021-02-28 18:02:01 +01:00
/// A CalDAV source that fetches its data from a CalDAV server
2021-02-20 00:10:05 +01:00
pub struct Client {
url: Url,
username: String,
password: String,
principal: Option<Url>,
calendar_home_set: Option<Url>,
2021-03-01 23:39:16 +01:00
calendars: Option<Vec<CachedCalendar>>,
2021-02-20 00:10:05 +01:00
}
2021-02-18 12:02:04 +01:00
impl Client {
2021-02-20 17:58:46 +01:00
/// Create a client. This does not start a connection
2021-02-20 00:10:05 +01:00
pub fn new<S: AsRef<str>, T: ToString, U: ToString>(url: S, username: T, password: U) -> Result<Self, Box<dyn Error>> {
let url = Url::parse(url.as_ref())?;
Ok(Self{
url,
username: username.to_string(),
password: password.to_string(),
principal: None,
calendar_home_set: None,
2021-02-21 00:16:40 +01:00
calendars: None,
2021-02-20 00:10:05 +01:00
})
}
2021-02-20 00:46:20 +01:00
async fn sub_request(&self, url: &Url, body: String, depth: u32) -> Result<String, Box<dyn Error>> {
2021-02-20 00:10:05 +01:00
let method = Method::from_bytes(b"PROPFIND")
.expect("cannot create PROPFIND method.");
let res = reqwest::Client::new()
2021-02-20 00:18:51 +01:00
.request(method, url.as_str())
2021-02-20 00:46:20 +01:00
.header("Depth", depth)
2021-02-20 00:10:05 +01:00
.header(CONTENT_TYPE, "application/xml")
.basic_auth(self.username.clone(), Some(self.password.clone()))
2021-02-20 00:18:51 +01:00
.body(body)
2021-02-20 00:10:05 +01:00
.send()
.await?;
let text = res.text().await?;
2021-02-20 00:18:51 +01:00
Ok(text)
}
2021-02-20 00:10:05 +01:00
2021-02-20 00:46:20 +01:00
async fn sub_request_and_process(&self, url: &Url, body: String, items: &[&str]) -> Result<String, Box<dyn Error>> {
let text = self.sub_request(url, body, 0).await?;
let mut current_element: &Element = &text.parse().unwrap();
items.iter()
.map(|item| {
2021-02-21 00:26:54 +01:00
current_element = find_elem(&current_element, item).unwrap();
2021-02-20 00:46:20 +01:00
})
.collect::<()>();
Ok(current_element.text())
}
2021-02-20 00:18:51 +01:00
/// Return the Principal URL, or fetch it from server if not known yet
async fn get_principal(&mut self) -> Result<Url, Box<dyn Error>> {
if let Some(p) = &self.principal {
return Ok(p.clone());
}
2021-02-20 00:10:05 +01:00
2021-02-20 00:46:20 +01:00
let href = self.sub_request_and_process(&self.url, DAVCLIENT_BODY.into(), &["current-user-principal", "href"]).await?;
2021-02-20 00:10:05 +01:00
let mut principal_url = self.url.clone();
2021-02-20 00:18:51 +01:00
principal_url.set_path(&href);
2021-02-20 00:10:05 +01:00
self.principal = Some(principal_url.clone());
2021-02-21 00:14:55 +01:00
log::debug!("Principal URL is {}", href);
2021-02-20 00:10:05 +01:00
return Ok(principal_url);
}
/// 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>> {
if let Some(h) = &self.calendar_home_set {
return Ok(h.clone());
}
let principal_url = self.get_principal().await?;
2021-02-20 00:46:20 +01:00
let href = self.sub_request_and_process(&principal_url, HOMESET_BODY.into(), &["calendar-home-set", "href"]).await?;
2021-02-20 00:10:05 +01:00
let mut chs_url = self.url.clone();
2021-02-20 00:18:51 +01:00
chs_url.set_path(&href);
2021-02-20 00:10:05 +01:00
self.calendar_home_set = Some(chs_url.clone());
2021-02-21 00:14:55 +01:00
log::debug!("Calendar home set URL is {:?}", chs_url.path());
2021-02-20 00:10:05 +01:00
Ok(chs_url)
}
2021-02-20 00:46:20 +01:00
2021-02-20 17:59:12 +01:00
/// Return the list of calendars, or fetch from server if not known yet
2021-03-01 23:39:16 +01:00
pub async fn get_calendars(&mut self) -> Result<Vec<CachedCalendar>, Box<dyn Error>> {
2021-02-21 00:16:40 +01:00
if let Some(c) = &self.calendars {
return Ok(c.to_vec());
2021-02-20 17:59:12 +01:00
}
2021-02-20 00:46:20 +01:00
let cal_home_set = self.get_cal_home_set().await?;
let text = self.sub_request(&cal_home_set, CAL_BODY.into(), 1).await?;
2021-02-21 00:16:40 +01:00
2021-02-20 00:46:20 +01:00
let root: Element = text.parse().unwrap();
2021-02-21 00:26:54 +01:00
let reps = find_elems(&root, "response");
2021-02-20 17:59:12 +01:00
let mut calendars = Vec::new();
2021-02-20 00:46:20 +01:00
for rep in reps {
2021-02-21 00:26:54 +01:00
let display_name = find_elem(rep, "displayname").map(|e| e.text()).unwrap_or("<no name>".to_string());
2021-02-21 00:16:40 +01:00
log::debug!("Considering calendar {}", display_name);
// We filter out non-calendar items
2021-02-21 00:26:54 +01:00
let resource_types = match find_elem(rep, "resourcetype") {
2021-02-21 00:16:40 +01:00
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 {
2021-02-20 00:46:20 +01:00
continue;
}
2021-02-21 00:16:40 +01:00
// We filter out the root calendar collection, that has an empty supported-calendar-component-set
2021-02-21 00:26:54 +01:00
let el_supported_comps = match find_elem(rep, "supported-calendar-component-set") {
2021-02-21 00:16:40 +01:00
None => continue,
Some(comps) => comps,
};
if el_supported_comps.children().count() == 0 {
continue;
}
2021-02-20 00:46:20 +01:00
2021-02-21 00:26:54 +01:00
let calendar_href = match find_elem(rep, "href") {
2021-02-21 00:16:40 +01:00
None => {
log::warn!("Calendar {} has no URL! Ignoring it.", display_name);
continue;
},
Some(h) => h.text(),
};
2021-02-20 00:46:20 +01:00
2021-02-20 17:59:12 +01:00
let mut this_calendar_url = self.url.clone();
2021-02-21 00:16:40 +01:00
this_calendar_url.set_path(&calendar_href);
2021-02-22 00:13:29 +01:00
let supported_components = match crate::calendar::SupportedComponents::try_from(el_supported_comps.clone()) {
2021-02-21 00:16:40 +01:00
Err(err) => {
log::warn!("Calendar {} has invalid supported components ({})! Ignoring it.", display_name, err);
continue;
},
Ok(sc) => sc,
};
2021-03-01 23:39:16 +01:00
let this_calendar = CachedCalendar::new(display_name, this_calendar_url, supported_components);
2021-02-21 00:16:40 +01:00
log::info!("Found calendar {}", this_calendar.name());
calendars.push(this_calendar);
2021-02-20 17:59:12 +01:00
}
2021-02-21 00:16:40 +01:00
self.calendars = Some(calendars.clone());
2021-02-20 17:59:12 +01:00
Ok(calendars)
2021-02-20 00:46:20 +01:00
}
2021-02-21 23:29:21 +01:00
2021-02-21 23:30:09 +01:00
pub async fn get_tasks(&mut self, calendar: &Url) -> Result<(), Box<dyn Error>> {
2021-02-21 23:29:21 +01:00
let method = Method::from_bytes(b"REPORT")
.expect("cannot create REPORT method.");
let res = reqwest::Client::new()
.request(method, calendar.as_str())
.header("Depth", 1)
.header(CONTENT_TYPE, "application/xml")
.basic_auth(self.username.clone(), Some(self.password.clone()))
.body(TASKS_BODY)
.send()
.await?;
let text = res.text().await?;
let el: Element = text.parse().unwrap();
let responses = find_elems(&el, "response");
for response in responses {
2021-02-21 23:41:35 +01:00
println!("(a response)\n");
2021-02-21 23:29:21 +01:00
}
Ok(())
}
2021-02-20 00:10:05 +01:00
}