Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
299 lines
10 KiB
Rust
299 lines
10 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* 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<CalendarAlarm>,
|
|
) -> 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<i64>, 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::<Vec<_>>()
|
|
}) {
|
|
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::<u32>())
|
|
.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::<u32>())
|
|
.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::<Vec<_>>(),
|
|
Details = ical.to_string(),
|
|
Limit = max_expansions,
|
|
);
|
|
}
|
|
|
|
CalendarEventData {
|
|
event: ical,
|
|
time_ranges: events.into_boxed_slice(),
|
|
alarms: alarms
|
|
.into_values()
|
|
.flatten()
|
|
.collect::<Vec<_>>()
|
|
.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<Tz> {
|
|
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<Tz> {
|
|
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,
|
|
}
|
|
}
|
|
}
|