From ca0d07f16a1b2f214bd46a97d7b232524bf1f4b4 Mon Sep 17 00:00:00 2001 From: daladim Date: Sun, 21 Feb 2021 00:16:40 +0100 Subject: [PATCH] Parsing calendars from server --- Cargo.lock | 102 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/data/calendar.rs | 53 ++++++++++++++++++++++ src/data/client.rs | 80 ++++++++++++++++++++++++--------- src/data/mod.rs | 2 +- src/data/task.rs | 1 + 6 files changed, 218 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 935af7c..d06ecde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,25 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -67,6 +87,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fnv" version = "1.0.7" @@ -174,6 +207,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + [[package]] name = "http" version = "0.2.3" @@ -207,6 +249,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.4" @@ -361,6 +409,9 @@ dependencies = [ name = "my-tasks" version = "0.1.0" dependencies = [ + "bitflags", + "env_logger", + "log", "minidom", "reqwest", "tokio", @@ -395,6 +446,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + [[package]] name = "openssl" version = "0.10.32" @@ -554,6 +611,24 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -707,6 +782,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + [[package]] name = "tinyvec" version = "1.1.1" @@ -972,6 +1065,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index da96375..a17ce71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ uuid = "0.8" reqwest = "0.11" minidom = "0.13" url = "2.2" +bitflags = "1.2" diff --git a/src/data/calendar.rs b/src/data/calendar.rs index e55104c..58163b9 100644 --- a/src/data/calendar.rs +++ b/src/data/calendar.rs @@ -1,14 +1,67 @@ +use std::convert::TryFrom; +use std::error::Error; + +use url::Url; + use crate::data::Task; use crate::data::task::TaskId; +use bitflags::bitflags; + +bitflags! { + pub struct SupportedComponents: u8 { + /// An event, such as a calendar meeting + const Event = 1; + /// A to-do item, such as a reminder + const Todo = 2; + } +} + +impl TryFrom for SupportedComponents { + type Error = Box; + + /// Create an instance from an XML element + fn try_from(element: minidom::Element) -> Result { + if element.name() != "supported-calendar-component-set" { + return Err("Element must be a ".into()); + } + + let mut flags = Self::empty(); + for child in element.children() { + match child.attr("name") { + None => continue, + Some("VEVENT") => flags.insert(Self::Event), + Some("VTODO") => flags.insert(Self::Todo), + Some(other) => { + log::warn!("Unimplemented supported component type: {:?}. Ignoring it", other); + continue + }, + }; + } + + Ok(flags) + } +} + + /// A Caldav Calendar +#[derive(Clone, Debug)] pub struct Calendar { name: String, + url: Url, + supported_components: SupportedComponents, tasks: Vec, } impl Calendar { + pub fn new(name: String, url: Url, supported_components: SupportedComponents) -> Self { + Self { + name, url, supported_components, + tasks: Vec::new(), + } + } + pub fn name(&self) -> &str { &self.name } diff --git a/src/data/client.rs b/src/data/client.rs index f3f3653..9a69ff9 100644 --- a/src/data/client.rs +++ b/src/data/client.rs @@ -3,12 +3,15 @@ //! Some of it comes from https://github.com/marshalshi/caldav-client-rust.git use std::error::Error; +use std::convert::TryFrom; use reqwest::Method; use reqwest::header::CONTENT_TYPE; use minidom::Element; use url::Url; +use crate::data::Calendar; + static DAVCLIENT_BODY: &str = r#" @@ -43,7 +46,7 @@ pub struct Client { principal: Option, calendar_home_set: Option, - calendars_url: Option>, + calendars: Option>, } impl Client { @@ -57,7 +60,7 @@ impl Client { password: password.to_string(), principal: None, calendar_home_set: None, - calendars_url: None, + calendars: None, }) } @@ -122,38 +125,70 @@ impl Client { } /// Return the list of calendars, or fetch from server if not known yet - pub async fn get_calendars_url(&mut self) -> Result, Box> { - if let Some(c) = &self.calendars_url { - return Ok(c.clone()); + pub async fn get_calendars(&mut self) -> Result, Box> { + if let Some(c) = &self.calendars { + return Ok(c.to_vec()); } 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".to_string()); let mut calendars = Vec::new(); for rep in reps { - // TODO checking `displayname` here but may there are better way - let displayname = find_elem(rep, "displayname".to_string()) - .unwrap() - .text(); - if displayname == "" { + let display_name = find_elem(rep, "displayname".to_string()).map(|e| e.text()).unwrap_or("".to_string()); + log::debug!("Considering calendar {}", display_name); + + // We filter out non-calendar items + let resource_types = match find_elem(rep, "resourcetype".to_string()) { + 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; } - // TODO: filter by: - // - // - // - // + // 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".to_string()) { + None => continue, + Some(comps) => comps, + }; + if el_supported_comps.children().count() == 0 { + continue; + } - let href = find_elem(rep, "href".to_string()).unwrap(); - let href_text = href.text(); + let calendar_href = match find_elem(rep, "href".to_string()) { + 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(&href_text); - calendars.push(this_calendar_url); + this_calendar_url.set_path(&calendar_href); + + let supported_components = match crate::data::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 = Calendar::new(display_name, this_calendar_url, supported_components); + log::info!("Found calendar {}", this_calendar.name()); + calendars.push(this_calendar); } + + self.calendars = Some(calendars.clone()); Ok(calendars) } } @@ -208,6 +243,11 @@ mod test { let _ = env_logger::builder().is_test(true).try_init(); let mut client = Client::new(URL, USERNAME, PASSWORD).unwrap(); - client.get_calendars_url().await.unwrap(); + let calendars = client.get_calendars().await.unwrap(); + + println!("Calendars:"); + calendars.iter() + .map(|cal| println!(" {}", cal.name())) + .collect::<()>(); } } diff --git a/src/data/mod.rs b/src/data/mod.rs index cc8e19b..3d0c265 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use std::error::Error; -mod calendar; +pub mod calendar; mod task; mod client; diff --git a/src/data/task.rs b/src/data/task.rs index 5fecc73..02932dc 100644 --- a/src/data/task.rs +++ b/src/data/task.rs @@ -3,6 +3,7 @@ use uuid::Uuid; pub type TaskId = Uuid; /// A to-do task +#[derive(Clone, Debug)] pub struct Task { id: TaskId, name: String,