/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use super::{ ArchivedCalendarEventData, ArchivedTimezone, CalendarEventData, Timezone, alarm::{CalendarAlarm, ExpandAlarm}, }; use crate::calendar::{ComponentTimeRange, alarm::CalendarAlarmType}; use calcard::{ common::timezone::Tz, icalendar::{ICalendar, ICalendarComponentType, dates::TimeOrDelta}, }; use compact_str::ToCompactString; use indexmap::IndexMap; use store::{ ahash::{AHashMap, RandomState}, write::{key::KeySerializer, now}, }; const MAX_TIME_SPAN: i64 = u32::MAX as i64; impl CalendarEventData { pub fn new( ical: ICalendar, default_tz: Tz, max_expansions: usize, next_email_alarm: &mut Option, ) -> Self { let mut ranges = TimeRanges::default(); let now = now() as i64; let expanded = ical.expand_dates(default_tz, max_expansions); let mut groups: IndexMap<(u16, u16, u16, i32), Vec, RandomState> = IndexMap::with_capacity_and_hasher(16, RandomState::default()); let mut alarms = AHashMap::with_capacity(16); for event in expanded.events { let start_naive = event.start.naive_local(); let start_tz = event.start.timezone().as_id(); let start_timestamp_utc = event.start.timestamp(); let start_timestamp_naive = start_naive.and_utc().timestamp(); let (end_timestamp_utc, end_timestamp_naive, end_tz) = match event.end { TimeOrDelta::Time(time) => { let end_naive = time.naive_local(); let end_timestamp_utc = time.timestamp(); let end_timestamp_naive = end_naive.and_utc().timestamp(); ( end_timestamp_utc, end_timestamp_naive, time.timezone().as_id(), ) } TimeOrDelta::Delta(delta) => { let delta = delta.num_seconds(); ( start_timestamp_utc + delta, start_timestamp_naive + delta, start_tz, ) } }; // Expand alarms let mut min = std::cmp::min(start_timestamp_utc, end_timestamp_utc); let mut max = std::cmp::max(start_timestamp_utc, end_timestamp_utc); for alarm in alarms.entry(event.comp_id).or_insert_with(|| { ical.component_by_id(event.comp_id) .map_or(&[][..], |c| c.component_ids.as_slice()) .iter() .filter_map(|alarm_id| { ical.component_by_id(*alarm_id).and_then(|alarm| { if alarm.component_type == ICalendarComponentType::VAlarm { alarm.expand_alarm(*alarm_id as u16, event.comp_id as u16) } else { None } }) }) .collect::>() }) { if let Some(alarm_time) = alarm .delta .to_timestamp(start_timestamp_utc, end_timestamp_utc, default_tz) { if alarm_time < min { min = alarm_time; } if alarm_time > max { max = alarm_time; } if alarm_time > now && next_email_alarm .as_ref() .is_none_or(|next| alarm_time < next.alarm_time) { *next_email_alarm = Some(CalendarAlarm { alarm_id: alarm.id, event_id: alarm.parent_id, alarm_time, typ: if alarm.is_email_alert { CalendarAlarmType::Email { event_start: start_timestamp_naive, event_end: end_timestamp_naive, event_start_tz: start_tz, event_end_tz: end_tz, } } else { CalendarAlarmType::Display { recurrence_id: if ical.components[alarm.parent_id as usize] .is_recurrent_or_override() { start_timestamp_naive.into() } else { None }, } }, }); } } } ranges.update_base_offset(start_timestamp_naive, end_timestamp_naive); ranges.update_utc_min_max(min, max); groups .entry(( start_tz, end_tz, event.comp_id as u16, (end_timestamp_naive - start_timestamp_naive) .clamp(i32::MIN as i64, i32::MAX as i64) as i32, )) .or_default() .push(start_timestamp_naive); } let mut events = Vec::with_capacity(groups.len()); for ((start_tz, end_tz, id, duration), mut instances) in groups { instances.sort_unstable(); instances.truncate(instances.partition_point(|instance| { instance.saturating_sub(ranges.base_offset) <= MAX_TIME_SPAN })); let instances = match instances.len() { 0 => continue, 1 => KeySerializer::new(std::mem::size_of::()) .write_leb128((instances[0] - ranges.base_offset) as u32) .finalize(), len => { // Bitpack instances let mut instance_offsets = Vec::with_capacity(len); for instance in instances { debug_assert!(instance >= ranges.base_offset); instance_offsets.push((instance - ranges.base_offset) as u32); } KeySerializer::new(instance_offsets.len() * std::mem::size_of::()) .bitpack_sorted(&instance_offsets) .finalize() } }; events.push(ComponentTimeRange { id, start_tz, end_tz, duration, instances: instances.into_boxed_slice(), }); } if !expanded.errors.is_empty() { trc::event!( Calendar(trc::CalendarEvent::RuleExpansionError), Reason = expanded .errors .into_iter() .map(|e| e.error.to_compact_string()) .collect::>(), Details = ical.to_string(), Limit = max_expansions, ); } CalendarEventData { event: ical, time_ranges: events.into_boxed_slice(), alarms: alarms .into_values() .flatten() .collect::>() .into_boxed_slice(), base_offset: ranges.base_offset, base_time_utc: (ranges.min_time_utc - ranges.base_offset).clamp(0, MAX_TIME_SPAN) as u32, duration: (ranges.max_time_utc - ranges.min_time_utc).clamp(0, MAX_TIME_SPAN) as u32, } } pub fn event_range(&self) -> Option<(i64, u32)> { if self.base_offset != 0 { Some((self.base_offset + self.base_time_utc as i64, self.duration)) } else { None } } } #[derive(Default, Debug)] struct TimeRanges { max_time_utc: i64, min_time_utc: i64, base_offset: i64, } impl TimeRanges { pub fn update_base_offset(&mut self, t1: i64, t2: i64) { let offset = std::cmp::min(t1, t2); if offset < self.base_offset || self.base_offset == 0 { self.base_offset = offset; } } pub fn update_utc_min_max(&mut self, min: i64, max: i64) { if min < self.min_time_utc || self.min_time_utc == 0 { self.min_time_utc = min; } if max > self.max_time_utc { self.max_time_utc = max; } if min < self.base_offset || self.base_offset == 0 { self.base_offset = min; } } } impl ArchivedCalendarEventData { pub fn event_range(&self) -> Option<(i64, u32)> { if self.base_offset != 0 { Some(( self.base_offset.to_native() + self.base_time_utc.to_native() as i64, self.duration.to_native(), )) } else { None } } pub fn event_range_start(&self) -> i64 { self.base_offset.to_native() + self.base_time_utc.to_native() as i64 } pub fn event_range_end(&self) -> i64 { self.base_offset.to_native() + self.base_time_utc.to_native() as i64 + self.duration.to_native() as i64 } } impl CalendarEventData { pub fn event_range_start(&self) -> i64 { self.base_offset + self.base_time_utc as i64 } pub fn event_range_end(&self) -> i64 { self.base_offset + self.base_time_utc as i64 + self.duration as i64 } } impl Timezone { pub fn tz(&self) -> Option { match self { Timezone::IANA(iana) => Tz::from_id(*iana), Timezone::Custom(icalendar) => icalendar .timezones() .filter_map(|t| t.timezone().map(|x| x.1)) .next(), Timezone::Default => None, } } } impl ArchivedTimezone { pub fn tz(&self) -> Option { match self { ArchivedTimezone::IANA(iana) => Tz::from_id(iana.to_native()), ArchivedTimezone::Custom(icalendar) => icalendar .timezones() .filter_map(|t| t.timezone().map(|x| x.1)) .next(), ArchivedTimezone::Default => None, } } }