From 968a5e931c0181cf42cca1dc2151f6de647e96a2 Mon Sep 17 00:00:00 2001 From: Lennart K <18233294+lennart-k@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:14:04 +0100 Subject: [PATCH] Make CalendarObject id an extrinsic property --- crates/caldav/src/calendar/methods/get.rs | 2 +- crates/caldav/src/calendar/methods/import.rs | 5 +- .../methods/report/calendar_multiget.rs | 4 +- .../report/calendar_query/comp_filter.rs | 6 +- .../methods/report/calendar_query/mod.rs | 4 +- .../caldav/src/calendar/methods/report/mod.rs | 7 +- .../methods/report/sync_collection.rs | 5 +- crates/caldav/src/calendar/service.rs | 3 +- crates/caldav/src/calendar_object/methods.rs | 4 +- crates/caldav/src/calendar_object/resource.rs | 3 +- crates/caldav/src/calendar_object/service.rs | 1 + crates/ical/src/address_object.rs | 28 +-- crates/ical/src/icalendar/object.rs | 14 +- crates/ical/tests/test_cal_object.rs | 2 +- crates/store/src/calendar_store.rs | 12 +- crates/store/src/combined_calendar_store.rs | 11 +- .../benches/insert_calendar_object.rs | 15 +- .../addressbook_store/birthday_calendar.rs | 48 ++--- crates/store_sqlite/src/calendar_store.rs | 64 +++--- ...ation_tests__caldav__propfind_depth_1.snap | 188 ++++++++++++++++++ 20 files changed, 317 insertions(+), 109 deletions(-) diff --git a/crates/caldav/src/calendar/methods/get.rs b/crates/caldav/src/calendar/methods/get.rs index de684a0..0161383 100644 --- a/crates/caldav/src/calendar/methods/get.rs +++ b/crates/caldav/src/calendar/methods/get.rs @@ -61,7 +61,7 @@ pub async fn route_get( }); } - for object in &objects { + for (_object_id, object) in &objects { vtimezones.extend(object.get_inner().get_vtimezones()); match object.get_inner().get_inner() { CalendarInnerData::Event(main, overrides) => { diff --git a/crates/caldav/src/calendar/methods/import.rs b/crates/caldav/src/calendar/methods/import.rs index deb3b05..312d1a3 100644 --- a/crates/caldav/src/calendar/methods/import.rs +++ b/crates/caldav/src/calendar/methods/import.rs @@ -82,7 +82,10 @@ pub async fn route_import( let objects = expanded_cals .into_iter() .map(|cal| cal.generate()) - .map(|ics| CalendarObject::from_ics(ics, None)) + .map(|ics| { + CalendarObject::from_ics(ics) + .map(|object| (object.get_inner().get_uid().to_owned(), object)) + }) .collect::, _>>()?; let new_cal = Calendar { principal, diff --git a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs index 2cf61ff..a16c6fa 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -21,7 +21,7 @@ pub async fn get_objects_calendar_multiget( principal: &str, cal_id: &str, store: &C, -) -> Result<(Vec, Vec), Error> { +) -> Result<(Vec<(String, CalendarObject)>, Vec), Error> { let mut result = vec![]; let mut not_found = vec![]; @@ -32,7 +32,7 @@ pub async fn get_objects_calendar_multiget( let filename = filename.trim_start_matches('/'); if let Some(object_id) = filename.strip_suffix(".ics") { match store.get_object(principal, cal_id, object_id, false).await { - Ok(object) => result.push(object), + Ok(object) => result.push((object_id.to_owned(), object)), Err(rustical_store::Error::NotFound) => not_found.push(href.to_string()), Err(err) => return Err(err.into()), } diff --git a/crates/caldav/src/calendar/methods/report/calendar_query/comp_filter.rs b/crates/caldav/src/calendar/methods/report/calendar_query/comp_filter.rs index 5009f0e..47ba74f 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query/comp_filter.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query/comp_filter.rs @@ -167,7 +167,7 @@ END:VCALENDAR"; #[test] fn test_comp_filter_matching() { - let object = CalendarObject::from_ics(ICS.to_string(), None).unwrap(); + let object = CalendarObject::from_ics(ICS.to_string()).unwrap(); let comp_filter = CompFilterElement { is_not_defined: Some(()), @@ -257,7 +257,7 @@ END:VCALENDAR"; } #[test] fn test_comp_filter_time_range() { - let object = CalendarObject::from_ics(ICS.to_string(), None).unwrap(); + let object = CalendarObject::from_ics(ICS.to_string()).unwrap(); let comp_filter = CompFilterElement { is_not_defined: None, @@ -312,7 +312,7 @@ END:VCALENDAR"; #[test] fn test_match_timezone() { - let object = CalendarObject::from_ics(ICS.to_string(), None).unwrap(); + let object = CalendarObject::from_ics(ICS.to_string()).unwrap(); let comp_filter = CompFilterElement { is_not_defined: None, diff --git a/crates/caldav/src/calendar/methods/report/calendar_query/mod.rs b/crates/caldav/src/calendar/methods/report/calendar_query/mod.rs index 6e23e78..890ca4c 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query/mod.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query/mod.rs @@ -16,12 +16,12 @@ pub async fn get_objects_calendar_query( principal: &str, cal_id: &str, store: &C, -) -> Result, Error> { +) -> Result, Error> { let mut objects = store .calendar_query(principal, cal_id, cal_query.into()) .await?; if let Some(filter) = &cal_query.filter { - objects.retain(|object| filter.matches(object)); + objects.retain(|(_, object)| filter.matches(object)); } Ok(objects) } diff --git a/crates/caldav/src/calendar/methods/report/mod.rs b/crates/caldav/src/calendar/methods/report/mod.rs index 4d7ef6b..6d35f1b 100644 --- a/crates/caldav/src/calendar/methods/report/mod.rs +++ b/crates/caldav/src/calendar/methods/report/mod.rs @@ -51,7 +51,7 @@ impl ReportRequest { } fn objects_response( - objects: Vec, + objects: Vec<(String, CalendarObject)>, not_found: Vec, path: &str, principal: &str, @@ -60,11 +60,12 @@ fn objects_response( prop: &PropfindType, ) -> Result, Error> { let mut responses = Vec::new(); - for object in objects { - let path = format!("{}/{}.ics", path, object.get_id()); + for (object_id, object) in objects { + let path = format!("{}/{}.ics", path, &object_id); responses.push( CalendarObjectResource { object, + object_id, principal: principal.to_owned(), } .propfind(&path, prop, None, puri, user)?, diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index 7b70f20..97b6806 100644 --- a/crates/caldav/src/calendar/methods/report/sync_collection.rs +++ b/crates/caldav/src/calendar/methods/report/sync_collection.rs @@ -32,11 +32,12 @@ pub async fn handle_sync_collection( .await?; let mut responses = Vec::new(); - for object in new_objects { - let path = format!("{}/{}.ics", path, object.get_id()); + for (object_id, object) in new_objects { + let path = format!("{}/{}.ics", path, &object_id); responses.push( CalendarObjectResource { object, + object_id, principal: principal.to_owned(), } .propfind(&path, &sync_collection.prop, None, puri, user)?, diff --git a/crates/caldav/src/calendar/service.rs b/crates/caldav/src/calendar/service.rs index ec6384d..515bdd4 100644 --- a/crates/caldav/src/calendar/service.rs +++ b/crates/caldav/src/calendar/service.rs @@ -78,9 +78,10 @@ impl ResourceService for CalendarResourc .get_objects(principal, cal_id) .await? .into_iter() - .map(|object| CalendarObjectResource { + .map(|(object_id, object)| CalendarObjectResource { object, principal: principal.to_owned(), + object_id, }) .collect()) } diff --git a/crates/caldav/src/calendar_object/methods.rs b/crates/caldav/src/calendar_object/methods.rs index f91653c..d0b2661 100644 --- a/crates/caldav/src/calendar_object/methods.rs +++ b/crates/caldav/src/calendar_object/methods.rs @@ -78,12 +78,12 @@ pub async fn put_event( true }; - let Ok(object) = CalendarObject::from_ics(body.clone(), Some(object_id)) else { + let Ok(object) = CalendarObject::from_ics(body.clone()) else { debug!("invalid calendar data:\n{body}"); return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); }; cal_store - .put_object(principal, calendar_id, object, overwrite) + .put_object(principal, calendar_id, (object_id, object), overwrite) .await?; Ok(StatusCode::CREATED.into_response()) diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index e4953ae..bc4e649 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -17,12 +17,13 @@ use rustical_store::auth::Principal; #[derive(Clone, From, Into)] pub struct CalendarObjectResource { pub object: CalendarObject, + pub object_id: String, pub principal: String, } impl ResourceName for CalendarObjectResource { fn get_name(&self) -> String { - format!("{}.ics", self.object.get_id()) + format!("{}.ics", self.object_id) } } diff --git a/crates/caldav/src/calendar_object/service.rs b/crates/caldav/src/calendar_object/service.rs index 8a63e12..5cb3137 100644 --- a/crates/caldav/src/calendar_object/service.rs +++ b/crates/caldav/src/calendar_object/service.rs @@ -67,6 +67,7 @@ impl ResourceService for CalendarObjectResourceService { Ok(CalendarObjectResource { object, principal: principal.to_owned(), + object_id: object_id.to_owned(), }) } diff --git a/crates/ical/src/address_object.rs b/crates/ical/src/address_object.rs index a775da2..e787f00 100644 --- a/crates/ical/src/address_object.rs +++ b/crates/ical/src/address_object.rs @@ -7,7 +7,7 @@ use ical::parser::{ }; use ical::types::CalDate; use sha2::{Digest, Sha256}; -use std::{collections::HashMap, io::BufReader}; +use std::io::BufReader; #[derive(Debug, Clone)] pub struct AddressObject { @@ -93,9 +93,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( - 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 @@ -113,9 +112,7 @@ DESCRIPTION:💍 {fullname}{year_suffix} END:VALARM END:VEVENT END:VCALENDAR", - ), - None, - )?) + ))?) } else { None }, @@ -134,9 +131,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( - 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 @@ -154,9 +150,7 @@ DESCRIPTION:🎂 {fullname}{year_suffix} END:VALARM END:VEVENT END:VCALENDAR", - ), - None, - )?) + ))?) } else { None }, @@ -164,13 +158,13 @@ END:VCALENDAR", } /// Get significant dates associated with this address object - pub fn get_significant_dates(&self) -> Result, Error> { - let mut out = HashMap::new(); + pub fn get_significant_dates(&self) -> Result, Error> { + let mut out = vec![]; if let Some(birthday) = self.get_birthday_object()? { - out.insert("birthday", birthday); + out.push((birthday.get_inner().get_uid().to_owned(), birthday)); } if let Some(anniversary) = self.get_anniversary_object()? { - out.insert("anniversary", anniversary); + out.push((anniversary.get_inner().get_uid().to_owned(), anniversary)); } Ok(out) } diff --git a/crates/ical/src/icalendar/object.rs b/crates/ical/src/icalendar/object.rs index 14a2520..4820ff2 100644 --- a/crates/ical/src/icalendar/object.rs +++ b/crates/ical/src/icalendar/object.rs @@ -8,13 +8,12 @@ use std::io::BufReader; #[derive(Debug, Clone)] pub struct CalendarObject { - id: String, ics: String, inner: IcalCalendarObject, } impl CalendarObject { - pub fn from_ics(ics: String, id: Option) -> Result { + pub fn from_ics(ics: String) -> Result { let mut parser = ical::IcalObjectParser::new(BufReader::new(ics.as_bytes())); let cal = parser.next().ok_or(Error::MissingCalendar)??; if parser.next().is_some() { @@ -23,11 +22,7 @@ impl CalendarObject { )); } - Ok(Self { - id: id.unwrap_or_else(|| cal.get_uid().to_owned()), - ics, - inner: cal, - }) + Ok(Self { ics, inner: cal }) } #[must_use] @@ -35,11 +30,6 @@ impl CalendarObject { &self.inner } - #[must_use] - pub fn get_id(&self) -> &str { - &self.id - } - #[must_use] pub fn get_etag(&self) -> String { let mut hasher = Sha256::new(); diff --git a/crates/ical/tests/test_cal_object.rs b/crates/ical/tests/test_cal_object.rs index 89a5c65..fe63e64 100644 --- a/crates/ical/tests/test_cal_object.rs +++ b/crates/ical/tests/test_cal_object.rs @@ -25,6 +25,6 @@ END:VCALENDAR #[test] fn parse_calendar_object() { - let object = CalendarObject::from_ics(MULTI_VEVENT.to_string(), None).unwrap(); + let object = CalendarObject::from_ics(MULTI_VEVENT.to_string()).unwrap(); object.get_inner().expand_recurrence(None, None).unwrap(); } diff --git a/crates/store/src/calendar_store.rs b/crates/store/src/calendar_store.rs index 0c51909..8778212 100644 --- a/crates/store/src/calendar_store.rs +++ b/crates/store/src/calendar_store.rs @@ -37,7 +37,7 @@ pub trait CalendarStore: Send + Sync + 'static { async fn import_calendar( &self, calendar: Calendar, - objects: Vec, + objects: Vec<(String, CalendarObject)>, merge_existing: bool, ) -> Result<(), Error>; @@ -46,7 +46,7 @@ pub trait CalendarStore: Send + Sync + 'static { principal: &str, cal_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), Error>; + ) -> Result<(Vec<(String, CalendarObject)>, Vec, i64), Error>; /// Since the rules are rather complex this function /// is only meant to do some prefiltering @@ -55,7 +55,7 @@ pub trait CalendarStore: Send + Sync + 'static { principal: &str, cal_id: &str, _query: CalendarQuery, - ) -> Result, Error> { + ) -> Result, Error> { self.get_objects(principal, cal_id).await } @@ -69,7 +69,7 @@ pub trait CalendarStore: Send + Sync + 'static { &self, principal: &str, cal_id: &str, - ) -> Result, Error>; + ) -> Result, Error>; async fn get_object( &self, principal: &str, @@ -81,14 +81,14 @@ pub trait CalendarStore: Send + Sync + 'static { &self, principal: String, cal_id: String, - objects: Vec, + objects: Vec<(String, CalendarObject)>, overwrite: bool, ) -> Result<(), Error>; async fn put_object( &self, principal: String, cal_id: String, - object: CalendarObject, + object: (String, CalendarObject), overwrite: bool, ) -> Result<(), Error> { self.put_objects(principal, cal_id, vec![object], overwrite) diff --git a/crates/store/src/combined_calendar_store.rs b/crates/store/src/combined_calendar_store.rs index a83b7e4..b8639c2 100644 --- a/crates/store/src/combined_calendar_store.rs +++ b/crates/store/src/combined_calendar_store.rs @@ -1,5 +1,6 @@ use crate::CalendarStore; use async_trait::async_trait; +use rustical_ical::CalendarObject; use std::{collections::HashMap, sync::Arc}; pub trait PrefixedCalendarStore: CalendarStore { @@ -88,7 +89,7 @@ impl CalendarStore for CombinedCalendarStore { principal: &str, cal_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), crate::Error> { + ) -> Result<(Vec<(String, CalendarObject)>, Vec, i64), crate::Error> { self.store_for_id(cal_id) .sync_changes(principal, cal_id, synctoken) .await @@ -97,7 +98,7 @@ impl CalendarStore for CombinedCalendarStore { async fn import_calendar( &self, calendar: crate::Calendar, - objects: Vec, + objects: Vec<(String, CalendarObject)>, merge_existing: bool, ) -> Result<(), crate::Error> { self.store_for_id(&calendar.id) @@ -110,7 +111,7 @@ impl CalendarStore for CombinedCalendarStore { principal: &str, cal_id: &str, query: crate::calendar_store::CalendarQuery, - ) -> Result, crate::Error> { + ) -> Result, crate::Error> { self.store_for_id(cal_id) .calendar_query(principal, cal_id, query) .await @@ -141,7 +142,7 @@ impl CalendarStore for CombinedCalendarStore { &self, principal: &str, cal_id: &str, - ) -> Result, crate::Error> { + ) -> Result, crate::Error> { self.store_for_id(cal_id) .get_objects(principal, cal_id) .await @@ -151,7 +152,7 @@ impl CalendarStore for CombinedCalendarStore { &self, principal: String, cal_id: String, - objects: Vec, + objects: Vec<(String, CalendarObject)>, overwrite: bool, ) -> Result<(), crate::Error> { self.store_for_id(&cal_id) diff --git a/crates/store_sqlite/benches/insert_calendar_object.rs b/crates/store_sqlite/benches/insert_calendar_object.rs index 1926075..ab98c5c 100644 --- a/crates/store_sqlite/benches/insert_calendar_object.rs +++ b/crates/store_sqlite/benches/insert_calendar_object.rs @@ -34,10 +34,14 @@ fn benchmark(c: &mut Criterion) { cal_store }); - let object = CalendarObject::from_ics(include_str!("ical_event.ics").to_owned(), None).unwrap(); + let object = CalendarObject::from_ics(include_str!("ical_event.ics").to_owned()).unwrap(); let batch_size = 1000; - let objects: Vec<_> = std::iter::repeat_n(object.clone(), batch_size).collect(); + let objects: Vec<_> = std::iter::repeat_n( + (object.get_inner().get_uid().to_owned(), object.clone()), + batch_size, + ) + .collect(); c.bench_function("put_batch", |b| { b.to_async(&runtime).iter(async || { @@ -54,7 +58,12 @@ fn benchmark(c: &mut Criterion) { // yeet for _ in 0..1000 { cal_store - .put_object("user".to_owned(), "okwow".to_owned(), object.clone(), true) + .put_object( + "user".to_owned(), + "okwow".to_owned(), + (object.get_inner().get_uid().to_owned(), object.clone()), + true, + ) .await .unwrap(); } diff --git a/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs b/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs index 8a73824..6b1bc70 100644 --- a/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs +++ b/crates/store_sqlite/src/addressbook_store/birthday_calendar.rs @@ -8,7 +8,6 @@ use rustical_store::{ }; use sha2::{Digest, Sha256}; use sqlx::{Executor, Sqlite}; -use std::collections::HashMap; use tracing::instrument; pub const BIRTHDAYS_PREFIX: &str = "_birthdays_"; @@ -312,7 +311,7 @@ impl CalendarStore for SqliteAddressbookStore { async fn import_calendar( &self, _calendar: Calendar, - _objects: Vec, + _objects: Vec<(String, CalendarObject)>, _merge_existing: bool, ) -> Result<(), Error> { Err(Error::ReadOnly) @@ -324,17 +323,19 @@ impl CalendarStore for SqliteAddressbookStore { principal: &str, cal_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), Error> { + ) -> Result<(Vec<(String, CalendarObject)>, Vec, i64), Error> { let cal_id = cal_id .strip_prefix(BIRTHDAYS_PREFIX) .ok_or(Error::NotFound)?; let (objects, deleted_objects, new_synctoken) = AddressbookStore::sync_changes(self, principal, cal_id, synctoken).await?; - let objects: Result>, rustical_ical::Error> = objects + let objects = objects .iter() - .map(AddressObject::get_birthday_object) + .map(AddressObject::get_significant_dates) + .collect::, _>>()? + .into_iter() + .flatten() .collect(); - let objects = objects?.into_iter().flatten().collect(); Ok((objects, deleted_objects, new_synctoken)) } @@ -356,22 +357,18 @@ impl CalendarStore for SqliteAddressbookStore { &self, principal: &str, cal_id: &str, - ) -> Result, Error> { + ) -> Result, Error> { let cal_id = cal_id .strip_prefix(BIRTHDAYS_PREFIX) .ok_or(Error::NotFound)?; - let objects: Result>, rustical_ical::Error> = - AddressbookStore::get_objects(self, principal, cal_id) - .await? - .iter() - .map(AddressObject::get_significant_dates) - .collect(); - let objects = objects? + Ok(AddressbookStore::get_objects(self, principal, cal_id) + .await? + .iter() + .map(AddressObject::get_significant_dates) + .collect::, _>>()? .into_iter() - .flat_map(HashMap::into_values) - .collect(); - - Ok(objects) + .flatten() + .collect()) } #[instrument] @@ -386,11 +383,14 @@ impl CalendarStore for SqliteAddressbookStore { .strip_prefix(BIRTHDAYS_PREFIX) .ok_or(Error::NotFound)?; let (addressobject_id, date_type) = object_id.rsplit_once('-').ok_or(Error::NotFound)?; - AddressbookStore::get_object(self, principal, cal_id, addressobject_id, show_deleted) - .await? - .get_significant_dates()? - .remove(date_type) - .ok_or(Error::NotFound) + let addr_object = + AddressbookStore::get_object(self, principal, cal_id, addressobject_id, show_deleted) + .await?; + match date_type { + "birthday" => addr_object.get_birthday_object()?.ok_or(Error::NotFound), + "anniversary" => addr_object.get_anniversary_object()?.ok_or(Error::NotFound), + _ => Err(Error::NotFound), + } } #[instrument] @@ -398,7 +398,7 @@ impl CalendarStore for SqliteAddressbookStore { &self, _principal: String, _cal_id: String, - _objects: Vec, + _objects: Vec<(String, CalendarObject)>, _overwrite: bool, ) -> Result<(), Error> { Err(Error::ReadOnly) diff --git a/crates/store_sqlite/src/calendar_store.rs b/crates/store_sqlite/src/calendar_store.rs index 3141d40..326f1e8 100644 --- a/crates/store_sqlite/src/calendar_store.rs +++ b/crates/store_sqlite/src/calendar_store.rs @@ -21,11 +21,21 @@ struct CalendarObjectRow { uid: String, } +impl TryFrom for (String, CalendarObject) { + type Error = rustical_store::Error; + + fn try_from(value: CalendarObjectRow) -> Result { + let object_id = value.id.clone(); + + Ok((object_id, value.try_into()?)) + } +} + impl TryFrom for CalendarObject { type Error = rustical_store::Error; fn try_from(value: CalendarObjectRow) -> Result { - let object = Self::from_ics(value.ics, Some(value.id))?; + let object = Self::from_ics(value.ics)?; if object.get_inner().get_uid() != value.uid { return Err(rustical_store::Error::IcalError( rustical_ical::Error::InvalidData(format!( @@ -379,7 +389,7 @@ impl SqliteCalendarStore { executor: E, principal: &str, cal_id: &str, - ) -> Result, Error> { + ) -> Result, Error> { sqlx::query_as!( CalendarObjectRow, "SELECT id, uid, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL", @@ -389,7 +399,7 @@ impl SqliteCalendarStore { .fetch_all(executor) .await.map_err(crate::Error::from)? .into_iter() - .map(std::convert::TryInto::try_into) + .map(TryInto::try_into) .collect() } @@ -398,7 +408,7 @@ impl SqliteCalendarStore { principal: &str, cal_id: &str, query: CalendarQuery, - ) -> Result, Error> { + ) -> Result, Error> { // We extend our query interval by one day in each direction since we really don't want to // miss any objects because of timezone differences // I've previously tried NaiveDate::MIN,MAX, but it seems like sqlite cannot handle these @@ -423,7 +433,7 @@ impl SqliteCalendarStore { .await .map_err(crate::Error::from)? .into_iter() - .map(std::convert::TryInto::try_into) + .map(TryInto::try_into) .collect() } @@ -453,14 +463,11 @@ impl SqliteCalendarStore { executor: E, principal: &str, cal_id: &str, + object_id: &str, object: &CalendarObject, overwrite: bool, ) -> Result<(), Error> { - let (object_id, uid, ics) = ( - object.get_id(), - object.get_inner().get_uid(), - object.get_ics(), - ); + let (uid, ics) = (object.get_inner().get_uid(), object.get_ics()); let first_occurence = object .get_inner() @@ -567,7 +574,7 @@ impl SqliteCalendarStore { principal: &str, cal_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), Error> { + ) -> Result<(Vec<(String, CalendarObject)>, Vec, i64), Error> { struct Row { object_id: String, synctoken: i64, @@ -594,7 +601,7 @@ impl SqliteCalendarStore { for Row { object_id, .. } in changes { match Self::_get_object(&mut *conn, principal, cal_id, &object_id, false).await { - Ok(object) => objects.push(object), + Ok(object) => objects.push((object_id, object)), Err(rustical_store::Error::NotFound) => deleted_objects.push(object_id), Err(err) => return Err(err), } @@ -679,7 +686,7 @@ impl CalendarStore for SqliteCalendarStore { async fn import_calendar( &self, calendar: Calendar, - objects: Vec, + objects: Vec<(String, CalendarObject)>, merge_existing: bool, ) -> Result<(), Error> { let mut tx = self @@ -702,15 +709,23 @@ impl CalendarStore for SqliteCalendarStore { } let mut sync_token = None; - for object in objects { - Self::_put_object(&mut *tx, &calendar.principal, &calendar.id, &object, false).await?; + for (object_id, object) in objects { + Self::_put_object( + &mut *tx, + &calendar.principal, + &calendar.id, + &object_id, + &object, + false, + ) + .await?; sync_token = Some( Self::log_object_operation( &mut tx, &calendar.principal, &calendar.id, - object.get_id(), + &object_id, ChangeOperation::Add, ) .await?, @@ -736,7 +751,7 @@ impl CalendarStore for SqliteCalendarStore { principal: &str, cal_id: &str, query: CalendarQuery, - ) -> Result, Error> { + ) -> Result, Error> { Self::_calendar_query(&self.db, principal, cal_id, query).await } @@ -767,7 +782,7 @@ impl CalendarStore for SqliteCalendarStore { &self, principal: &str, cal_id: &str, - ) -> Result, Error> { + ) -> Result, Error> { Self::_get_objects(&self.db, principal, cal_id).await } @@ -787,7 +802,7 @@ impl CalendarStore for SqliteCalendarStore { &self, principal: String, cal_id: String, - objects: Vec, + objects: Vec<(String, CalendarObject)>, overwrite: bool, ) -> Result<(), Error> { let mut tx = self @@ -803,18 +818,21 @@ impl CalendarStore for SqliteCalendarStore { } let mut sync_token = None; - for object in objects { + for (object_id, object) in objects { sync_token = Some( Self::log_object_operation( &mut tx, &principal, &cal_id, - object.get_id(), + &object_id, ChangeOperation::Add, ) .await?, ); - Self::_put_object(&mut *tx, &principal, &cal_id, &object, overwrite).await?; + Self::_put_object( + &mut *tx, &principal, &cal_id, &object_id, &object, overwrite, + ) + .await?; } tx.commit().await.map_err(crate::Error::from)?; @@ -892,7 +910,7 @@ impl CalendarStore for SqliteCalendarStore { principal: &str, cal_id: &str, synctoken: i64, - ) -> Result<(Vec, Vec, i64), Error> { + ) -> Result<(Vec<(String, CalendarObject)>, Vec, i64), Error> { Self::_sync_changes(&self.db, principal, cal_id, synctoken).await } diff --git a/src/integration_tests/caldav/snapshots/rustical__integration_tests__caldav__propfind_depth_1.snap b/src/integration_tests/caldav/snapshots/rustical__integration_tests__caldav__propfind_depth_1.snap index dfabdac..7e33986 100644 --- a/src/integration_tests/caldav/snapshots/rustical__integration_tests__caldav__propfind_depth_1.snap +++ b/src/integration_tests/caldav/snapshots/rustical__integration_tests__caldav__propfind_depth_1.snap @@ -50,4 +50,192 @@ expression: body HTTP/1.1 200 OK + + /caldav/principal/user/calendar/ + + + #00FF00 + Description + BEGIN:VCALENDAR +PRODID:-//github.com/lennart-k/vzic-rs//RustiCal Calendar server//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Berlin +LAST-MODIFIED:20250723T190331Z +X-LIC-LOCATION:Europe/Berlin +X-PROLEPTIC-TZNAME:LMT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+005328 +TZOFFSETTO:+0100 +DTSTART:18930401T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19160430T230000 +RDATE:19400401T020000 +RDATE:19430329T020000 +RDATE:19460414T020000 +RDATE:19470406T030000 +RDATE:19480418T020000 +RDATE:19490410T020000 +RDATE:19800406T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19161001T010000 +RDATE:19421102T030000 +RDATE:19431004T030000 +RDATE:19441002T030000 +RDATE:19451118T030000 +RDATE:19461007T030000 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19170416T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3MO;UNTIL=19180415T010000Z +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19170917T030000 +RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3MO;UNTIL=19180916T010000Z +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19440403T020000 +RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1MO;UNTIL=19450402T010000Z +END:DAYLIGHT +BEGIN:DAYLIGHT +TZNAME:CEMT +TZOFFSETFROM:+0200 +TZOFFSETTO:+0300 +DTSTART:19450524T020000 +RDATE:19470511T030000 +END:DAYLIGHT +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0300 +TZOFFSETTO:+0200 +DTSTART:19450924T030000 +RDATE:19470629T030000 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0100 +TZOFFSETTO:+0100 +DTSTART:19460101T000000 +RDATE:19800101T000000 +END:STANDARD +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19471005T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19491002T010000Z +END:STANDARD +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19800928T030000 +RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU;UNTIL=19950924T010000Z +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +END:VCALENDAR + + + https://www.iana.org/time-zones + + Europe/Berlin + 0 + + + + + + + + + + i;ascii-casemap + i;octet + + 10000000 + + + + + + + + + + + + + + + + + + -2621430101T000000Z + +2621421231T235959Z + github.com/lennart-k/rustical/ns/0 + github.com/lennart-k/rustical/ns/0 + + + + [PUSH_TOPIC] + + + 1 + + + 1 + + + + + + + Calendar + + /caldav/principal/user/ + + + + + + + + /caldav/principal/user/ + + + HTTP/1.1 200 OK + +