From 51ec1d7ff9cc678a0e08b1e221af09fc7d8f4296 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Tue, 21 May 2024 18:09:21 +0200 Subject: [PATCH] calendar-query filter propertiers --- aero-dav/src/caldecoder.rs | 7 +- aero-dav/src/calencoder.rs | 3 - aero-dav/src/caltypes.rs | 1 - aero-proto/src/dav/controller.rs | 120 +++++++++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index 16c9c6c..b4391a4 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -805,7 +805,6 @@ impl QRead for PropFilter { impl QRead for PropFilterRules { async fn qread(xml: &mut Reader) -> Result { - let mut time_range = None; let mut time_or_text = None; let mut param_filter = Vec::new(); @@ -817,7 +816,6 @@ impl QRead for PropFilterRules { return Ok(Self::IsNotDefined); } - xml.maybe_read(&mut time_range, &mut dirty).await?; xml.maybe_read(&mut time_or_text, &mut dirty).await?; xml.maybe_push(&mut param_filter, &mut dirty).await?; @@ -829,10 +827,9 @@ impl QRead for PropFilterRules { } } - match (&time_range, &time_or_text, ¶m_filter[..]) { - (None, None, []) => Err(ParsingError::Recoverable), + match (&time_or_text, ¶m_filter[..]) { + (None, []) => Err(ParsingError::Recoverable), _ => Ok(PropFilterRules::Match(PropFilterMatch { - time_range, time_or_text, param_filter, })), diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs index 06cafd4..4467f7c 100644 --- a/aero-dav/src/calencoder.rs +++ b/aero-dav/src/calencoder.rs @@ -638,9 +638,6 @@ impl QWrite for PropFilterRules { impl QWrite for PropFilterMatch { async fn qwrite(&self, xml: &mut Writer) -> Result<(), QError> { - if let Some(time_range) = &self.time_range { - time_range.qwrite(xml).await?; - } if let Some(time_or_text) = &self.time_or_text { time_or_text.qwrite(xml).await?; } diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs index 7c85642..717086b 100644 --- a/aero-dav/src/caltypes.rs +++ b/aero-dav/src/caltypes.rs @@ -1242,7 +1242,6 @@ pub enum PropFilterRules { } #[derive(Debug, PartialEq, Clone)] pub struct PropFilterMatch { - pub time_range: Option, pub time_or_text: Option, pub param_filter: Vec, } diff --git a/aero-proto/src/dav/controller.rs b/aero-proto/src/dav/controller.rs index 541beb6..a1c2660 100644 --- a/aero-proto/src/dav/controller.rs +++ b/aero-proto/src/dav/controller.rs @@ -365,32 +365,142 @@ fn apply_filter<'a>( Some(cal::CompFilterRules::Matches(m)) => m, }; - let evts = ics + let is_keep = ics .components .iter() - .all(|single_comp| is_component_match(single_comp, matcher)); + .any(|single_comp| is_component_match(single_comp, matcher)); // Object has been kept Some(Ok(single_node)) }) } +fn component_date( + component: &icalendar::parser::Component, + prop: &str, +) -> Option> { + component + .find_prop(prop) + .map(|p| p.val.as_str()) + .map(|raw_dtstart| { + NaiveDateTime::parse_from_str(raw_dtstart, cal::ICAL_DATETIME_FMT) + .ok() + .map(|v| v.and_utc()) + }) + .flatten() +} + +use chrono::NaiveDateTime; fn is_component_match( component: &icalendar::parser::Component, matcher: &cal::CompFilterMatch, ) -> bool { if let Some(time_range) = &matcher.time_range { - todo!(); // check DTSTART and DTEND + let (dtstart, dtend) = match ( + component_date(component, "DTSTART"), + component_date(component, "DTEND"), + ) { + (Some(start), None) => (start, start), + (None, Some(end)) => (end, end), + (Some(start), Some(end)) => (start, end), + _ => return false, + }; + + let is_in_range = match time_range { + cal::TimeRange::OnlyStart(after) => &dtend >= after, + cal::TimeRange::OnlyEnd(before) => &dtstart <= before, + cal::TimeRange::FullRange(after, before) => &dtend >= after && &dtstart <= before, + }; + + if !is_in_range { + return false; + } } if !matcher.prop_filter.iter().all(|single_prop_filter| { - true // check prop filter against component + match ( + &single_prop_filter.additional_rules, + component.find_prop(single_prop_filter.name.0.as_str()), + ) { + (None, Some(_)) | (Some(cal::PropFilterRules::IsNotDefined), None) => true, + (None, None) + | (Some(cal::PropFilterRules::IsNotDefined), Some(_)) + | (Some(cal::PropFilterRules::Match(_)), None) => false, + (Some(cal::PropFilterRules::Match(pattern)), Some(prop)) => { + // check value + match &pattern.time_or_text { + Some(cal::TimeOrText::Time(time_range)) => { + // try parse entry as date + let parsed_date = + match component_date(component, single_prop_filter.name.0.as_str()) { + Some(v) => v, + None => return false, + }; + + // see if entry is in range + let is_in_range = match time_range { + cal::TimeRange::OnlyStart(after) => &parsed_date >= after, + cal::TimeRange::OnlyEnd(before) => &parsed_date <= before, + cal::TimeRange::FullRange(after, before) => { + &parsed_date >= after && &parsed_date <= before + } + }; + if !is_in_range { + return false; + } + + // if you are here, this subcondition is valid + } + Some(cal::TimeOrText::Text(txt_match)) => { + //@FIXME ignoring collation + let is_match = match txt_match.negate_condition { + None | Some(false) => { + prop.val.as_str().contains(txt_match.text.as_str()) + } + Some(true) => !prop.val.as_str().contains(txt_match.text.as_str()), + }; + if !is_match { + return false; + } + } + None => (), // if not filter on value is set, continue + }; + + // check parameters + pattern.param_filter.iter().all(|single_param_filter| { + let maybe_param = prop.params.iter().find(|candidate| { + candidate.key.as_str() == single_param_filter.name.as_str() + }); + + match (maybe_param, &single_param_filter.additional_rules) { + (Some(_), None) => true, + (None, None) => false, + (Some(_), Some(cal::ParamFilterMatch::IsNotDefined)) => false, + (None, Some(cal::ParamFilterMatch::IsNotDefined)) => true, + (None, Some(cal::ParamFilterMatch::Match(_))) => false, + (Some(param), Some(cal::ParamFilterMatch::Match(txt_match))) => { + let param_val = match ¶m.val { + Some(v) => v, + None => return false, + }; + + match txt_match.negate_condition { + None | Some(false) => { + param_val.as_str().contains(txt_match.text.as_str()) + } + Some(true) => !param_val.as_str().contains(txt_match.text.as_str()), + } + } + } + }) + } + } }) { return false; } //component.components.iter().any - matcher.comp_filter.iter().all(|single_comp_filter| { + matcher.comp_filter.iter().any(|single_comp_filter| { true //@TODO find component, find }) }