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,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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user