JMAP for Calendars implementation (part 3)
This commit is contained in:
@@ -11,7 +11,6 @@ common = { path = "../common" }
|
||||
types = { path = "../types" }
|
||||
trc = { path = "../trc" }
|
||||
directory = { path = "../directory" }
|
||||
dav-proto = { path = "../dav-proto" }
|
||||
calcard = { path = "/Users/me/code/calcard", features = ["rkyv"] }
|
||||
hashify = "0.2"
|
||||
tokio = { version = "1.47", features = ["net", "macros"] }
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::calendar::CalendarEventData;
|
||||
use ahash::AHashSet;
|
||||
use calcard::common::timezone::Tz;
|
||||
use chrono::{DateTime, TimeZone};
|
||||
use dav_proto::schema::property::TimeRange;
|
||||
use store::write::bitpack::BitpackIterator;
|
||||
use types::TimeRange;
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -35,6 +35,9 @@ impl ArchivedCalendarEventData {
|
||||
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())?;
|
||||
let is_todo = self.event.components[comp_id as usize]
|
||||
.component_type
|
||||
.is_todo();
|
||||
|
||||
if start_tz.is_floating() && !default_tz.is_floating() {
|
||||
start_tz = default_tz;
|
||||
@@ -65,9 +68,7 @@ impl ArchivedCalendarEventData {
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
if ((start < limit.end) || (start <= limit.start))
|
||||
&& (end > limit.start || end >= limit.end)
|
||||
{
|
||||
if limit.is_in_range(is_todo, start, end) {
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
expansion_id,
|
||||
@@ -97,9 +98,7 @@ impl ArchivedCalendarEventData {
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
if ((start < limit.end) || (start <= limit.start))
|
||||
&& (end > limit.start || end >= limit.end)
|
||||
{
|
||||
if limit.is_in_range(is_todo, start, end) {
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
expansion_id: base_expansion_id,
|
||||
|
||||
@@ -4,14 +4,26 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::calendar::{ArchivedCalendarEventNotification, CalendarEventNotification};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::{
|
||||
ArchivedCalendar, ArchivedCalendarEvent, ArchivedCalendarPreferences, ArchivedDefaultAlert,
|
||||
ArchivedTimezone, Calendar, CalendarEvent, CalendarPreferences, DefaultAlert, Timezone,
|
||||
};
|
||||
use common::storage::index::{IndexValue, IndexableAndSerializableObject, IndexableObject};
|
||||
use crate::calendar::{
|
||||
ArchivedCalendarEventNotification, ArchivedEventPreferences, CalendarEventNotification,
|
||||
EventPreferences,
|
||||
};
|
||||
use calcard::icalendar::{
|
||||
ArchivedICalendarParameterValue, ArchivedICalendarProperty, ArchivedICalendarValue,
|
||||
ICalendarParameterValue, ICalendarProperty, ICalendarValue,
|
||||
};
|
||||
use common::storage::index::{
|
||||
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
|
||||
};
|
||||
use store::backend::MAX_TOKEN_LENGTH;
|
||||
use types::{acl::AclGrant, collection::SyncCollection, field::CalendarField};
|
||||
use utils::sanitize_email;
|
||||
|
||||
impl IndexableObject for Calendar {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
@@ -69,10 +81,39 @@ impl IndexableObject for CalendarEvent {
|
||||
field: CalendarField::Uid.into(),
|
||||
value: self.data.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarField::Start.into(),
|
||||
value: self.data.event_range_start().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarField::Created.into(),
|
||||
value: self.created.into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarField::Updated.into(),
|
||||
value: self.modified.into(),
|
||||
},
|
||||
IndexValue::IndexList {
|
||||
field: CalendarField::Text.into(),
|
||||
value: self
|
||||
.text()
|
||||
.filter_map(|v| {
|
||||
if let Some(email) = v.strip_prefix("mailto:") {
|
||||
sanitize_email(email)
|
||||
} else {
|
||||
Some(v.to_lowercase())
|
||||
}
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect::<HashSet<IndexItem>>()
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
|
||||
+ self.names.iter().map(|n| n.name.len() as u32).sum::<u32>()
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>() as u32
|
||||
+ self.size,
|
||||
},
|
||||
IndexValue::LogItem {
|
||||
@@ -91,10 +132,39 @@ impl IndexableObject for &ArchivedCalendarEvent {
|
||||
field: CalendarField::Uid.into(),
|
||||
value: self.data.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarField::Start.into(),
|
||||
value: self.data.event_range_start().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarField::Created.into(),
|
||||
value: self.created.to_native().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: CalendarField::Updated.into(),
|
||||
value: self.modified.to_native().into(),
|
||||
},
|
||||
IndexValue::IndexList {
|
||||
field: CalendarField::Text.into(),
|
||||
value: self
|
||||
.text()
|
||||
.filter_map(|v| {
|
||||
if let Some(email) = v.strip_prefix("mailto:") {
|
||||
sanitize_email(email)
|
||||
} else {
|
||||
Some(v.to_lowercase())
|
||||
}
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect::<HashSet<IndexItem>>()
|
||||
.into_iter()
|
||||
.collect(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
|
||||
+ self.names.iter().map(|n| n.name.len() as u32).sum::<u32>()
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>() as u32
|
||||
+ self.size,
|
||||
},
|
||||
IndexValue::LogItem {
|
||||
@@ -161,6 +231,7 @@ impl CalendarPreferences {
|
||||
+ 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>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +242,23 @@ impl ArchivedCalendarPreferences {
|
||||
+ 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>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,3 +293,83 @@ impl ArchivedDefaultAlert {
|
||||
std::mem::size_of::<Self>() + self.id.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn text(&self) -> impl Iterator<Item = &str> {
|
||||
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
|
||||
)
|
||||
})
|
||||
})
|
||||
.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,
|
||||
}))
|
||||
})
|
||||
.flat_map(str::split_whitespace)
|
||||
.filter(|s| s.len() < MAX_TOKEN_LENGTH)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEvent {
|
||||
pub fn text(&self) -> impl Iterator<Item = &str> {
|
||||
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
|
||||
)
|
||||
})
|
||||
})
|
||||
.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,
|
||||
}))
|
||||
})
|
||||
.flat_map(str::split_whitespace)
|
||||
.filter(|s| s.len() < MAX_TOKEN_LENGTH)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ pub mod storage;
|
||||
|
||||
use calcard::icalendar::{ICalendar, ICalendarComponent, ICalendarDuration, ICalendarEntry};
|
||||
use common::{DavName, auth::AccessToken};
|
||||
use dav_proto::schema::request::DeadProperty;
|
||||
use types::acl::AclGrant;
|
||||
use types::{acl::AclGrant, dead_property::DeadProperty};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
|
||||
@@ -10,6 +10,7 @@ use common::storage::index::{
|
||||
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use store::backend::MAX_TOKEN_LENGTH;
|
||||
use types::{acl::AclGrant, collection::SyncCollection, field::ContactField};
|
||||
use utils::sanitize_email;
|
||||
|
||||
@@ -199,6 +200,7 @@ impl ContactCard {
|
||||
})
|
||||
.flat_map(|e| e.values.iter().filter_map(|v| v.as_text()))
|
||||
.flat_map(str::split_whitespace)
|
||||
.filter(|s| s.len() < MAX_TOKEN_LENGTH)
|
||||
}
|
||||
|
||||
pub fn emails(&self) -> impl Iterator<Item = String> {
|
||||
@@ -229,6 +231,7 @@ impl ArchivedContactCard {
|
||||
})
|
||||
.flat_map(|e| e.values.iter().filter_map(|v| v.as_text()))
|
||||
.flat_map(str::split_whitespace)
|
||||
.filter(|s| s.len() < MAX_TOKEN_LENGTH)
|
||||
}
|
||||
|
||||
pub fn emails(&self) -> impl Iterator<Item = String> {
|
||||
|
||||
@@ -9,8 +9,7 @@ pub mod storage;
|
||||
|
||||
use calcard::vcard::VCard;
|
||||
use common::{DavName, auth::AccessToken};
|
||||
use dav_proto::schema::request::DeadProperty;
|
||||
use types::acl::AclGrant;
|
||||
use types::{acl::AclGrant, dead_property::DeadProperty};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
pub mod index;
|
||||
pub mod storage;
|
||||
|
||||
use dav_proto::schema::request::DeadProperty;
|
||||
use types::{acl::AclGrant, blob_hash::BlobHash};
|
||||
use types::{acl::AclGrant, blob_hash::BlobHash, dead_property::DeadProperty};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
|
||||
@@ -14,7 +14,6 @@ use calcard::{
|
||||
ICalendarStatus, ICalendarUserTypes, ICalendarValue, Uri,
|
||||
},
|
||||
};
|
||||
use dav_proto::schema::response::CalCondition;
|
||||
use std::{fmt::Display, hash::Hash};
|
||||
|
||||
pub mod attendee;
|
||||
@@ -341,28 +340,6 @@ impl ItipDateTime<'_> {
|
||||
}
|
||||
|
||||
impl ItipError {
|
||||
pub fn failed_precondition(&self) -> Option<CalCondition> {
|
||||
match self {
|
||||
ItipError::MultipleOrganizer => Some(CalCondition::SameOrganizerInAllComponents),
|
||||
ItipError::OrganizerIsLocalAddress
|
||||
| ItipError::SenderIsNotParticipant(_)
|
||||
| ItipError::OrganizerMismatch => Some(CalCondition::ValidOrganizer),
|
||||
ItipError::CannotModifyProperty(_)
|
||||
| ItipError::CannotModifyInstance
|
||||
| ItipError::CannotModifyAddress => Some(CalCondition::AllowedAttendeeObjectChange),
|
||||
ItipError::MissingUid
|
||||
| ItipError::MultipleUid
|
||||
| ItipError::MultipleObjectTypes
|
||||
| ItipError::MultipleObjectInstances
|
||||
| ItipError::MissingMethod
|
||||
| ItipError::InvalidComponentType
|
||||
| ItipError::OutOfSequence
|
||||
| ItipError::UnknownParticipant(_)
|
||||
| ItipError::UnsupportedMethod(_) => Some(CalCondition::ValidSchedulingMessage),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_jmap_error(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user