diff --git a/Cargo.lock b/Cargo.lock index 82b4e93..b4efa34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1598,11 +1598,11 @@ dependencies = [ [[package]] name = "ical" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7cab7543a8b7729a19e2c04309f902861293dcdae6558dfbeb634454d279f6" +source = "git+https://github.com/lennart-k/ical-rs#44209d8fbdde7a8f9ec42e90a91a90b2b46ed1f0" dependencies = [ + "chrono-tz", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0f14dac..436506d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,11 @@ strum = "0.27" strum_macros = "0.27" serde_json = { version = "1.0", features = ["raw_value"] } sqlx-sqlite = { version = "0.8", features = ["bundled"] } -ical = { version = "0.11", features = ["generator", "serde"] } +ical = { git = "https://github.com/lennart-k/ical-rs", features = [ + "generator", + "serde", + "chrono-tz", +] } toml = "0.9" tower = "0.5" tower-http = { version = "0.6", features = [ diff --git a/crates/caldav/src/calendar/methods/get.rs b/crates/caldav/src/calendar/methods/get.rs index 2d30422..d9348b7 100644 --- a/crates/caldav/src/calendar/methods/get.rs +++ b/crates/caldav/src/calendar/methods/get.rs @@ -63,7 +63,6 @@ pub async fn route_get( params: None, }); } - let mut ical_calendar = ical_calendar_builder.build(); for object in &objects { match object.get_data() { @@ -73,17 +72,21 @@ pub async fn route_get( .. }) => { timezones.extend(object_timezones); - ical_calendar.events.push(event.clone()); + ical_calendar_builder = ical_calendar_builder.add_event(event.clone()); } CalendarObjectComponent::Todo(TodoObject { todo, .. }) => { - ical_calendar.todos.push(todo.clone()); + ical_calendar_builder = ical_calendar_builder.add_todo(todo.clone()); } CalendarObjectComponent::Journal(JournalObject { journal, .. }) => { - ical_calendar.journals.push(journal.clone()); + ical_calendar_builder = ical_calendar_builder.add_journal(journal.clone()); } } } + let ical_calendar = ical_calendar_builder + .build() + .map_err(|parser_error| Error::IcalError(parser_error.into()))?; + let mut resp = Response::builder().status(StatusCode::OK); let hdrs = resp.headers_mut().unwrap(); hdrs.typed_insert(ContentType::from_str("text/calendar").unwrap()); diff --git a/crates/ical/src/icalendar/event.rs b/crates/ical/src/icalendar/event.rs index f989232..1f8efcb 100644 --- a/crates/ical/src/icalendar/event.rs +++ b/crates/ical/src/icalendar/event.rs @@ -1,18 +1,15 @@ use crate::Error; -use crate::{CalDateTime, ComponentMut, parse_duration}; +use crate::{CalDateTime, parse_duration}; use chrono::{DateTime, Duration, Utc}; -use ical::{ - generator::IcalEvent, - parser::{Component, ical::component::IcalTimeZone}, - property::Property, -}; +use ical::parser::ComponentMut; +use ical::{generator::IcalEvent, parser::Component, property::Property}; use rrule::{RRule, RRuleSet}; use std::{collections::HashMap, str::FromStr}; #[derive(Debug, Clone)] pub struct EventObject { pub event: IcalEvent, - pub timezones: HashMap, + pub timezones: HashMap>, pub(crate) ics: String, } @@ -128,7 +125,7 @@ impl EventObject { } else { date.format() }; - let mut ev = self.event.clone(); + let mut ev = self.event.clone().mutable(); ev.remove_property("RRULE"); ev.remove_property("RDATE"); ev.remove_property("EXDATE"); @@ -163,7 +160,7 @@ impl EventObject { params: dtstart_prop.params, }); } - events.push(ev); + events.push(ev.verify()?); } Ok(events) } else { diff --git a/crates/ical/src/icalendar/object.rs b/crates/ical/src/icalendar/object.rs index fb772d0..ddb4833 100644 --- a/crates/ical/src/icalendar/object.rs +++ b/crates/ical/src/icalendar/object.rs @@ -4,10 +4,7 @@ use crate::Error; use chrono::DateTime; use chrono::Utc; use derive_more::Display; -use ical::{ - generator::{Emitter, IcalCalendar}, - parser::{Component, ical::component::IcalTimeZone}, -}; +use ical::generator::{Emitter, IcalCalendar}; use serde::Serialize; use sha2::{Digest, Sha256}; use std::{collections::HashMap, io::BufReader}; @@ -90,15 +87,11 @@ impl CalendarObject { )); } - let timezones: HashMap = cal + let timezones: HashMap> = cal .timezones .clone() .into_iter() - .filter_map(|timezone| { - let timezone_prop = timezone.get_property("TZID")?.to_owned(); - let tzid = timezone_prop.value?; - Some((tzid, timezone)) - }) + .map(|timezone| (timezone.get_tzid().to_owned(), (&timezone).try_into().ok())) .collect(); if let Some(event) = cal.events.first() { @@ -161,11 +154,7 @@ impl CalendarObject { } pub fn get_component_name(&self) -> &str { - match self.data { - CalendarObjectComponent::Todo(_) => "VTODO", - CalendarObjectComponent::Event(_) => "VEVENT", - CalendarObjectComponent::Journal(_) => "VJOURNAL", - } + self.get_object_type().as_str() } pub fn get_object_type(&self) -> CalendarObjectType { diff --git a/crates/ical/src/lib.rs b/crates/ical/src/lib.rs index 32d7221..b7c97e6 100644 --- a/crates/ical/src/lib.rs +++ b/crates/ical/src/lib.rs @@ -1,6 +1,3 @@ -mod property_ext; -pub use property_ext::*; - mod timestamp; mod timezone; pub use timestamp::*; diff --git a/crates/ical/src/property_ext.rs b/crates/ical/src/property_ext.rs deleted file mode 100644 index 3f3c134..0000000 --- a/crates/ical/src/property_ext.rs +++ /dev/null @@ -1,46 +0,0 @@ -use ical::{generator::IcalEvent, property::Property}; - -pub trait IcalProperty { - fn get_param(&self, name: &str) -> Option>; - fn get_value_type(&self) -> Option<&str>; - fn get_tzid(&self) -> Option<&str>; -} - -impl IcalProperty for ical::property::Property { - fn get_param(&self, name: &str) -> Option> { - self.params - .as_ref()? - .iter() - .find(|(key, _)| name == key) - .map(|(_, value)| value.iter().map(String::as_str).collect()) - } - - fn get_value_type(&self) -> Option<&str> { - self.get_param("VALUE") - .and_then(|params| params.into_iter().next()) - } - - fn get_tzid(&self) -> Option<&str> { - self.get_param("TZID") - .and_then(|params| params.into_iter().next()) - } -} - -pub trait ComponentMut { - fn remove_property(&mut self, name: &str); - fn set_property(&mut self, prop: Property); - fn push_property(&mut self, prop: Property); -} - -impl ComponentMut for IcalEvent { - fn remove_property(&mut self, name: &str) { - self.properties.retain(|prop| prop.name != name); - } - fn set_property(&mut self, prop: Property) { - self.remove_property(&prop.name); - self.push_property(prop); - } - fn push_property(&mut self, prop: Property) { - self.properties.push(prop); - } -} diff --git a/crates/ical/src/timestamp.rs b/crates/ical/src/timestamp.rs index e2d9f4d..4f04b86 100644 --- a/crates/ical/src/timestamp.rs +++ b/crates/ical/src/timestamp.rs @@ -1,12 +1,8 @@ use super::timezone::CalTimezone; -use crate::IcalProperty; use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; use chrono_tz::Tz; use derive_more::derive::Deref; -use ical::{ - parser::{Component, ical::component::IcalTimeZone}, - property::Property, -}; +use ical::property::Property; use lazy_static::lazy_static; use rustical_xml::{ValueDeserialize, ValueSerialize}; use std::{borrow::Cow, collections::HashMap, ops::Add}; @@ -136,7 +132,7 @@ impl Add for CalDateTime { impl CalDateTime { pub fn parse_prop( prop: &Property, - timezones: &HashMap, + timezones: &HashMap>, ) -> Result { let prop_value = prop .value @@ -145,28 +141,9 @@ impl CalDateTime { "empty property".to_owned(), ))?; - // Use the TZID parameter from the property - let timezone = if let Some(tzid) = prop.get_tzid() { + let timezone = if let Some(tzid) = prop.get_param("TZID") { if let Some(timezone) = timezones.get(tzid) { - // X-LIC-LOCATION is often used to refer to a standardised timezone from the Olson - // database - if let Some(olson_name) = timezone - .get_property("X-LIC-LOCATION") - .map(|prop| prop.value.to_owned()) - .unwrap_or_default() - { - if let Ok(tz) = olson_name.parse::() { - Some(tz) - } else { - return Err(CalDateTimeError::InvalidOlson(olson_name)); - } - } else { - // If the TZID matches a name from the Olson database (e.g. Europe/Berlin) we - // guess that we can just use it - tzid.parse::().ok() - // TODO: If None: Too bad, we need to manually parse it - // For now it's just treated as localtime - } + timezone.to_owned() } else { // TZID refers to timezone that does not exist return Err(CalDateTimeError::InvalidTZID(tzid.to_string()));