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:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+273
View File
@@ -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 (&param.name, &param.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())
}
}
}
}
+298
View File
@@ -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,
}
}
}
+483
View File
@@ -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]);
}
}
+495
View File
@@ -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
+439
View File
@@ -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()),
}
}
}
+634
View File
@@ -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"))
}
}