diff --git a/Cargo.lock b/Cargo.lock index 4fc1cea..ddf7288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1598,7 +1598,7 @@ dependencies = [ [[package]] name = "ical" version = "0.11.0" -source = "git+https://github.com/lennart-k/ical-rs#a911f2d500f9f422934c5ec6afc94586a8814fb5" +source = "git+https://github.com/lennart-k/ical-rs#c5fa2217af23ba27ba80295a2c0eb922f08f6c97" dependencies = [ "chrono-tz", "serde", diff --git a/crates/caldav/src/calendar/methods/get.rs b/crates/caldav/src/calendar/methods/get.rs index d9348b7..9314d5b 100644 --- a/crates/caldav/src/calendar/methods/get.rs +++ b/crates/caldav/src/calendar/methods/get.rs @@ -74,10 +74,10 @@ pub async fn route_get( timezones.extend(object_timezones); ical_calendar_builder = ical_calendar_builder.add_event(event.clone()); } - CalendarObjectComponent::Todo(TodoObject { todo, .. }) => { + CalendarObjectComponent::Todo(TodoObject(todo)) => { ical_calendar_builder = ical_calendar_builder.add_todo(todo.clone()); } - CalendarObjectComponent::Journal(JournalObject { journal, .. }) => { + CalendarObjectComponent::Journal(JournalObject(journal)) => { ical_calendar_builder = ical_calendar_builder.add_journal(journal.clone()); } } diff --git a/crates/caldav/src/calendar_object/methods.rs b/crates/caldav/src/calendar_object/methods.rs index faa98d2..9620b13 100644 --- a/crates/caldav/src/calendar_object/methods.rs +++ b/crates/caldav/src/calendar_object/methods.rs @@ -78,12 +78,13 @@ pub async fn put_event( true }; - let object = match CalendarObject::from_ics(object_id, body) { + let object = match CalendarObject::from_ics(body) { Ok(obj) => obj, Err(_) => { return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); } }; + assert_eq!(object.get_id(), object_id); cal_store .put_object(principal, calendar_id, object, overwrite) .await?; diff --git a/crates/ical/src/address_object.rs b/crates/ical/src/address_object.rs index 64ee409..6e6d3c5 100644 --- a/crates/ical/src/address_object.rs +++ b/crates/ical/src/address_object.rs @@ -95,10 +95,8 @@ impl AddressObject { let uid = format!("{}-anniversary", self.get_id()); let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default(); - Some(CalendarObject::from_ics( - uid.clone(), - format!( - r#"BEGIN:VCALENDAR + Some(CalendarObject::from_ics(format!( + r#"BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//github.com/lennart-k/rustical birthday calendar//EN @@ -116,8 +114,7 @@ DESCRIPTION:💍 {fullname}{year_suffix} END:VALARM END:VEVENT END:VCALENDAR"#, - ), - )?) + ))?) } else { None }, @@ -139,10 +136,8 @@ END:VCALENDAR"#, let uid = format!("{}-birthday", self.get_id()); let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default(); - Some(CalendarObject::from_ics( - uid.clone(), - format!( - r#"BEGIN:VCALENDAR + Some(CalendarObject::from_ics(format!( + r#"BEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:-//github.com/lennart-k/rustical birthday calendar//EN @@ -160,8 +155,7 @@ DESCRIPTION:🎂 {fullname}{year_suffix} END:VALARM END:VEVENT END:VCALENDAR"#, - ), - )?) + ))?) } else { None }, diff --git a/crates/ical/src/icalendar/event.rs b/crates/ical/src/icalendar/event.rs index 1f8efcb..8cc9976 100644 --- a/crates/ical/src/icalendar/event.rs +++ b/crates/ical/src/icalendar/event.rs @@ -6,11 +6,12 @@ use ical::{generator::IcalEvent, parser::Component, property::Property}; use rrule::{RRule, RRuleSet}; use std::{collections::HashMap, str::FromStr}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct EventObject { pub event: IcalEvent, + // If a timezone is None that means that in the VCALENDAR object there's a timezone defined + // with that name but its not from the Olson DB pub timezones: HashMap>, - pub(crate) ics: String, } impl EventObject { @@ -239,11 +240,7 @@ END:VEVENT\r\n", #[test] fn test_expand_recurrence() { - let event = CalendarObject::from_ics( - "318ec6503573d9576818daf93dac07317058d95c".to_string(), - ICS.to_string(), - ) - .unwrap(); + let event = CalendarObject::from_ics(ICS.to_string()).unwrap(); let event = event.event().unwrap(); let events: Vec = event diff --git a/crates/ical/src/icalendar/journal.rs b/crates/ical/src/icalendar/journal.rs index fe9b684..926dd82 100644 --- a/crates/ical/src/icalendar/journal.rs +++ b/crates/ical/src/icalendar/journal.rs @@ -1,7 +1,5 @@ +use derive_more::From; use ical::parser::ical::component::IcalJournal; -#[derive(Debug, Clone)] -pub struct JournalObject { - pub journal: IcalJournal, - pub(crate) ics: String, -} +#[derive(Debug, Clone, From)] +pub struct JournalObject(pub IcalJournal); diff --git a/crates/ical/src/icalendar/object.rs b/crates/ical/src/icalendar/object.rs index ddb4833..f6d4af7 100644 --- a/crates/ical/src/icalendar/object.rs +++ b/crates/ical/src/icalendar/object.rs @@ -5,6 +5,7 @@ use chrono::DateTime; use chrono::Utc; use derive_more::Display; use ical::generator::{Emitter, IcalCalendar}; +use ical::property::Property; use serde::Serialize; use sha2::{Digest, Sha256}; use std::{collections::HashMap, io::BufReader}; @@ -58,15 +59,21 @@ pub enum CalendarObjectComponent { Journal(JournalObject), } -#[derive(Debug, Clone)] -pub struct CalendarObject { - id: String, +impl Default for CalendarObjectComponent { + fn default() -> Self { + Self::Event(EventObject::default()) + } +} + +#[derive(Debug, Clone, Default)] +pub struct CalendarObject { data: CalendarObjectComponent, - cal: IcalCalendar, + properties: Vec, + ics: String, } impl CalendarObject { - pub fn from_ics(object_id: String, ics: String) -> Result { + pub fn from_ics(ics: String) -> Result { let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes())); let cal = parser.next().ok_or(Error::MissingCalendar)??; if parser.next().is_some() { @@ -94,41 +101,23 @@ impl CalendarObject { .map(|timezone| (timezone.get_tzid().to_owned(), (&timezone).try_into().ok())) .collect(); - if let Some(event) = cal.events.first() { - return Ok(CalendarObject { - id: object_id, - cal: cal.clone(), - data: CalendarObjectComponent::Event(EventObject { - event: event.clone(), - timezones, - ics, - }), - }); - } - if let Some(todo) = cal.todos.first() { - return Ok(CalendarObject { - id: object_id, - cal: cal.clone(), - data: CalendarObjectComponent::Todo(TodoObject { - todo: todo.clone(), - ics, - }), - }); - } - if let Some(journal) = cal.journals.first() { - return Ok(CalendarObject { - id: object_id, - cal: cal.clone(), - data: CalendarObjectComponent::Journal(JournalObject { - journal: journal.clone(), - ics, - }), - }); - } + let data = if let Some(event) = cal.events.into_iter().next() { + CalendarObjectComponent::Event(EventObject { event, timezones }) + } else if let Some(todo) = cal.todos.into_iter().next() { + CalendarObjectComponent::Todo(todo.into()) + } else if let Some(journal) = cal.journals.into_iter().next() { + CalendarObjectComponent::Journal(journal.into()) + } else { + return Err(Error::InvalidData( + "iCalendar component type not supported :(".to_owned(), + )); + }; - Err(Error::InvalidData( - "iCalendar component type not supported :(".to_owned(), - )) + Ok(Self { + data, + properties: cal.properties, + ics, + }) } pub fn get_data(&self) -> &CalendarObjectComponent { @@ -136,21 +125,22 @@ impl CalendarObject { } pub fn get_id(&self) -> &str { - &self.id + match &self.data { + CalendarObjectComponent::Todo(todo) => todo.0.get_uid(), + CalendarObjectComponent::Event(event) => event.event.get_uid(), + CalendarObjectComponent::Journal(journal) => journal.0.get_uid(), + } } + pub fn get_etag(&self) -> String { let mut hasher = Sha256::new(); - hasher.update(&self.id); + hasher.update(self.get_id()); hasher.update(self.get_ics()); format!("\"{:x}\"", hasher.finalize()) } pub fn get_ics(&self) -> &str { - match &self.data { - CalendarObjectComponent::Todo(todo) => &todo.ics, - CalendarObjectComponent::Event(event) => &event.ics, - CalendarObjectComponent::Journal(journal) => &journal.ics, - } + &self.ics } pub fn get_component_name(&self) -> &str { @@ -194,8 +184,11 @@ impl CalendarObject { // Only events can be expanded match &self.data { CalendarObjectComponent::Event(event) => { - let mut cal = self.cal.clone(); - cal.events = event.expand_recurrence(start, end)?; + let cal = IcalCalendar { + properties: self.properties.clone(), + events: event.expand_recurrence(start, end)?, + ..Default::default() + }; Ok(cal.generate()) } _ => Ok(self.get_ics().to_string()), diff --git a/crates/ical/src/icalendar/todo.rs b/crates/ical/src/icalendar/todo.rs index 12d9aa1..ce44f43 100644 --- a/crates/ical/src/icalendar/todo.rs +++ b/crates/ical/src/icalendar/todo.rs @@ -1,7 +1,5 @@ +use derive_more::From; use ical::parser::ical::component::IcalTodo; -#[derive(Debug, Clone)] -pub struct TodoObject { - pub todo: IcalTodo, - pub(crate) ics: String, -} +#[derive(Debug, Clone, From)] +pub struct TodoObject(pub IcalTodo); diff --git a/crates/store_sqlite/src/calendar_store.rs b/crates/store_sqlite/src/calendar_store.rs index 5b02786..92e2e9a 100644 --- a/crates/store_sqlite/src/calendar_store.rs +++ b/crates/store_sqlite/src/calendar_store.rs @@ -22,7 +22,17 @@ impl TryFrom for CalendarObject { type Error = rustical_store::Error; fn try_from(value: CalendarObjectRow) -> Result { - Ok(CalendarObject::from_ics(value.id, value.ics)?) + let object = CalendarObject::from_ics(value.ics)?; + if object.get_id() != value.id { + return Err(rustical_store::Error::IcalError( + rustical_ical::Error::InvalidData(format!( + "object_id={} and UID={} don't match", + object.get_id(), + value.id + )), + )); + } + Ok(object) } }