Import upstream v0.16.22, stripped
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.
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
Alarm, AlarmDelta, ArchivedAlarmDelta, ArchivedCalendarEventData, expand::resolve_local,
|
||||
};
|
||||
use calcard::{
|
||||
common::timezone::Tz,
|
||||
icalendar::{
|
||||
ICalendarComponent, ICalendarParameterName, ICalendarParameterValue, ICalendarProperty,
|
||||
ICalendarRelated, ICalendarValue,
|
||||
},
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use store::write::bitpack::BitpackIterator;
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct CalendarAlarm {
|
||||
pub alarm_id: u16,
|
||||
pub event_id: u16,
|
||||
pub alarm_time: i64,
|
||||
pub typ: CalendarAlarmType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum CalendarAlarmType {
|
||||
Email {
|
||||
event_start: i64,
|
||||
event_start_tz: u16,
|
||||
event_end: i64,
|
||||
event_end_tz: u16,
|
||||
},
|
||||
Display {
|
||||
recurrence_id: Option<i64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEventData {
|
||||
pub fn next_alarm(&self, start_time: i64, default_tz: Tz) -> Option<CalendarAlarm> {
|
||||
if self.alarms.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let base_offset = self.base_offset.to_native();
|
||||
let mut next_alarm: Option<CalendarAlarm> = None;
|
||||
|
||||
'outer: for range in self.time_ranges.iter() {
|
||||
let comp_id = range.id.to_native();
|
||||
let Some(alarm) = self.alarms.iter().find(|a| a.parent_id == comp_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let instances = range.instances.as_ref();
|
||||
let (offset_or_count, bytes_read) = instances.read_leb128::<u32>()?;
|
||||
|
||||
let duration = range.duration.to_native() as i64;
|
||||
let mut start_tz = Tz::from_id(range.start_tz.to_native())?;
|
||||
let mut end_tz = Tz::from_id(range.end_tz.to_native())?;
|
||||
|
||||
if start_tz.is_floating() && !default_tz.is_floating() {
|
||||
start_tz = default_tz;
|
||||
}
|
||||
if end_tz.is_floating() && !default_tz.is_floating() {
|
||||
end_tz = default_tz;
|
||||
}
|
||||
|
||||
if instances.len() > bytes_read {
|
||||
// Recurring event
|
||||
let unpacker =
|
||||
BitpackIterator::from_bytes_and_offset(instances, bytes_read, offset_or_count);
|
||||
for start_offset in unpacker {
|
||||
let start_date_naive = start_offset as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
let (Some(start), Some(end)) = (
|
||||
resolve_local(start_tz, start_date_naive),
|
||||
resolve_local(end_tz, end_date_naive),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(alarm_time) = alarm.delta.to_timestamp(start, end, default_tz)
|
||||
&& alarm_time > start_time
|
||||
&& next_alarm
|
||||
.as_ref()
|
||||
.is_none_or(|next| alarm_time < next.alarm_time)
|
||||
{
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
alarm_id: alarm.id.to_native(),
|
||||
event_id: alarm.parent_id.to_native(),
|
||||
alarm_time,
|
||||
typ: if alarm.is_email_alert {
|
||||
CalendarAlarmType::Email {
|
||||
event_start: start_date_naive,
|
||||
event_start_tz: start_tz.as_id(),
|
||||
event_end: end_date_naive,
|
||||
event_end_tz: end_tz.as_id(),
|
||||
}
|
||||
} else {
|
||||
let comp =
|
||||
&self.event.components[alarm.parent_id.to_native() as usize];
|
||||
|
||||
CalendarAlarmType::Display {
|
||||
recurrence_id: if comp.is_recurrent_or_override() {
|
||||
start_date_naive.into()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single event
|
||||
let start_date_naive = offset_or_count as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
let (Some(start), Some(end)) = (
|
||||
resolve_local(start_tz, start_date_naive),
|
||||
resolve_local(end_tz, end_date_naive),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(alarm_time) = alarm.delta.to_timestamp(start, end, default_tz)
|
||||
&& alarm_time > start_time
|
||||
&& next_alarm
|
||||
.as_ref()
|
||||
.is_none_or(|next| alarm_time < next.alarm_time)
|
||||
{
|
||||
next_alarm = Some(CalendarAlarm {
|
||||
alarm_id: alarm.id.to_native(),
|
||||
event_id: alarm.parent_id.to_native(),
|
||||
alarm_time,
|
||||
typ: if alarm.is_email_alert {
|
||||
CalendarAlarmType::Email {
|
||||
event_start: start_date_naive,
|
||||
event_start_tz: start_tz.as_id(),
|
||||
event_end: end_date_naive,
|
||||
event_end_tz: end_tz.as_id(),
|
||||
}
|
||||
} else {
|
||||
let comp = &self.event.components[alarm.parent_id.to_native() as usize];
|
||||
|
||||
CalendarAlarmType::Display {
|
||||
recurrence_id: if comp.is_recurrent_or_override() {
|
||||
start_date_naive.into()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next_alarm
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExpandAlarm {
|
||||
fn expand_alarm(&self, id: u16, parent_id: u16) -> Option<Alarm>;
|
||||
}
|
||||
|
||||
impl ExpandAlarm for ICalendarComponent {
|
||||
fn expand_alarm(&self, id: u16, parent_id: u16) -> Option<Alarm> {
|
||||
let mut trigger = None;
|
||||
let mut is_email_alert = false;
|
||||
|
||||
for entry in self.entries.iter() {
|
||||
match &entry.name {
|
||||
ICalendarProperty::Trigger => {
|
||||
let mut tz = None;
|
||||
let mut trigger_start = true;
|
||||
|
||||
for param in entry.params.iter() {
|
||||
match (¶m.name, ¶m.value) {
|
||||
(
|
||||
ICalendarParameterName::Related,
|
||||
ICalendarParameterValue::Related(related),
|
||||
) => {
|
||||
trigger_start = matches!(related, ICalendarRelated::Start);
|
||||
}
|
||||
(
|
||||
ICalendarParameterName::Tzid,
|
||||
ICalendarParameterValue::Text(tz_id),
|
||||
) => {
|
||||
tz = Tz::from_str(tz_id).ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
trigger = match entry.values.first()? {
|
||||
ICalendarValue::PartialDateTime(dt) => {
|
||||
let tz = tz.unwrap_or(Tz::Floating);
|
||||
|
||||
dt.to_date_time_with_tz(tz).map(|dt| {
|
||||
let timestamp = dt.timestamp();
|
||||
if !dt.timezone().is_floating() {
|
||||
AlarmDelta::FixedUtc(timestamp)
|
||||
} else {
|
||||
AlarmDelta::FixedFloating(timestamp)
|
||||
}
|
||||
})
|
||||
}
|
||||
ICalendarValue::Duration(duration) => {
|
||||
if trigger_start {
|
||||
Some(AlarmDelta::Start(duration.as_seconds()))
|
||||
} else {
|
||||
Some(AlarmDelta::End(duration.as_seconds()))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
ICalendarProperty::Action => {
|
||||
is_email_alert = is_email_alert
|
||||
|| entry
|
||||
.values
|
||||
.first()
|
||||
.and_then(|v| v.as_text())
|
||||
.is_some_and(|v| v.eq_ignore_ascii_case("email"));
|
||||
}
|
||||
ICalendarProperty::Summary | ICalendarProperty::Description => {
|
||||
is_email_alert = is_email_alert
|
||||
|| entry
|
||||
.values
|
||||
.first()
|
||||
.and_then(|v| v.as_text())
|
||||
.is_some_and(|v| v.contains("@email"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
trigger.map(|delta| Alarm {
|
||||
id,
|
||||
parent_id,
|
||||
delta,
|
||||
is_email_alert,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AlarmDelta {
|
||||
pub fn to_timestamp(&self, start: i64, end: i64, default_tz: Tz) -> Option<i64> {
|
||||
match self {
|
||||
AlarmDelta::Start(delta) => Some(start + delta),
|
||||
AlarmDelta::End(delta) => Some(end + delta),
|
||||
AlarmDelta::FixedUtc(timestamp) => Some(*timestamp),
|
||||
AlarmDelta::FixedFloating(timestamp) => resolve_local(default_tz, *timestamp),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedAlarmDelta {
|
||||
pub fn to_timestamp(&self, start: i64, end: i64, default_tz: Tz) -> Option<i64> {
|
||||
match self {
|
||||
ArchivedAlarmDelta::Start(delta) => Some(start + delta.to_native()),
|
||||
ArchivedAlarmDelta::End(delta) => Some(end + delta.to_native()),
|
||||
ArchivedAlarmDelta::FixedUtc(timestamp) => Some(timestamp.to_native()),
|
||||
ArchivedAlarmDelta::FixedFloating(timestamp) => {
|
||||
resolve_local(default_tz, timestamp.to_native())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::ArchivedCalendarEventData;
|
||||
use crate::calendar::CalendarEventData;
|
||||
use ahash::AHashSet;
|
||||
use calcard::{
|
||||
common::{DateTimeResult, timezone::Tz},
|
||||
icalendar::{ArchivedICalendarComponent, ICalendarComponent, ICalendarProperty},
|
||||
};
|
||||
use chrono::{DateTime, TimeZone};
|
||||
use std::str::FromStr;
|
||||
use store::write::bitpack::BitpackIterator;
|
||||
use types::TimeRange;
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
|
||||
const RECURRENCE_KEY_EPOCH: i64 = -2208988800;
|
||||
const RECURRENCE_KEY_GRANULARITY: i64 = 60;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct RecurrenceKey(u32);
|
||||
|
||||
impl RecurrenceKey {
|
||||
pub fn from_recurrence_id(recurrence_id_naive: i64) -> Option<Self> {
|
||||
u32::try_from(
|
||||
recurrence_id_naive
|
||||
.checked_sub(RECURRENCE_KEY_EPOCH)?
|
||||
.div_euclid(RECURRENCE_KEY_GRANULARITY),
|
||||
)
|
||||
.ok()?
|
||||
.checked_add(1)
|
||||
.map(RecurrenceKey)
|
||||
}
|
||||
|
||||
pub fn from_prefix(prefix: u32) -> Option<Self> {
|
||||
(prefix != 0).then_some(RecurrenceKey(prefix))
|
||||
}
|
||||
|
||||
pub fn prefix(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RecurrenceId {
|
||||
pub utc: i64,
|
||||
pub naive: i64,
|
||||
}
|
||||
|
||||
pub trait ComponentRecurrenceId {
|
||||
fn recurrence_id(&self, fallback_tz: Tz) -> Option<RecurrenceId>;
|
||||
}
|
||||
|
||||
impl ComponentRecurrenceId for ArchivedICalendarComponent {
|
||||
fn recurrence_id(&self, fallback_tz: Tz) -> Option<RecurrenceId> {
|
||||
let entry = self.property(&ICalendarProperty::RecurrenceId)?;
|
||||
resolve_recurrence_id(
|
||||
entry.tz_id(),
|
||||
entry
|
||||
.values
|
||||
.first()?
|
||||
.as_partial_date_time()?
|
||||
.to_date_time()?,
|
||||
fallback_tz,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentRecurrenceId for ICalendarComponent {
|
||||
fn recurrence_id(&self, fallback_tz: Tz) -> Option<RecurrenceId> {
|
||||
let entry = self.property(&ICalendarProperty::RecurrenceId)?;
|
||||
resolve_recurrence_id(
|
||||
entry.tz_id(),
|
||||
entry
|
||||
.values
|
||||
.first()?
|
||||
.as_partial_date_time()?
|
||||
.to_date_time()?,
|
||||
fallback_tz,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_recurrence_id(
|
||||
tz_id: Option<&str>,
|
||||
date_time: DateTimeResult,
|
||||
fallback_tz: Tz,
|
||||
) -> Option<RecurrenceId> {
|
||||
let tz = tz_id
|
||||
.and_then(|tz_id| Tz::from_str(tz_id).ok())
|
||||
.unwrap_or(fallback_tz);
|
||||
let date_time = date_time.to_date_time_with_tz(tz)?.with_timezone(&tz);
|
||||
|
||||
Some(RecurrenceId {
|
||||
utc: date_time.timestamp(),
|
||||
naive: date_time.naive_local().and_utc().timestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CalendarEventExpansion {
|
||||
pub comp_id: u32,
|
||||
pub own_recurrence_id: Option<RecurrenceId>,
|
||||
pub start: i64,
|
||||
pub end: i64,
|
||||
pub start_naive: i64,
|
||||
}
|
||||
|
||||
impl CalendarEventExpansion {
|
||||
pub fn recurrence_id(&self) -> RecurrenceId {
|
||||
self.own_recurrence_id.unwrap_or(RecurrenceId {
|
||||
utc: self.start,
|
||||
naive: self.start_naive,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn recurrence_key(&self) -> Option<RecurrenceKey> {
|
||||
RecurrenceKey::from_recurrence_id(self.recurrence_id().naive)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEventData {
|
||||
pub fn expand(&self, default_tz: Tz, limit: TimeRange) -> Option<Vec<CalendarEventExpansion>> {
|
||||
let mut expansion = Vec::with_capacity(self.time_ranges.len());
|
||||
let base_offset = self.base_offset.to_native();
|
||||
|
||||
'outer: for (range_index, range) in self.time_ranges.iter().enumerate() {
|
||||
let instances = range.instances.as_ref();
|
||||
let (offset_or_count, bytes_read) = instances.read_leb128::<u32>()?;
|
||||
|
||||
let comp_id = range.id.to_native() as u32;
|
||||
let component = self.event.components.get(comp_id as usize)?;
|
||||
let duration = range.duration.to_native() as i64;
|
||||
let component_tz = Tz::from_id(range.start_tz.to_native())?;
|
||||
let mut own_recurrence_id = self
|
||||
.time_ranges
|
||||
.iter()
|
||||
.take(range_index)
|
||||
.all(|prior| prior.id != range.id)
|
||||
.then(|| component.recurrence_id(component_tz))
|
||||
.flatten();
|
||||
let mut start_tz = component_tz;
|
||||
let mut end_tz = Tz::from_id(range.end_tz.to_native())?;
|
||||
let is_todo = component.component_type.is_todo();
|
||||
|
||||
if start_tz.is_floating() && !default_tz.is_floating() {
|
||||
start_tz = default_tz;
|
||||
}
|
||||
if end_tz.is_floating() && !default_tz.is_floating() {
|
||||
end_tz = default_tz;
|
||||
}
|
||||
|
||||
if instances.len() > bytes_read {
|
||||
let unpacker =
|
||||
BitpackIterator::from_bytes_and_offset(instances, bytes_read, offset_or_count);
|
||||
for start_offset in unpacker {
|
||||
let own_recurrence_id = own_recurrence_id.take();
|
||||
let start_date_naive = start_offset as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
let (Some(start), Some(end)) = (
|
||||
resolve_local(start_tz, start_date_naive),
|
||||
resolve_local(end_tz, end_date_naive),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if limit.is_in_range(is_todo, start, end) {
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
own_recurrence_id,
|
||||
start,
|
||||
end,
|
||||
start_naive: start_date_naive,
|
||||
});
|
||||
} else if start > limit.end {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let start_date_naive = offset_or_count as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
if let (Some(start), Some(end)) = (
|
||||
resolve_local(start_tz, start_date_naive),
|
||||
resolve_local(end_tz, end_date_naive),
|
||||
) && limit.is_in_range(is_todo, start, end)
|
||||
{
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
own_recurrence_id,
|
||||
start,
|
||||
end,
|
||||
start_naive: start_date_naive,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(expansion)
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventData {
|
||||
pub fn component_tz(&self, comp_id: u32) -> Option<Tz> {
|
||||
self.time_ranges
|
||||
.iter()
|
||||
.find(|range| range.id as u32 == comp_id)
|
||||
.and_then(|range| Tz::from_id(range.start_tz))
|
||||
}
|
||||
|
||||
pub fn expand_from_ids(
|
||||
&self,
|
||||
keys: &mut AHashSet<RecurrenceKey>,
|
||||
default_tz: Tz,
|
||||
) -> Option<Vec<CalendarEventExpansion>> {
|
||||
let mut expansion = Vec::with_capacity(keys.len());
|
||||
let base_offset = self.base_offset;
|
||||
|
||||
for (range_index, range) in self.time_ranges.iter().enumerate() {
|
||||
let instances = range.instances.as_ref();
|
||||
let (offset_or_count, bytes_read) = instances.read_leb128::<u32>()?;
|
||||
let comp_id = range.id as u32;
|
||||
let component = self.event.components.get(comp_id as usize)?;
|
||||
let duration = range.duration as i64;
|
||||
let component_tz = Tz::from_id(range.start_tz)?;
|
||||
let mut own_recurrence_id = self
|
||||
.time_ranges
|
||||
.iter()
|
||||
.take(range_index)
|
||||
.all(|prior| prior.id != range.id)
|
||||
.then(|| component.recurrence_id(component_tz))
|
||||
.flatten();
|
||||
let mut start_tz = component_tz;
|
||||
let mut end_tz = Tz::from_id(range.end_tz)?;
|
||||
|
||||
if start_tz.is_floating() && !default_tz.is_floating() {
|
||||
start_tz = default_tz;
|
||||
}
|
||||
if end_tz.is_floating() && !default_tz.is_floating() {
|
||||
end_tz = default_tz;
|
||||
}
|
||||
|
||||
let mut push_instance = |own_recurrence_id: Option<RecurrenceId>, start_offset: u32| {
|
||||
let start_date_naive = start_offset as i64 + base_offset;
|
||||
let recurrence_id_naive =
|
||||
own_recurrence_id.map_or(start_date_naive, |recurrence_id| recurrence_id.naive);
|
||||
if RecurrenceKey::from_recurrence_id(recurrence_id_naive)
|
||||
.is_none_or(|key| !keys.contains(&key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
if let (Some(start), Some(end)) = (
|
||||
resolve_local(start_tz, start_date_naive),
|
||||
resolve_local(end_tz, end_date_naive),
|
||||
) {
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
own_recurrence_id,
|
||||
start,
|
||||
end,
|
||||
start_naive: start_date_naive,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if instances.len() > bytes_read {
|
||||
let unpacker =
|
||||
BitpackIterator::from_bytes_and_offset(instances, bytes_read, offset_or_count);
|
||||
for start_offset in unpacker {
|
||||
push_instance(own_recurrence_id.take(), start_offset);
|
||||
}
|
||||
} else {
|
||||
push_instance(own_recurrence_id, offset_or_count);
|
||||
}
|
||||
}
|
||||
|
||||
keys.retain(|key| {
|
||||
!expansion
|
||||
.iter()
|
||||
.any(|expansion| expansion.recurrence_key() == Some(*key))
|
||||
});
|
||||
|
||||
Some(expansion)
|
||||
}
|
||||
|
||||
pub fn expand_single(&self, comp_id: u32, default_tz: Tz) -> Option<CalendarEventExpansion> {
|
||||
let range = self.time_ranges.iter().find(|r| r.id as u32 == comp_id)?;
|
||||
let instances = range.instances.as_ref();
|
||||
let (offset_or_count, bytes_read) = instances.read_leb128::<u32>()?;
|
||||
let component_tz = Tz::from_id(range.start_tz)?;
|
||||
let own_recurrence_id = self
|
||||
.event
|
||||
.components
|
||||
.get(comp_id as usize)
|
||||
.and_then(|component| component.recurrence_id(component_tz));
|
||||
let mut start_tz = component_tz;
|
||||
let mut end_tz = Tz::from_id(range.end_tz)?;
|
||||
|
||||
if start_tz.is_floating() && !default_tz.is_floating() {
|
||||
start_tz = default_tz;
|
||||
}
|
||||
if end_tz.is_floating() && !default_tz.is_floating() {
|
||||
end_tz = default_tz;
|
||||
}
|
||||
let start_offset = if instances.len() > bytes_read {
|
||||
let mut unpacker =
|
||||
BitpackIterator::from_bytes_and_offset(instances, bytes_read, offset_or_count);
|
||||
unpacker.next()?
|
||||
} else {
|
||||
offset_or_count
|
||||
};
|
||||
let start_date_naive = start_offset as i64 + self.base_offset;
|
||||
let end_date_naive = start_date_naive + range.duration as i64;
|
||||
let start = resolve_local(start_tz, start_date_naive)?;
|
||||
let end = resolve_local(end_tz, end_date_naive)?;
|
||||
|
||||
Some(CalendarEventExpansion {
|
||||
comp_id,
|
||||
own_recurrence_id,
|
||||
start,
|
||||
end,
|
||||
start_naive: start_date_naive,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarEventExpansion {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
comp_id: u32::MAX,
|
||||
own_recurrence_id: None,
|
||||
start: i64::MAX,
|
||||
end: i64::MAX,
|
||||
start_naive: i64::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_local(tz: Tz, naive_secs: i64) -> Option<i64> {
|
||||
tz.from_local_datetime(&DateTime::from_timestamp(naive_secs, 0)?.naive_local())
|
||||
.earliest()
|
||||
.map(|dt| dt.timestamp())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use calcard::{Entry, Parser};
|
||||
use chrono::NaiveDate;
|
||||
|
||||
fn naive(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> i64 {
|
||||
NaiveDate::from_ymd_opt(year, month, day)
|
||||
.and_then(|date| date.and_hms_opt(hour, minute, second))
|
||||
.map(|date_time| date_time.and_utc().timestamp())
|
||||
.expect("valid date")
|
||||
}
|
||||
|
||||
fn key(year: i32, month: u32, day: u32, hour: u32, minute: u32) -> RecurrenceKey {
|
||||
RecurrenceKey::from_recurrence_id(naive(year, month, day, hour, minute, 0))
|
||||
.expect("representable recurrence id")
|
||||
}
|
||||
|
||||
fn event_data(ical: &str) -> CalendarEventData {
|
||||
let entry = Parser::new(ical).entry();
|
||||
let Entry::ICalendar(ical) = entry else {
|
||||
panic!("failed to parse iCalendar: {entry:?}");
|
||||
};
|
||||
CalendarEventData::new(ical, Tz::UTC, 1000, &mut None)
|
||||
}
|
||||
|
||||
fn expand_key(data: &CalendarEventData, key: RecurrenceKey) -> Vec<(u32, i64)> {
|
||||
let mut keys = AHashSet::from_iter([key]);
|
||||
data.expand_from_ids(&mut keys, Tz::UTC)
|
||||
.expect("expansion")
|
||||
.into_iter()
|
||||
.map(|expansion| (expansion.comp_id, expansion.start_naive))
|
||||
.collect()
|
||||
}
|
||||
|
||||
const MASTER: &str = concat!(
|
||||
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Test//EN\r\n",
|
||||
"BEGIN:VEVENT\r\nUID:[email protected]\r\nDTSTAMP:20270101T000000Z\r\n",
|
||||
"DTSTART:20270301T090000Z\r\nDTEND:20270301T100000Z\r\n",
|
||||
"RRULE:FREQ=WEEKLY;COUNT=5\r\nSUMMARY:Weekly\r\nEND:VEVENT\r\n",
|
||||
);
|
||||
|
||||
const OVERRIDE: &str = concat!(
|
||||
"BEGIN:VEVENT\r\nUID:[email protected]\r\nDTSTAMP:20270101T000000Z\r\n",
|
||||
"RECURRENCE-ID:20270308T090000Z\r\nDTSTART:20270308T140000Z\r\n",
|
||||
"DTEND:20270308T150000Z\r\nSUMMARY:Moved\r\nEND:VEVENT\r\n",
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn recurrence_key_encoding() {
|
||||
assert_eq!(
|
||||
RecurrenceKey::from_recurrence_id(RECURRENCE_KEY_EPOCH),
|
||||
Some(RecurrenceKey(1))
|
||||
);
|
||||
assert_eq!(
|
||||
RecurrenceKey::from_recurrence_id(RECURRENCE_KEY_EPOCH - 1),
|
||||
None
|
||||
);
|
||||
assert_eq!(RecurrenceKey::from_recurrence_id(i64::MAX), None);
|
||||
assert_eq!(RecurrenceKey::from_prefix(0), None);
|
||||
assert_eq!(
|
||||
RecurrenceKey::from_prefix(key(2027, 3, 15, 9, 0).prefix()),
|
||||
Some(key(2027, 3, 15, 9, 0))
|
||||
);
|
||||
assert_ne!(key(2027, 3, 15, 9, 0), key(2027, 3, 15, 9, 1));
|
||||
assert_eq!(
|
||||
RecurrenceKey::from_recurrence_id(naive(2027, 3, 15, 9, 0, 30)),
|
||||
Some(key(2027, 3, 15, 9, 0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recurrence_keys_survive_an_override() {
|
||||
let before = event_data(&format!("{MASTER}END:VCALENDAR\r\n"));
|
||||
let after = event_data(&format!("{MASTER}{OVERRIDE}END:VCALENDAR\r\n"));
|
||||
|
||||
for (day, comp_id) in [(1, 1), (15, 1), (22, 1), (29, 1)] {
|
||||
let key = key(2027, 3, day, 9, 0);
|
||||
let start_naive = naive(2027, 3, day, 9, 0, 0);
|
||||
assert_eq!(expand_key(&before, key), [(comp_id, start_naive)]);
|
||||
assert_eq!(expand_key(&after, key), [(comp_id, start_naive)]);
|
||||
}
|
||||
|
||||
let overridden = key(2027, 3, 8, 9, 0);
|
||||
assert_eq!(
|
||||
expand_key(&before, overridden),
|
||||
[(1, naive(2027, 3, 8, 9, 0, 0))]
|
||||
);
|
||||
assert_eq!(
|
||||
expand_key(&after, overridden),
|
||||
[(2, naive(2027, 3, 8, 14, 0, 0))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn this_and_future_instances_get_distinct_keys() {
|
||||
const THIS_AND_FUTURE: &str = concat!(
|
||||
"BEGIN:VEVENT\r\nUID:[email protected]\r\nDTSTAMP:20270101T000000Z\r\n",
|
||||
"RECURRENCE-ID;RANGE=THISANDFUTURE:20270315T090000Z\r\n",
|
||||
"DTSTART:20270315T100000Z\r\nDTEND:20270315T113000Z\r\n",
|
||||
"SUMMARY:Longer\r\nEND:VEVENT\r\n",
|
||||
);
|
||||
let data = event_data(&format!("{MASTER}{THIS_AND_FUTURE}END:VCALENDAR\r\n"));
|
||||
|
||||
assert_eq!(
|
||||
expand_key(&data, key(2027, 3, 15, 9, 0)),
|
||||
[(2, naive(2027, 3, 15, 10, 0, 0))]
|
||||
);
|
||||
assert_eq!(
|
||||
expand_key(&data, key(2027, 3, 22, 10, 0)),
|
||||
[(2, naive(2027, 3, 22, 10, 0, 0))]
|
||||
);
|
||||
assert_eq!(
|
||||
expand_key(&data, key(2027, 3, 29, 10, 0)),
|
||||
[(2, naive(2027, 3, 29, 10, 0, 0))]
|
||||
);
|
||||
assert_eq!(
|
||||
expand_key(&data, key(2027, 3, 1, 9, 0)),
|
||||
[(1, naive(2027, 3, 1, 9, 0, 0))]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmatched_recurrence_keys_are_reported_back() {
|
||||
let data = event_data(&format!("{MASTER}END:VCALENDAR\r\n"));
|
||||
let missing = key(2027, 4, 5, 9, 0);
|
||||
let present = key(2027, 3, 15, 9, 0);
|
||||
let mut keys = AHashSet::from_iter([missing, present]);
|
||||
|
||||
let expansion = data.expand_from_ids(&mut keys, Tz::UTC).expect("expansion");
|
||||
|
||||
assert_eq!(expansion.len(), 1);
|
||||
assert_eq!(keys.into_iter().collect::<Vec<_>>(), [missing]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
ArchivedCalendar, ArchivedCalendarEvent, ArchivedCalendarPreferences, ArchivedDefaultAlert,
|
||||
ArchivedTimezone, Calendar, CalendarEvent, CalendarPreferences, DefaultAlert, Timezone,
|
||||
};
|
||||
use crate::{
|
||||
calendar::{
|
||||
ArchivedCalendarEventNotification, ArchivedChangedBy, ArchivedEventPreferences,
|
||||
CalendarEventNotification, ChangedBy, EventPreferences,
|
||||
},
|
||||
strip_mailto_scheme,
|
||||
};
|
||||
use ahash::AHashSet;
|
||||
use calcard::icalendar::{
|
||||
ArchivedICalendarParameterValue, ArchivedICalendarProperty, ArchivedICalendarValue,
|
||||
ICalendarParameterValue, ICalendarProperty, ICalendarValue,
|
||||
};
|
||||
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
|
||||
use nlp::language::{
|
||||
Language,
|
||||
detect::{LanguageDetector, MIN_LANGUAGE_SCORE},
|
||||
};
|
||||
use store::{
|
||||
U32_LEN,
|
||||
search::{CalendarSearchField, IndexDocument, SearchField},
|
||||
write::{IndexPropertyClass, SearchIndex, ValueClass},
|
||||
xxhash_rust::xxh3,
|
||||
};
|
||||
use types::{
|
||||
acl::AclGrant,
|
||||
collection::SyncCollection,
|
||||
field::{CalendarEventField, CalendarNotificationField},
|
||||
};
|
||||
|
||||
impl IndexableObject for Calendar {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::Acl {
|
||||
value: (&self.acls).into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.size() as u32,
|
||||
},
|
||||
IndexValue::LogContainer {
|
||||
sync_collection: SyncCollection::Calendar,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for &ArchivedCalendar {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::Acl {
|
||||
value: self
|
||||
.acls
|
||||
.iter()
|
||||
.map(AclGrant::from)
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.size() as u32,
|
||||
},
|
||||
IndexValue::LogContainer {
|
||||
sync_collection: SyncCollection::Calendar,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableAndSerializableObject for Calendar {
|
||||
fn is_versioned() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for CalendarEvent {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::SearchIndex {
|
||||
index: SearchIndex::Calendar,
|
||||
hash: self
|
||||
.hashes()
|
||||
.chain([self.data.event_range_start() as u64])
|
||||
.fold(0, |acc, hash| acc ^ hash),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarEventField::Uid.into(),
|
||||
value: self.data.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.size() as u32,
|
||||
},
|
||||
IndexValue::LogItem {
|
||||
sync_collection: SyncCollection::Calendar,
|
||||
prefix: None,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for &ArchivedCalendarEvent {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::SearchIndex {
|
||||
index: SearchIndex::Calendar,
|
||||
hash: self
|
||||
.hashes()
|
||||
.chain([self.data.event_range_start() as u64])
|
||||
.fold(0, |acc, hash| acc ^ hash),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarEventField::Uid.into(),
|
||||
value: self.data.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.size() as u32,
|
||||
},
|
||||
IndexValue::LogItem {
|
||||
sync_collection: SyncCollection::Calendar,
|
||||
prefix: None,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableAndSerializableObject for CalendarEvent {
|
||||
fn is_versioned() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for CalendarEventNotification {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::Quota {
|
||||
used: self.size() as u32,
|
||||
},
|
||||
IndexValue::Property {
|
||||
field: ValueClass::IndexProperty(IndexPropertyClass::Integer {
|
||||
property: CalendarNotificationField::CreatedToId.into(),
|
||||
value: self.created as u64,
|
||||
}),
|
||||
value: self.event_id.unwrap_or(u32::MAX).into(),
|
||||
},
|
||||
IndexValue::LogItem {
|
||||
sync_collection: SyncCollection::CalendarEventNotification,
|
||||
prefix: None,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for &ArchivedCalendarEventNotification {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::Quota {
|
||||
used: self.size() as u32,
|
||||
},
|
||||
IndexValue::Property {
|
||||
field: ValueClass::IndexProperty(IndexPropertyClass::Integer {
|
||||
property: CalendarNotificationField::CreatedToId.into(),
|
||||
value: self.created.to_native() as u64,
|
||||
}),
|
||||
value: self
|
||||
.event_id
|
||||
.as_ref()
|
||||
.map(|v| v.to_native())
|
||||
.unwrap_or(u32::MAX)
|
||||
.into(),
|
||||
},
|
||||
IndexValue::LogItem {
|
||||
sync_collection: SyncCollection::CalendarEventNotification,
|
||||
prefix: None,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableAndSerializableObject for CalendarEventNotification {
|
||||
fn is_versioned() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
pub fn size(&self) -> usize {
|
||||
self.dead_properties.size()
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>()
|
||||
+ self.name.len()
|
||||
+ std::mem::size_of::<Calendar>()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendar {
|
||||
pub fn size(&self) -> usize {
|
||||
self.dead_properties.size()
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>()
|
||||
+ self.name.len()
|
||||
+ std::mem::size_of::<Calendar>()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn size(&self) -> usize {
|
||||
self.dead_properties.size()
|
||||
+ self.display_name.as_ref().map_or(0, |n| n.len())
|
||||
+ self.names.iter().map(|n| n.name.len()).sum::<usize>()
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>()
|
||||
+ self.size as usize
|
||||
+ std::mem::size_of::<CalendarEvent>()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEvent {
|
||||
pub fn size(&self) -> usize {
|
||||
self.dead_properties.size()
|
||||
+ self.display_name.as_ref().map_or(0, |n| n.len())
|
||||
+ self.names.iter().map(|n| n.name.len()).sum::<usize>()
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>()
|
||||
+ self.size.to_native() as usize
|
||||
+ std::mem::size_of::<CalendarEvent>()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventNotification {
|
||||
pub fn size(&self) -> usize {
|
||||
(match &self.changed_by {
|
||||
ChangedBy::PrincipalId(_) => U32_LEN,
|
||||
ChangedBy::CalendarAddress(v) => v.len(),
|
||||
}) + std::mem::size_of::<CalendarEventNotification>()
|
||||
+ self.size as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEventNotification {
|
||||
pub fn size(&self) -> usize {
|
||||
(match &self.changed_by {
|
||||
ArchivedChangedBy::PrincipalId(_) => U32_LEN,
|
||||
ArchivedChangedBy::CalendarAddress(v) => v.len(),
|
||||
}) + std::mem::size_of::<CalendarEventNotification>()
|
||||
+ self.size.to_native() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarPreferences {
|
||||
pub fn size(&self) -> usize {
|
||||
self.name.len()
|
||||
+ self.default_alerts.iter().map(|a| a.size()).sum::<usize>()
|
||||
+ self.description.as_ref().map_or(0, |n| n.len())
|
||||
+ self.color.as_ref().map_or(0, |n| n.len())
|
||||
+ self.time_zone.size()
|
||||
+ std::mem::size_of::<CalendarPreferences>()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarPreferences {
|
||||
pub fn size(&self) -> usize {
|
||||
self.name.len()
|
||||
+ self.default_alerts.iter().map(|a| a.size()).sum::<usize>()
|
||||
+ self.description.as_ref().map_or(0, |n| n.len())
|
||||
+ self.color.as_ref().map_or(0, |n| n.len())
|
||||
+ self.time_zone.size()
|
||||
+ std::mem::size_of::<CalendarPreferences>()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventPreferences {
|
||||
pub fn size(&self) -> usize {
|
||||
self.alerts.iter().map(|a| a.size()).sum::<usize>()
|
||||
+ self.properties.iter().map(|p| p.size()).sum::<usize>()
|
||||
+ std::mem::size_of::<EventPreferences>()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedEventPreferences {
|
||||
pub fn size(&self) -> usize {
|
||||
self.alerts.iter().map(|a| a.size()).sum::<usize>()
|
||||
+ self.properties.iter().map(|p| p.size()).sum::<usize>()
|
||||
+ std::mem::size_of::<EventPreferences>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Timezone {
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Timezone::IANA(_) => 2,
|
||||
Timezone::Custom(c) => c.size(),
|
||||
Timezone::Default => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedTimezone {
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
ArchivedTimezone::IANA(_) => 2,
|
||||
ArchivedTimezone::Custom(c) => c.size(),
|
||||
ArchivedTimezone::Default => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultAlert {
|
||||
pub fn size(&self) -> usize {
|
||||
std::mem::size_of::<DefaultAlert>() + self.id.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedDefaultAlert {
|
||||
pub fn size(&self) -> usize {
|
||||
std::mem::size_of::<DefaultAlert>() + self.id.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn hashes(&self) -> impl Iterator<Item = u64> {
|
||||
self.data
|
||||
.event
|
||||
.components
|
||||
.iter()
|
||||
.filter(|e| e.component_type.is_scheduling_object())
|
||||
.flat_map(|e| {
|
||||
e.entries.iter().filter(|e| {
|
||||
matches!(
|
||||
e.name,
|
||||
ICalendarProperty::Summary
|
||||
| ICalendarProperty::Location
|
||||
| ICalendarProperty::Description
|
||||
| ICalendarProperty::Categories
|
||||
| ICalendarProperty::Comment
|
||||
| ICalendarProperty::Attendee
|
||||
| ICalendarProperty::Organizer
|
||||
| ICalendarProperty::Uid
|
||||
)
|
||||
})
|
||||
})
|
||||
.flat_map(|e| {
|
||||
e.values
|
||||
.iter()
|
||||
.filter_map(|v| match v {
|
||||
ICalendarValue::Text(v) => Some(v.as_str()),
|
||||
ICalendarValue::Uri(uri) => uri.as_str(),
|
||||
_ => None,
|
||||
})
|
||||
.chain(e.params.iter().filter_map(|p| match &p.value {
|
||||
ICalendarParameterValue::Text(v) => Some(v.as_str()),
|
||||
ICalendarParameterValue::Uri(uri) => uri.as_str(),
|
||||
_ => None,
|
||||
}))
|
||||
})
|
||||
.map(|v| xxh3::xxh3_64(v.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEvent {
|
||||
pub fn hashes(&self) -> impl Iterator<Item = u64> {
|
||||
self.data
|
||||
.event
|
||||
.components
|
||||
.iter()
|
||||
.filter(|e| e.component_type.is_scheduling_object())
|
||||
.flat_map(|e| {
|
||||
e.entries.iter().filter(|e| {
|
||||
matches!(
|
||||
e.name,
|
||||
ArchivedICalendarProperty::Summary
|
||||
| ArchivedICalendarProperty::Location
|
||||
| ArchivedICalendarProperty::Description
|
||||
| ArchivedICalendarProperty::Categories
|
||||
| ArchivedICalendarProperty::Comment
|
||||
| ArchivedICalendarProperty::Attendee
|
||||
| ArchivedICalendarProperty::Organizer
|
||||
| ArchivedICalendarProperty::Uid
|
||||
)
|
||||
})
|
||||
})
|
||||
.flat_map(|e| {
|
||||
e.values
|
||||
.iter()
|
||||
.filter_map(|v| match v {
|
||||
ArchivedICalendarValue::Text(v) => Some(v.as_str()),
|
||||
ArchivedICalendarValue::Uri(uri) => uri.as_str(),
|
||||
_ => None,
|
||||
})
|
||||
.chain(e.params.iter().filter_map(|p| match &p.value {
|
||||
ArchivedICalendarParameterValue::Text(v) => Some(v.as_str()),
|
||||
ArchivedICalendarParameterValue::Uri(uri) => uri.as_str(),
|
||||
_ => None,
|
||||
}))
|
||||
})
|
||||
.map(|v| xxh3::xxh3_64(v.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEvent {
|
||||
pub fn index_document(
|
||||
&self,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
index_fields: &AHashSet<SearchField>,
|
||||
default_language: Language,
|
||||
) -> IndexDocument {
|
||||
let mut document = IndexDocument::new(SearchIndex::Calendar)
|
||||
.with_account_id(account_id)
|
||||
.with_document_id(document_id);
|
||||
|
||||
if index_fields.is_empty()
|
||||
|| index_fields.contains(&SearchField::Calendar(CalendarSearchField::Start))
|
||||
{
|
||||
document.index_integer(CalendarSearchField::Start, self.data.event_range_start());
|
||||
}
|
||||
|
||||
let mut detector = LanguageDetector::new();
|
||||
for component in self
|
||||
.data
|
||||
.event
|
||||
.components
|
||||
.iter()
|
||||
.filter(|e| e.component_type.is_scheduling_object())
|
||||
{
|
||||
for entry in component.entries.iter() {
|
||||
let (is_lang, is_keyword, field) = match entry.name {
|
||||
ArchivedICalendarProperty::Summary => (true, false, CalendarSearchField::Title),
|
||||
ArchivedICalendarProperty::Description => {
|
||||
(true, false, CalendarSearchField::Description)
|
||||
}
|
||||
ArchivedICalendarProperty::Location => {
|
||||
(false, false, CalendarSearchField::Location)
|
||||
}
|
||||
ArchivedICalendarProperty::Organizer => {
|
||||
(false, false, CalendarSearchField::Owner)
|
||||
}
|
||||
ArchivedICalendarProperty::Attendee => {
|
||||
(false, false, CalendarSearchField::Attendee)
|
||||
}
|
||||
ArchivedICalendarProperty::Uid => (false, true, CalendarSearchField::Uid),
|
||||
_ => continue,
|
||||
};
|
||||
let field = SearchField::Calendar(field);
|
||||
|
||||
if index_fields.is_empty() || index_fields.contains(&field) {
|
||||
for value in entry
|
||||
.values
|
||||
.iter()
|
||||
.filter_map(|v| match v {
|
||||
ArchivedICalendarValue::Text(v) => Some(v.as_str()),
|
||||
ArchivedICalendarValue::Uri(uri) => uri.as_str(),
|
||||
_ => None,
|
||||
})
|
||||
.chain(entry.params.iter().filter_map(|p| match &p.value {
|
||||
ArchivedICalendarParameterValue::Text(v) => Some(v.as_str()),
|
||||
ArchivedICalendarParameterValue::Uri(uri) => uri.as_str(),
|
||||
_ => None,
|
||||
}))
|
||||
{
|
||||
let value = strip_mailto_scheme(value);
|
||||
let lang = if is_lang {
|
||||
detector.detect(value, MIN_LANGUAGE_SCORE);
|
||||
Language::Unknown
|
||||
} else {
|
||||
Language::None
|
||||
};
|
||||
|
||||
if !is_keyword {
|
||||
document.index_text(field.clone(), value, lang);
|
||||
} else {
|
||||
document.index_keyword(field.clone(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.set_unknown_language(
|
||||
detector
|
||||
.most_frequent_language()
|
||||
.unwrap_or(default_language),
|
||||
);
|
||||
|
||||
document
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod alarm;
|
||||
pub mod dates;
|
||||
pub mod expand;
|
||||
pub mod index;
|
||||
pub mod itip;
|
||||
pub mod storage;
|
||||
|
||||
use calcard::icalendar::{
|
||||
ICalendar, ICalendarComponent, ICalendarComponentType, ICalendarDuration, ICalendarEntry,
|
||||
};
|
||||
use common::DavName;
|
||||
use types::{acl::AclGrant, dead_property::DeadProperty};
|
||||
use utils::map::bitmap::BitmapItem;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct Calendar {
|
||||
pub name: String,
|
||||
pub preferences: Vec<CalendarPreferences>,
|
||||
pub acls: Vec<AclGrant>,
|
||||
pub supported_components: u64,
|
||||
pub dead_properties: DeadProperty,
|
||||
pub created: i64,
|
||||
pub modified: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SupportedComponent {
|
||||
VCalendar, // [RFC5545, Section 3.4]
|
||||
VEvent, // [RFC5545, Section 3.6.1]
|
||||
VTodo, // [RFC5545, Section 3.6.2]
|
||||
VJournal, // [RFC5545, Section 3.6.3]
|
||||
VFreebusy, // [RFC5545, Section 3.6.4]
|
||||
VTimezone, // [RFC5545, Section 3.6.5]
|
||||
VAlarm, // [RFC5545, Section 3.6.6]
|
||||
Standard, // [RFC5545, Section 3.6.5]
|
||||
Daylight, // [RFC5545, Section 3.6.5]
|
||||
VAvailability, // [RFC7953, Section 3.1]
|
||||
Available, // [RFC7953, Section 3.1]
|
||||
Participant, // [RFC9073, Section 7.1]
|
||||
VLocation, // [RFC9073, Section 7.2] [RFC Errata 7381]
|
||||
VResource, // [RFC9073, Section 7.3]
|
||||
VStatus, // draft-ietf-calext-ical-tasks-14
|
||||
Other,
|
||||
}
|
||||
|
||||
pub const CALENDAR_SUBSCRIBED: u16 = 1;
|
||||
pub const CALENDAR_INVISIBLE: u16 = 1 << 1;
|
||||
pub const CALENDAR_AVAILABILITY_NONE: u16 = 1 << 2;
|
||||
pub const CALENDAR_AVAILABILITY_ATTENDING: u16 = 1 << 3;
|
||||
pub const CALENDAR_AVAILABILITY_ALL: u16 = 1 << 4;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarPreferences {
|
||||
pub account_id: u32,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub sort_order: u32,
|
||||
pub color: Option<String>,
|
||||
pub flags: u16,
|
||||
pub time_zone: Timezone,
|
||||
pub default_alerts: Vec<DefaultAlert>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct DefaultAlert {
|
||||
pub id: String,
|
||||
pub offset: ICalendarDuration,
|
||||
pub flags: u16,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct ParticipantIdentities {
|
||||
pub identities: Vec<ParticipantIdentity>,
|
||||
pub default_name: String,
|
||||
pub default: u32,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct ParticipantIdentity {
|
||||
pub id: u32,
|
||||
pub name: Option<String>,
|
||||
pub calendar_address: String,
|
||||
}
|
||||
|
||||
pub const ALERT_WITH_TIME: u16 = 1;
|
||||
pub const ALERT_EMAIL: u16 = 1 << 1;
|
||||
pub const ALERT_RELATIVE_TO_END: u16 = 1 << 2;
|
||||
|
||||
pub const SCHEDULE_INBOX_ID: u32 = u32::MAX - 1;
|
||||
pub const SCHEDULE_OUTBOX_ID: u32 = u32::MAX - 2;
|
||||
|
||||
pub const EVENT_INVITE_SELF: u16 = 1;
|
||||
pub const EVENT_INVITE_OTHERS: u16 = 1 << 1;
|
||||
pub const EVENT_HIDE_ATTENDEES: u16 = 1 << 2;
|
||||
pub const EVENT_DRAFT: u16 = 1 << 3;
|
||||
|
||||
pub const EVENT_NOTIFICATION_IS_DRAFT: u16 = 1;
|
||||
pub const EVENT_NOTIFICATION_IS_CHANGE: u16 = 1 << 1;
|
||||
|
||||
pub const PREF_USE_DEFAULT_ALERTS: u16 = 1;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarEvent {
|
||||
pub names: Vec<DavName>,
|
||||
pub display_name: Option<String>,
|
||||
pub data: CalendarEventData,
|
||||
pub preferences: Vec<EventPreferences>,
|
||||
pub flags: u16,
|
||||
pub dead_properties: DeadProperty,
|
||||
pub size: u32,
|
||||
pub created: i64,
|
||||
pub modified: i64,
|
||||
pub schedule_tag: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarEventNotification {
|
||||
pub event: ICalendar,
|
||||
pub event_id: Option<u32>,
|
||||
pub changed_by: ChangedBy,
|
||||
pub flags: u16,
|
||||
pub size: u32,
|
||||
pub created: i64,
|
||||
pub modified: i64,
|
||||
}
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ChangedBy {
|
||||
PrincipalId(u32),
|
||||
CalendarAddress(String),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarEventData {
|
||||
pub event: ICalendar,
|
||||
pub time_ranges: Box<[ComponentTimeRange]>,
|
||||
pub alarms: Box<[Alarm]>,
|
||||
pub base_offset: i64,
|
||||
pub base_time_utc: u32,
|
||||
pub duration: u32,
|
||||
}
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[rkyv(compare(PartialEq), derive(Debug))]
|
||||
pub struct Alarm {
|
||||
pub id: u16,
|
||||
pub parent_id: u16,
|
||||
pub delta: AlarmDelta,
|
||||
pub is_email_alert: bool,
|
||||
}
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[rkyv(compare(PartialEq), derive(Debug))]
|
||||
pub enum AlarmDelta {
|
||||
Start(i64),
|
||||
End(i64),
|
||||
FixedUtc(i64),
|
||||
FixedFloating(i64),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct ComponentTimeRange {
|
||||
pub id: u16,
|
||||
pub start_tz: u16,
|
||||
pub end_tz: u16,
|
||||
pub duration: i32,
|
||||
pub instances: Box<[u8]>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct EventPreferences {
|
||||
pub account_id: u32,
|
||||
pub flags: u16,
|
||||
pub properties: Vec<ICalendarEntry>,
|
||||
pub alerts: Vec<ICalendarComponent>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub enum Timezone {
|
||||
IANA(u16),
|
||||
Custom(ICalendar),
|
||||
#[default]
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
pub fn preferences(&self, account_id: u32) -> &CalendarPreferences {
|
||||
if self.preferences.len() == 1 {
|
||||
&self.preferences[0]
|
||||
} else {
|
||||
self.preferences
|
||||
.iter()
|
||||
.find(|p| p.account_id == account_id)
|
||||
.or_else(|| self.preferences.first())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preferences_mut(&mut self, account_id: u32) -> &mut CalendarPreferences {
|
||||
let idx = if let Some(idx) = self
|
||||
.preferences
|
||||
.iter()
|
||||
.position(|p| p.account_id == account_id)
|
||||
{
|
||||
idx
|
||||
} else {
|
||||
let mut preferences = self.preferences[0].clone();
|
||||
preferences.account_id = account_id;
|
||||
self.preferences.push(preferences);
|
||||
self.preferences.len() - 1
|
||||
};
|
||||
|
||||
&mut self.preferences[idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendar {
|
||||
pub fn default_alerts(
|
||||
&self,
|
||||
account_id: u32,
|
||||
with_time: bool,
|
||||
) -> impl Iterator<Item = &ArchivedDefaultAlert> {
|
||||
self.preferences(account_id)
|
||||
.default_alerts
|
||||
.iter()
|
||||
.filter(move |a| (a.flags & ALERT_WITH_TIME != 0) == with_time)
|
||||
}
|
||||
|
||||
pub fn preferences(&self, account_id: u32) -> &ArchivedCalendarPreferences {
|
||||
if self.preferences.len() == 1 {
|
||||
&self.preferences[0]
|
||||
} else {
|
||||
self.preferences
|
||||
.iter()
|
||||
.find(|p| p.account_id == account_id)
|
||||
.or_else(|| self.preferences.first())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn preferences(&self, account_id: u32) -> Option<&EventPreferences> {
|
||||
self.preferences.iter().find(|p| p.account_id == account_id)
|
||||
}
|
||||
|
||||
pub fn preferences_mut(&mut self, account_id: u32) -> &mut EventPreferences {
|
||||
let idx = if let Some(idx) = self
|
||||
.preferences
|
||||
.iter()
|
||||
.position(|p| p.account_id == account_id)
|
||||
{
|
||||
idx
|
||||
} else {
|
||||
self.preferences.push(EventPreferences {
|
||||
account_id,
|
||||
flags: 0,
|
||||
properties: Vec::new(),
|
||||
alerts: Vec::new(),
|
||||
});
|
||||
self.preferences.len() - 1
|
||||
};
|
||||
|
||||
&mut self.preferences[idx]
|
||||
}
|
||||
|
||||
pub fn added_calendar_ids(
|
||||
&self,
|
||||
prev_data: &ArchivedCalendarEvent,
|
||||
) -> impl Iterator<Item = u32> {
|
||||
self.names
|
||||
.iter()
|
||||
.filter(|m| prev_data.names.iter().all(|pm| pm.parent_id != m.parent_id))
|
||||
.map(|m| m.parent_id)
|
||||
}
|
||||
|
||||
pub fn removed_calendar_ids(
|
||||
&self,
|
||||
prev_data: &ArchivedCalendarEvent,
|
||||
) -> impl Iterator<Item = u32> {
|
||||
prev_data
|
||||
.names
|
||||
.iter()
|
||||
.filter(|m| self.names.iter().all(|pm| pm.parent_id != m.parent_id))
|
||||
.map(|m| m.parent_id.to_native())
|
||||
}
|
||||
|
||||
pub fn unchanged_calendar_ids(
|
||||
&self,
|
||||
prev_data: &ArchivedCalendarEvent,
|
||||
) -> impl Iterator<Item = u32> {
|
||||
self.names
|
||||
.iter()
|
||||
.filter(|m| prev_data.names.iter().any(|pm| pm.parent_id == m.parent_id))
|
||||
.map(|m| m.parent_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEvent {
|
||||
pub fn preferences(&self, account_id: u32) -> Option<&ArchivedEventPreferences> {
|
||||
self.preferences.iter().find(|p| p.account_id == account_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChangedBy {
|
||||
fn default() -> Self {
|
||||
ChangedBy::CalendarAddress("".into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for SupportedComponent {
|
||||
fn from(value: u64) -> Self {
|
||||
match value {
|
||||
0 => SupportedComponent::VCalendar,
|
||||
1 => SupportedComponent::VEvent,
|
||||
2 => SupportedComponent::VTodo,
|
||||
3 => SupportedComponent::VJournal,
|
||||
4 => SupportedComponent::VFreebusy,
|
||||
5 => SupportedComponent::VTimezone,
|
||||
6 => SupportedComponent::VAlarm,
|
||||
7 => SupportedComponent::Standard,
|
||||
8 => SupportedComponent::Daylight,
|
||||
9 => SupportedComponent::VAvailability,
|
||||
10 => SupportedComponent::Available,
|
||||
11 => SupportedComponent::Participant,
|
||||
12 => SupportedComponent::VLocation,
|
||||
13 => SupportedComponent::VResource,
|
||||
14 => SupportedComponent::VStatus,
|
||||
_ => SupportedComponent::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SupportedComponent> for u64 {
|
||||
fn from(value: SupportedComponent) -> Self {
|
||||
match value {
|
||||
SupportedComponent::VCalendar => 0,
|
||||
SupportedComponent::VEvent => 1,
|
||||
SupportedComponent::VTodo => 2,
|
||||
SupportedComponent::VJournal => 3,
|
||||
SupportedComponent::VFreebusy => 4,
|
||||
SupportedComponent::VTimezone => 5,
|
||||
SupportedComponent::VAlarm => 6,
|
||||
SupportedComponent::Standard => 7,
|
||||
SupportedComponent::Daylight => 8,
|
||||
SupportedComponent::VAvailability => 9,
|
||||
SupportedComponent::Available => 10,
|
||||
SupportedComponent::Participant => 11,
|
||||
SupportedComponent::VLocation => 12,
|
||||
SupportedComponent::VResource => 13,
|
||||
SupportedComponent::VStatus => 14,
|
||||
SupportedComponent::Other => 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitmapItem for SupportedComponent {
|
||||
fn max() -> u64 {
|
||||
u64::from(SupportedComponent::Other)
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
!matches!(self, SupportedComponent::Other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ICalendarComponentType> for SupportedComponent {
|
||||
fn from(value: ICalendarComponentType) -> Self {
|
||||
match value {
|
||||
ICalendarComponentType::VCalendar => SupportedComponent::VCalendar,
|
||||
ICalendarComponentType::VEvent => SupportedComponent::VEvent,
|
||||
ICalendarComponentType::VTodo => SupportedComponent::VTodo,
|
||||
ICalendarComponentType::VJournal => SupportedComponent::VJournal,
|
||||
ICalendarComponentType::VFreebusy => SupportedComponent::VFreebusy,
|
||||
ICalendarComponentType::VTimezone => SupportedComponent::VTimezone,
|
||||
ICalendarComponentType::VAlarm => SupportedComponent::VAlarm,
|
||||
ICalendarComponentType::Standard => SupportedComponent::Standard,
|
||||
ICalendarComponentType::Daylight => SupportedComponent::Daylight,
|
||||
ICalendarComponentType::VAvailability => SupportedComponent::VAvailability,
|
||||
ICalendarComponentType::Available => SupportedComponent::Available,
|
||||
ICalendarComponentType::Participant => SupportedComponent::Participant,
|
||||
ICalendarComponentType::VLocation => SupportedComponent::VLocation,
|
||||
ICalendarComponentType::VResource => SupportedComponent::VResource,
|
||||
ICalendarComponentType::VStatus => SupportedComponent::VStatus,
|
||||
_ => SupportedComponent::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SupportedComponent> for ICalendarComponentType {
|
||||
fn from(value: SupportedComponent) -> Self {
|
||||
match value {
|
||||
SupportedComponent::VCalendar => ICalendarComponentType::VCalendar,
|
||||
SupportedComponent::VEvent => ICalendarComponentType::VEvent,
|
||||
SupportedComponent::VTodo => ICalendarComponentType::VTodo,
|
||||
SupportedComponent::VJournal => ICalendarComponentType::VJournal,
|
||||
SupportedComponent::VFreebusy => ICalendarComponentType::VFreebusy,
|
||||
SupportedComponent::VTimezone => ICalendarComponentType::VTimezone,
|
||||
SupportedComponent::VAlarm => ICalendarComponentType::VAlarm,
|
||||
SupportedComponent::Standard => ICalendarComponentType::Standard,
|
||||
SupportedComponent::Daylight => ICalendarComponentType::Daylight,
|
||||
SupportedComponent::VAvailability => ICalendarComponentType::VAvailability,
|
||||
SupportedComponent::Available => ICalendarComponentType::Available,
|
||||
SupportedComponent::Participant => ICalendarComponentType::Participant,
|
||||
SupportedComponent::VLocation => ICalendarComponentType::VLocation,
|
||||
SupportedComponent::VResource => ICalendarComponentType::VResource,
|
||||
SupportedComponent::VStatus => ICalendarComponentType::VStatus,
|
||||
SupportedComponent::Other => ICalendarComponentType::Other(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{
|
||||
ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent, CalendarPreferences,
|
||||
alarm::CalendarAlarm,
|
||||
};
|
||||
use crate::{
|
||||
DavResourceName, DestroyArchive, RFC_3986,
|
||||
calendar::{
|
||||
ArchivedCalendarEventNotification, CalendarEventNotification, alarm::CalendarAlarmType,
|
||||
},
|
||||
scheduling::{ItipMessages, event_cancel::itip_cancel},
|
||||
};
|
||||
use calcard::common::timezone::Tz;
|
||||
use common::{
|
||||
Server,
|
||||
auth::{AccountInfo, AccountTenantIds},
|
||||
storage::index::ObjectIndexBuilder,
|
||||
};
|
||||
use registry::{
|
||||
schema::structs::{Task, TaskCalendarAlarmEmail, TaskCalendarAlarmNotification, TaskStatus},
|
||||
types::{EnumImpl, ObjectImpl, datetime::UTCDateTime},
|
||||
};
|
||||
use store::{
|
||||
IterateParams, SerializeInfallible, U32_LEN, ValueKey,
|
||||
roaring::RoaringBitmap,
|
||||
write::{
|
||||
AlignedBytes, Archive, BatchBuilder, IndexPropertyClass, Operation, TaskQueueClass,
|
||||
ValueClass, ValueOp, key::DeserializeBigEndian, now,
|
||||
},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
collection::{Collection, VanishedCollection},
|
||||
field::CalendarNotificationField,
|
||||
id::Id,
|
||||
};
|
||||
|
||||
pub trait ItipAutoExpunge: Sync + Send {
|
||||
fn itip_ids(&self, account_id: u32) -> impl Future<Output = trc::Result<RoaringBitmap>> + Send;
|
||||
|
||||
fn itip_auto_expunge(
|
||||
&self,
|
||||
account_id: u32,
|
||||
hold_period: u64,
|
||||
) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
}
|
||||
|
||||
impl ItipAutoExpunge for Server {
|
||||
async fn itip_ids(&self, account_id: u32) -> trc::Result<RoaringBitmap> {
|
||||
let mut document_ids = RoaringBitmap::new();
|
||||
self.store()
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: Collection::CalendarEventNotification.into(),
|
||||
document_id: 0,
|
||||
class: ValueClass::IndexProperty(IndexPropertyClass::Integer {
|
||||
property: CalendarNotificationField::CreatedToId.into(),
|
||||
value: 0,
|
||||
}),
|
||||
},
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: Collection::CalendarEventNotification.into(),
|
||||
document_id: 0,
|
||||
class: ValueClass::IndexProperty(IndexPropertyClass::Integer {
|
||||
property: CalendarNotificationField::CreatedToId.into(),
|
||||
value: u64::MAX,
|
||||
}),
|
||||
},
|
||||
)
|
||||
.no_values()
|
||||
.ascending(),
|
||||
|key, _| {
|
||||
document_ids.insert(key.deserialize_be_u32(key.len() - U32_LEN)?);
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())
|
||||
.map(|_| document_ids)
|
||||
}
|
||||
|
||||
async fn itip_auto_expunge(&self, account_id: u32, hold_period: u64) -> trc::Result<()> {
|
||||
let mut destroy_ids = RoaringBitmap::new();
|
||||
self.store()
|
||||
.iterate(
|
||||
IterateParams::new(
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: Collection::CalendarEventNotification.into(),
|
||||
document_id: 0,
|
||||
class: ValueClass::IndexProperty(IndexPropertyClass::Integer {
|
||||
property: CalendarNotificationField::CreatedToId.into(),
|
||||
value: 0,
|
||||
}),
|
||||
},
|
||||
ValueKey {
|
||||
account_id,
|
||||
collection: Collection::CalendarEventNotification.into(),
|
||||
document_id: 0,
|
||||
class: ValueClass::IndexProperty(IndexPropertyClass::Integer {
|
||||
property: CalendarNotificationField::CreatedToId.into(),
|
||||
value: now().saturating_sub(hold_period),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.no_values()
|
||||
.ascending(),
|
||||
|key, _| {
|
||||
destroy_ids.insert(key.deserialize_be_u32(key.len() - U32_LEN)?);
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if destroy_ids.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
trc::event!(
|
||||
Store(trc::StoreEvent::AutoExpunge),
|
||||
AccountId = account_id,
|
||||
Collection = Collection::CalendarEventNotification.as_str(),
|
||||
Total = destroy_ids.len(),
|
||||
);
|
||||
|
||||
// Tombstone messages
|
||||
let mut batch = BatchBuilder::new();
|
||||
let changed_by = self
|
||||
.account(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.account_tenant_ids();
|
||||
|
||||
for document_id in destroy_ids {
|
||||
// Fetch event
|
||||
if let Some(event_) = self
|
||||
.store()
|
||||
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
|
||||
account_id,
|
||||
Collection::CalendarEventNotification,
|
||||
document_id,
|
||||
))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
let event = event_
|
||||
.to_unarchived::<CalendarEventNotification>()
|
||||
.caused_by(trc::location!())?;
|
||||
DestroyArchive(event)
|
||||
.delete(changed_by, account_id, document_id, &mut batch)
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
}
|
||||
|
||||
self.commit_batch(batch).await.caused_by(trc::location!())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn update<'x>(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
event: Archive<&ArchivedCalendarEvent>,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &'x mut BatchBuilder,
|
||||
) -> trc::Result<&'x mut BatchBuilder> {
|
||||
let mut new_event = self;
|
||||
|
||||
// Build event
|
||||
new_event.modified = now() as i64;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_current(event)
|
||||
.with_changes(new_event)
|
||||
.with_changed_by(changed_by),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
next_alarm: Option<CalendarAlarm>,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<&mut BatchBuilder> {
|
||||
// Build event
|
||||
let mut event = self;
|
||||
let now = now() as i64;
|
||||
event.modified = now;
|
||||
event.created = now;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<(), _>::new()
|
||||
.with_changes(event)
|
||||
.with_changed_by(changed_by),
|
||||
)
|
||||
.map(|batch| {
|
||||
if let Some(next_alarm) = next_alarm {
|
||||
next_alarm.write_task(batch);
|
||||
}
|
||||
|
||||
batch.commit_point()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
pub fn insert(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<&mut BatchBuilder> {
|
||||
// Build address calendar
|
||||
let mut calendar = self;
|
||||
let now = now() as i64;
|
||||
calendar.modified = now;
|
||||
calendar.created = now;
|
||||
|
||||
if calendar.preferences.is_empty() {
|
||||
calendar.preferences.push(CalendarPreferences {
|
||||
account_id,
|
||||
name: "default".to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Calendar)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<(), _>::new()
|
||||
.with_changes(calendar)
|
||||
.with_changed_by(changed_by),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
|
||||
pub fn update<'x>(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
calendar: Archive<&ArchivedCalendar>,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &'x mut BatchBuilder,
|
||||
) -> trc::Result<&'x mut BatchBuilder> {
|
||||
// Build address calendar
|
||||
let mut new_calendar = self;
|
||||
new_calendar.modified = now() as i64;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Calendar)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_current(calendar)
|
||||
.with_changes(new_calendar)
|
||||
.with_changed_by(changed_by),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventNotification {
|
||||
pub fn insert(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<&mut BatchBuilder> {
|
||||
// Build event
|
||||
let mut event = self;
|
||||
let now = now() as i64;
|
||||
event.modified = now;
|
||||
event.created = now;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEventNotification)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<(), _>::new()
|
||||
.with_changes(event)
|
||||
.with_changed_by(changed_by),
|
||||
)
|
||||
.map(|batch| batch.commit_point())
|
||||
}
|
||||
}
|
||||
|
||||
impl DestroyArchive<Archive<&ArchivedCalendar>> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn delete_with_events(
|
||||
self,
|
||||
server: &Server,
|
||||
account_info: &AccountInfo,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
children_ids: Vec<u32>,
|
||||
delete_path: Option<String>,
|
||||
send_itip: bool,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
// Process deletions
|
||||
let calendar_id = document_id;
|
||||
for document_id in children_ids {
|
||||
if let Some(event_) = server
|
||||
.store()
|
||||
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
|
||||
account_id,
|
||||
Collection::CalendarEvent,
|
||||
document_id,
|
||||
))
|
||||
.await?
|
||||
{
|
||||
DestroyArchive(
|
||||
event_
|
||||
.to_unarchived::<CalendarEvent>()
|
||||
.caused_by(trc::location!())?,
|
||||
)
|
||||
.delete(
|
||||
account_info,
|
||||
account_id,
|
||||
document_id,
|
||||
calendar_id,
|
||||
None,
|
||||
send_itip,
|
||||
batch,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.delete(
|
||||
account_info.account_tenant_ids(),
|
||||
account_id,
|
||||
document_id,
|
||||
delete_path,
|
||||
batch,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
delete_path: Option<String>,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
let calendar = self.0;
|
||||
// Delete calendar
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Calendar)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_changed_by(changed_by)
|
||||
.with_current(calendar),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
if let Some(delete_path) = delete_path {
|
||||
batch.log_vanished_item(VanishedCollection::Calendar, delete_path);
|
||||
}
|
||||
batch.commit_point();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DestroyArchive<Archive<&ArchivedCalendarEvent>> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn delete(
|
||||
self,
|
||||
account_info: &AccountInfo,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
calendar_id: u32,
|
||||
delete_path: Option<String>,
|
||||
send_itip: bool,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
if let Some(delete_idx) = self
|
||||
.0
|
||||
.inner
|
||||
.names
|
||||
.iter()
|
||||
.position(|name| name.parent_id == calendar_id)
|
||||
{
|
||||
if self.0.inner.names.len() > 1 {
|
||||
// Unlink calendar id from event
|
||||
let event = self.0;
|
||||
let mut new_event = event
|
||||
.deserialize::<CalendarEvent>()
|
||||
.caused_by(trc::location!())?;
|
||||
new_event.names.swap_remove(delete_idx);
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_changed_by(account_info.account_tenant_ids())
|
||||
.with_current(event)
|
||||
.with_changes(new_event),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
} else {
|
||||
self.delete_all(account_info, account_id, document_id, send_itip, batch)?;
|
||||
}
|
||||
|
||||
if let Some(delete_path) = delete_path {
|
||||
batch.log_vanished_item(VanishedCollection::Calendar, delete_path);
|
||||
}
|
||||
|
||||
batch.commit_point();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn delete_all(
|
||||
self,
|
||||
account_info: &AccountInfo,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
send_itip: bool,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
let event = self.0;
|
||||
// Delete event
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent)
|
||||
.with_document(document_id);
|
||||
|
||||
// Remove next alarm if it exists
|
||||
let now = now() as i64;
|
||||
if let Some(next_alarm) = event.inner.data.next_alarm(now, Tz::Floating) {
|
||||
next_alarm.delete_task(batch);
|
||||
}
|
||||
|
||||
// Scheduling
|
||||
if send_itip
|
||||
&& event.inner.schedule_tag.is_some()
|
||||
&& event.inner.data.event_range_end() > now
|
||||
{
|
||||
let event = event
|
||||
.deserialize::<CalendarEvent>()
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if let Ok(messages) = itip_cancel(&event.data.event, account_info.addresses(), true) {
|
||||
ItipMessages::new(vec![messages])
|
||||
.queue(batch)
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
}
|
||||
|
||||
batch
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_changed_by(account_info.account_tenant_ids())
|
||||
.with_current(event),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DestroyArchive<Archive<&ArchivedCalendarEventNotification>> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn delete(
|
||||
self,
|
||||
changed_by: AccountTenantIds,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
// Delete event
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEventNotification)
|
||||
.with_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_changed_by(changed_by)
|
||||
.with_current(self.0),
|
||||
)
|
||||
.caused_by(trc::location!())?
|
||||
.commit_point();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarAlarm {
|
||||
pub fn build_write_ops(&self, account_id: u32, document_id: u32) -> [Operation; 2] {
|
||||
let task = match &self.typ {
|
||||
CalendarAlarmType::Email {
|
||||
event_start,
|
||||
event_start_tz,
|
||||
event_end,
|
||||
event_end_tz,
|
||||
} => Task::CalendarAlarmEmail(TaskCalendarAlarmEmail {
|
||||
account_id: account_id.into(),
|
||||
document_id: document_id.into(),
|
||||
alarm_id: self.alarm_id.into(),
|
||||
event_id: self.event_id.into(),
|
||||
event_end: UTCDateTime::from_timestamp(*event_end),
|
||||
event_end_tz: (*event_end_tz).into(),
|
||||
event_start: UTCDateTime::from_timestamp(*event_start),
|
||||
event_start_tz: (*event_start_tz).into(),
|
||||
status: TaskStatus::at(self.alarm_time),
|
||||
}),
|
||||
CalendarAlarmType::Display { recurrence_id } => {
|
||||
Task::CalendarAlarmNotification(TaskCalendarAlarmNotification {
|
||||
account_id: account_id.into(),
|
||||
document_id: document_id.into(),
|
||||
alarm_id: self.alarm_id.into(),
|
||||
event_id: self.event_id.into(),
|
||||
recurrence_id: *recurrence_id,
|
||||
status: TaskStatus::at(self.alarm_time),
|
||||
})
|
||||
}
|
||||
};
|
||||
let id = Id::from_parts(account_id, document_id).id();
|
||||
[
|
||||
Operation::Value {
|
||||
class: ValueClass::TaskQueue(TaskQueueClass::Due {
|
||||
id,
|
||||
due: self.alarm_time as u64,
|
||||
}),
|
||||
op: ValueOp::Set(task.object_type().to_id().serialize()),
|
||||
},
|
||||
Operation::Value {
|
||||
class: ValueClass::TaskQueue(TaskQueueClass::Task { id }),
|
||||
op: ValueOp::Set(task.to_pickled_vec()),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn write_task(&self, batch: &mut BatchBuilder) {
|
||||
let account_id = batch.last_account_id().unwrap();
|
||||
let document_id = batch.last_document_id().unwrap();
|
||||
|
||||
for op in self.build_write_ops(account_id, document_id) {
|
||||
batch.any_op(op);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_task(&self, batch: &mut BatchBuilder) {
|
||||
let account_id = batch.last_account_id().unwrap();
|
||||
let document_id = batch.last_document_id().unwrap();
|
||||
let id = Id::from_parts(account_id, document_id).id();
|
||||
batch
|
||||
.clear(ValueClass::TaskQueue(TaskQueueClass::Task { id }))
|
||||
.clear(ValueClass::TaskQueue(TaskQueueClass::Due {
|
||||
id,
|
||||
due: self.alarm_time as u64,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEvent {
|
||||
pub async fn webcal_uri(
|
||||
&self,
|
||||
server: &Server,
|
||||
account_info: &AccountInfo,
|
||||
) -> trc::Result<String> {
|
||||
for event_name in self.names.iter() {
|
||||
if let Some(calendar_) = server
|
||||
.store()
|
||||
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
|
||||
account_info.account_id(),
|
||||
Collection::Calendar,
|
||||
event_name.parent_id.to_native(),
|
||||
))
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
let calendar = calendar_
|
||||
.unarchive::<Calendar>()
|
||||
.caused_by(trc::location!())?;
|
||||
return Ok(format!(
|
||||
"webcal://{}{}/{}/{}/{}",
|
||||
server.core.network.server_name,
|
||||
DavResourceName::Cal.base_path(),
|
||||
percent_encoding::utf8_percent_encode(account_info.name(), RFC_3986),
|
||||
calendar.name,
|
||||
event_name.name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Err(trc::StoreEvent::UnexpectedError
|
||||
.into_err()
|
||||
.details("Event is not linked to any calendar"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user