From c91205558e15549fa0e3aef64706613f40e78e10 Mon Sep 17 00:00:00 2001 From: Lennart K <18233294+lennart-k@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:45:34 +0100 Subject: [PATCH] Fix comp-filter --- Cargo.lock | 69 +++++----- .../report/calendar_query/comp_filter.rs | 124 +++++++++++++++++- .../report/calendar_query/prop_filter.rs | 8 +- 3 files changed, 158 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 163449d..6e15871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -595,9 +595,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -1770,8 +1770,8 @@ dependencies = [ [[package]] name = "ical" -version = "0.11.0" -source = "git+https://github.com/lennart-k/ical-rs?branch=dev#ece5b95ddc20f89d14e162aba3a49038f9989701" +version = "0.12.0-dev" +source = "git+https://github.com/lennart-k/ical-rs?branch=dev#5e61c25646c3785448d349e7d18b2833fc483c53" dependencies = [ "chrono", "chrono-tz", @@ -1923,9 +1923,9 @@ checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] name = "insta" -version = "1.46.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5" +checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8" dependencies = [ "console", "once_cell", @@ -1991,9 +1991,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -3262,9 +3262,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.9.0" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -3273,9 +3273,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.9.0" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" dependencies = [ "proc-macro2", "quote", @@ -3286,9 +3286,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.9.0" +version = "8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ "sha2", "walkdir", @@ -3296,9 +3296,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -3653,9 +3653,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "4910321ebe4151be888e35fe062169554e74aad01beafed60410131420ceffbc" dependencies = [ "web-time", "zeroize", @@ -4984,9 +4984,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -4999,9 +4999,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -5012,11 +5012,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -5025,9 +5026,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5035,9 +5036,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -5048,18 +5049,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5426,9 +5427,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" 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 517e486..57d08a6 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 @@ -1,8 +1,9 @@ use crate::calendar::methods::report::calendar_query::{ - TimeRangeElement, prop_filter::PropFilterElement, + TimeRangeElement, + prop_filter::{PropFilterElement, PropFilterable}, }; use ical::{ - component::IcalCalendarObject, + component::{CalendarInnerData, IcalAlarm, IcalCalendarObject, IcalEvent, IcalTodo}, parser::{Component, ical::component::IcalTimeZone}, }; use rustical_xml::XmlDeserialize; @@ -25,7 +26,9 @@ pub struct CompFilterElement { pub(crate) name: String, } -pub trait CompFilterable: Component + Sized { +pub trait CompFilterable: PropFilterable + Sized { + fn get_comp_name(&self) -> &'static str; + fn match_time_range(&self, time_range: &TimeRangeElement) -> bool; fn match_subcomponents(&self, comp_filter: &CompFilterElement) -> bool; @@ -67,7 +70,100 @@ pub trait CompFilterable: Component + Sized { } } +impl CompFilterable for CalendarInnerData { + fn get_comp_name(&self) -> &'static str { + match self { + Self::Event(main, _) => main.get_comp_name(), + Self::Journal(main, _) => main.get_comp_name(), + Self::Todo(main, _) => main.get_comp_name(), + } + } + + fn match_time_range(&self, time_range: &TimeRangeElement) -> bool { + if let Some(start) = &time_range.start + && let Some(last_end) = self.get_last_occurence() + && start.to_utc() > last_end.utc() + { + return false; + } + if let Some(end) = &time_range.end + && let Some(first_start) = self.get_first_occurence() + && end.to_utc() < first_start.utc() + { + return false; + } + true + } + + fn match_subcomponents(&self, comp_filter: &CompFilterElement) -> bool { + match self { + Self::Event(main, overrides) => std::iter::once(main) + .chain(overrides.iter()) + .flat_map(IcalEvent::get_alarms) + .any(|alarm| alarm.matches(comp_filter)), + Self::Todo(main, overrides) => std::iter::once(main) + .chain(overrides.iter()) + .flat_map(IcalTodo::get_alarms) + .any(|alarm| alarm.matches(comp_filter)), + // VJOURNAL has no subcomponents + Self::Journal(_, _) => comp_filter.is_not_defined.is_some(), + } + } +} + +impl PropFilterable for IcalAlarm { + fn get_named_properties<'a>( + &'a self, + name: &'a str, + ) -> impl Iterator { + Component::get_named_properties(self, name) + } +} + +impl CompFilterable for IcalAlarm { + fn get_comp_name(&self) -> &'static str { + Component::get_comp_name(self) + } + + fn match_time_range(&self, _time_range: &TimeRangeElement) -> bool { + true + } + + fn match_subcomponents(&self, comp_filter: &CompFilterElement) -> bool { + comp_filter.is_not_defined.is_some() + } +} + +impl PropFilterable for CalendarInnerData { + #[allow(refining_impl_trait)] + fn get_named_properties<'a>( + &'a self, + name: &'a str, + ) -> Box + 'a> { + // TODO: If we were pedantic, we would have to do recurrence expansion first + // and take into account the overrides :( + match self { + Self::Event(main, _) => Box::new(main.get_named_properties(name)), + Self::Todo(main, _) => Box::new(main.get_named_properties(name)), + Self::Journal(main, _) => Box::new(main.get_named_properties(name)), + } + } +} + +impl PropFilterable for IcalCalendarObject { + fn get_named_properties<'a>( + &'a self, + name: &'a str, + ) -> impl Iterator { + Component::get_named_properties(self, name) + } +} + impl CompFilterable for IcalCalendarObject { + fn get_comp_name(&self) -> &'static str { + Component::get_comp_name(self) + } + fn match_time_range(&self, _time_range: &TimeRangeElement) -> bool { // VCALENDAR has no concept of time range false @@ -78,23 +174,36 @@ impl CompFilterable for IcalCalendarObject { .get_vtimezones() .values() .map(|tz| tz.matches(comp_filter)) - .chain([self.matches(comp_filter)]); + .chain([self.get_inner().matches(comp_filter)]); if comp_filter.is_not_defined.is_some() { - matches.all(|x| x) + matches.all(|x| !x) } else { matches.any(|x| x) } } } +impl PropFilterable for IcalTimeZone { + fn get_named_properties<'a>( + &'a self, + name: &'a str, + ) -> impl Iterator { + Component::get_named_properties(self, name) + } +} + impl CompFilterable for IcalTimeZone { + fn get_comp_name(&self) -> &'static str { + Component::get_comp_name(self) + } fn match_time_range(&self, _time_range: &TimeRangeElement) -> bool { false } - fn match_subcomponents(&self, _comp_filter: &CompFilterElement) -> bool { - true + fn match_subcomponents(&self, comp_filter: &CompFilterElement) -> bool { + // VTIMEZONE has no subcomponents + comp_filter.is_not_defined.is_some() } } @@ -111,6 +220,7 @@ mod tests { const ICS: &str = r"BEGIN:VCALENDAR CALSCALE:GREGORIAN VERSION:2.0 +PRODID:me BEGIN:VTIMEZONE TZID:Europe/Berlin X-LIC-LOCATION:Europe/Berlin diff --git a/crates/caldav/src/calendar/methods/report/calendar_query/prop_filter.rs b/crates/caldav/src/calendar/methods/report/calendar_query/prop_filter.rs index fe99f03..cb7a7f0 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query/prop_filter.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query/prop_filter.rs @@ -1,5 +1,5 @@ use super::{ParamFilterElement, TimeRangeElement}; -use ical::{parser::Component, property::ContentLine, types::CalDateTime}; +use ical::{property::ContentLine, types::CalDateTime}; use rustical_dav::xml::TextMatchElement; use rustical_ical::UtcDateTime; use rustical_xml::XmlDeserialize; @@ -21,6 +21,10 @@ pub struct PropFilterElement { pub(crate) name: String, } +pub trait PropFilterable { + fn get_named_properties<'a>(&'a self, name: &'a str) -> impl Iterator; +} + impl PropFilterElement { #[must_use] pub fn match_property(&self, property: &ContentLine) -> bool { @@ -60,7 +64,7 @@ impl PropFilterElement { true } - pub fn match_component(&self, comp: &impl Component) -> bool { + pub fn match_component(&self, comp: &impl PropFilterable) -> bool { let mut properties = comp.get_named_properties(&self.name); if self.is_not_defined.is_some() { return properties.next().is_none();