JMAP for Calendars implementation (part 2)
This commit is contained in:
@@ -4,18 +4,28 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use calcard::{common::timezone::Tz, icalendar::dates::CalendarEvent};
|
||||
use super::ArchivedCalendarEventData;
|
||||
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 utils::codec::leb128::Leb128Reader;
|
||||
|
||||
use super::ArchivedCalendarEventData;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CalendarEventExpansion {
|
||||
pub comp_id: u32,
|
||||
pub expansion_id: u32,
|
||||
pub start: i64,
|
||||
pub end: i64,
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEventData {
|
||||
pub fn expand(&self, default_tz: Tz, limit: TimeRange) -> Option<Vec<CalendarEvent<i64, i64>>> {
|
||||
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();
|
||||
let mut base_expansion_id = 0;
|
||||
|
||||
'outer: for range in self.time_ranges.iter() {
|
||||
let instances = range.instances.as_ref();
|
||||
@@ -37,6 +47,8 @@ impl ArchivedCalendarEventData {
|
||||
// Recurring event
|
||||
let unpacker =
|
||||
BitpackIterator::from_bytes_and_offset(instances, bytes_read, offset_or_count);
|
||||
let mut expansion_id = base_expansion_id;
|
||||
base_expansion_id += 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;
|
||||
@@ -56,14 +68,17 @@ impl ArchivedCalendarEventData {
|
||||
if ((start < limit.end) || (start <= limit.start))
|
||||
&& (end > limit.start || end >= limit.end)
|
||||
{
|
||||
expansion.push(CalendarEvent {
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
expansion_id,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
} else if start > limit.end {
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
expansion_id += 1;
|
||||
}
|
||||
} else {
|
||||
// Single event
|
||||
@@ -85,15 +100,201 @@ impl ArchivedCalendarEventData {
|
||||
if ((start < limit.end) || (start <= limit.start))
|
||||
&& (end > limit.start || end >= limit.end)
|
||||
{
|
||||
expansion.push(CalendarEvent {
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id,
|
||||
expansion_id: base_expansion_id,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
|
||||
base_expansion_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Some(expansion)
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventData {
|
||||
pub fn expand_from_ids(
|
||||
&self,
|
||||
expansion_ids: &mut AHashSet<u32>,
|
||||
default_tz: Tz,
|
||||
) -> Option<Vec<CalendarEventExpansion>> {
|
||||
let mut expansion = Vec::with_capacity(expansion_ids.len());
|
||||
let base_offset = self.base_offset;
|
||||
let mut base_expansion_id = 0;
|
||||
|
||||
'outer: for range in self.time_ranges.iter() {
|
||||
let instances = range.instances.as_ref();
|
||||
let (offset_or_count, bytes_read) = instances.read_leb128::<u32>()?;
|
||||
let mut start_tz = Tz::from_id(range.start_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;
|
||||
}
|
||||
|
||||
if instances.len() > bytes_read {
|
||||
let match_range = base_expansion_id..base_expansion_id + offset_or_count;
|
||||
let mut match_count = expansion_ids
|
||||
.iter()
|
||||
.filter(|id| match_range.contains(id))
|
||||
.count();
|
||||
let mut expansion_id = base_expansion_id;
|
||||
base_expansion_id += offset_or_count;
|
||||
|
||||
if match_count > 0 {
|
||||
let unpacker = BitpackIterator::from_bytes_and_offset(
|
||||
instances,
|
||||
bytes_read,
|
||||
offset_or_count,
|
||||
);
|
||||
for start_offset in unpacker {
|
||||
if expansion_ids.remove(&expansion_id) {
|
||||
let start_date_naive = start_offset as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + range.duration as i64;
|
||||
let start = start_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(start_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
let end = end_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(end_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id: range.id as u32,
|
||||
expansion_id,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
match_count -= 1;
|
||||
if match_count == 0 {
|
||||
if expansion_ids.is_empty() {
|
||||
break 'outer;
|
||||
} else {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
expansion_id += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if expansion_ids.remove(&base_expansion_id) {
|
||||
// Single event
|
||||
let start_date_naive = offset_or_count as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + range.duration as i64;
|
||||
let start = start_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(start_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
let end = end_tz
|
||||
.from_local_datetime(
|
||||
&DateTime::from_timestamp(end_date_naive, 0)?.naive_local(),
|
||||
)
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
expansion.push(CalendarEventExpansion {
|
||||
comp_id: range.id as u32,
|
||||
expansion_id: base_expansion_id,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
if expansion_ids.is_empty() {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
base_expansion_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !expansion_ids.is_empty() {
|
||||
expansion.extend(
|
||||
expansion_ids
|
||||
.drain()
|
||||
.map(|expansion_id| CalendarEventExpansion {
|
||||
comp_id: u32::MAX,
|
||||
expansion_id,
|
||||
start: i64::MAX,
|
||||
end: i64::MAX,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
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 mut start_tz = Tz::from_id(range.start_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 {
|
||||
// Recurring event
|
||||
let mut unpacker =
|
||||
BitpackIterator::from_bytes_and_offset(instances, bytes_read, offset_or_count);
|
||||
unpacker.next()?
|
||||
} else {
|
||||
// Single event
|
||||
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 = start_tz
|
||||
.from_local_datetime(&DateTime::from_timestamp(start_date_naive, 0)?.naive_local())
|
||||
.single()?
|
||||
.timestamp();
|
||||
let end = end_tz
|
||||
.from_local_datetime(&DateTime::from_timestamp(end_date_naive, 0)?.naive_local())
|
||||
.single()?
|
||||
.timestamp();
|
||||
|
||||
Some(CalendarEventExpansion {
|
||||
comp_id,
|
||||
expansion_id: u32::MAX,
|
||||
start,
|
||||
end,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalendarEventExpansion {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
comp_id: u32::MAX,
|
||||
expansion_id: u32::MAX,
|
||||
start: i64::MAX,
|
||||
end: i64::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEventExpansion {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.comp_id != u32::MAX && self.start != i64::MAX && self.end != i64::MAX
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,11 @@ impl ItipIngest for Server {
|
||||
// Build event for schedule inbox
|
||||
let itip_document_id = self
|
||||
.store()
|
||||
.assign_document_ids(account_id, Collection::CalendarEventNotification, 1)
|
||||
.assign_document_ids(
|
||||
account_id,
|
||||
Collection::CalendarEventNotification,
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
let itip_message = CalendarEventNotification {
|
||||
@@ -412,14 +416,8 @@ impl ItipIngest for Server {
|
||||
'outer: for entry in &mut component.entries {
|
||||
if entry.name == ICalendarProperty::Attendee
|
||||
&& entry
|
||||
.values
|
||||
.first()
|
||||
.and_then(|v| v.as_text())
|
||||
.is_some_and(|v| {
|
||||
v.strip_prefix("mailto:")
|
||||
.unwrap_or(v)
|
||||
.eq_ignore_ascii_case(&rsvp.attendee)
|
||||
})
|
||||
.calendar_address()
|
||||
.is_some_and(|v| v.eq_ignore_ascii_case(&rsvp.attendee))
|
||||
{
|
||||
let mut add_partstat = true;
|
||||
for param in &mut entry.params {
|
||||
|
||||
@@ -11,7 +11,7 @@ pub mod index;
|
||||
pub mod itip;
|
||||
pub mod storage;
|
||||
|
||||
use calcard::icalendar::{ICalendar, ICalendarDuration};
|
||||
use calcard::icalendar::{ICalendar, ICalendarComponent, ICalendarDuration, ICalendarEntry};
|
||||
use common::{DavName, auth::AccessToken};
|
||||
use dav_proto::schema::request::DeadProperty;
|
||||
use types::acl::AclGrant;
|
||||
@@ -29,10 +29,9 @@ pub struct Calendar {
|
||||
}
|
||||
|
||||
pub const CALENDAR_SUBSCRIBED: u16 = 1;
|
||||
pub const CALENDAR_DEFAULT: u16 = 1 << 1;
|
||||
pub const CALENDAR_VISIBLE: u16 = 1 << 2;
|
||||
pub const CALENDAR_AVAILABILITY_ALL: u16 = 1 << 3;
|
||||
pub const CALENDAR_AVAILABILITY_ATTENDING: u16 = 1 << 4;
|
||||
pub const CALENDAR_INVISIBLE: u16 = 1 << 1;
|
||||
pub const CALENDAR_AVAILABILITY_NONE: u16 = 1 << 2;
|
||||
pub const CALENDAR_AVAILABILITY_ATTENDING: u16 = 1 << 3;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
@@ -52,7 +51,6 @@ pub struct CalendarPreferences {
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct DefaultAlert {
|
||||
pub account_id: u32,
|
||||
pub id: String,
|
||||
pub offset: ICalendarDuration,
|
||||
pub flags: u16,
|
||||
@@ -71,6 +69,8 @@ pub const EVENT_HIDE_ATTENDEES: u16 = 1 << 2;
|
||||
pub const EVENT_DRAFT: u16 = 1 << 3;
|
||||
pub const EVENT_ORIGIN: u16 = 1 << 4;
|
||||
|
||||
pub const PREF_USE_DEFAULT_ALERTS: u16 = 1;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
@@ -78,7 +78,7 @@ pub struct CalendarEvent {
|
||||
pub names: Vec<DavName>,
|
||||
pub display_name: Option<String>,
|
||||
pub data: CalendarEventData,
|
||||
pub user_properties: Vec<UserProperties>,
|
||||
pub preferences: Vec<EventPreferences>,
|
||||
pub flags: u16,
|
||||
pub dead_properties: DeadProperty,
|
||||
pub size: u32,
|
||||
@@ -143,9 +143,11 @@ pub struct ComponentTimeRange {
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct UserProperties {
|
||||
pub struct EventPreferences {
|
||||
pub account_id: u32,
|
||||
pub properties: ICalendar,
|
||||
pub flags: u16,
|
||||
pub properties: Vec<ICalendarEntry>,
|
||||
pub alerts: Vec<ICalendarComponent>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -192,6 +194,17 @@ impl Calendar {
|
||||
}
|
||||
|
||||
impl ArchivedCalendar {
|
||||
pub fn default_alerts(
|
||||
&self,
|
||||
access_token: &AccessToken,
|
||||
with_time: bool,
|
||||
) -> impl Iterator<Item = &ArchivedDefaultAlert> {
|
||||
self.preferences(access_token)
|
||||
.default_alerts
|
||||
.iter()
|
||||
.filter(move |a| (a.flags & ALERT_WITH_TIME != 0) == with_time)
|
||||
}
|
||||
|
||||
pub fn preferences(&self, access_token: &AccessToken) -> &ArchivedCalendarPreferences {
|
||||
if self.preferences.len() == 1 {
|
||||
&self.preferences[0]
|
||||
@@ -205,3 +218,71 @@ impl ArchivedCalendar {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn preferences(&self, access_token: &AccessToken) -> Option<&EventPreferences> {
|
||||
self.preferences
|
||||
.iter()
|
||||
.find(|p| p.account_id == access_token.primary_id())
|
||||
}
|
||||
|
||||
pub fn preferences_mut(&mut self, access_token: &AccessToken) -> &mut EventPreferences {
|
||||
let account_id = access_token.primary_id();
|
||||
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: PREF_USE_DEFAULT_ALERTS,
|
||||
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, access_token: &AccessToken) -> Option<&ArchivedEventPreferences> {
|
||||
self.preferences
|
||||
.iter()
|
||||
.find(|p| p.account_id == access_token.primary_id())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,24 +351,23 @@ impl DestroyArchive<Archive<&ArchivedCalendarEvent>> {
|
||||
send_itip: bool,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
let event = self.0;
|
||||
if let Some(delete_idx) = event
|
||||
if let Some(delete_idx) = self
|
||||
.0
|
||||
.inner
|
||||
.names
|
||||
.iter()
|
||||
.position(|name| name.parent_id == calendar_id)
|
||||
{
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent);
|
||||
|
||||
if event.inner.names.len() > 1 {
|
||||
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)
|
||||
.update_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
@@ -378,40 +377,7 @@ impl DestroyArchive<Archive<&ArchivedCalendarEvent>> {
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
} else {
|
||||
// Delete event
|
||||
batch.delete_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, access_token.emails.as_slice(), true)
|
||||
{
|
||||
ItipMessages::new(vec![messages])
|
||||
.queue(batch)
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
}
|
||||
|
||||
batch
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_tenant_id(access_token)
|
||||
.with_current(event),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
self.delete_all(access_token, account_id, document_id, send_itip, batch)?;
|
||||
}
|
||||
|
||||
if let Some(delete_path) = delete_path {
|
||||
@@ -423,6 +389,57 @@ impl DestroyArchive<Archive<&ArchivedCalendarEvent>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn delete_all(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
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)
|
||||
.delete_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, access_token.emails.as_slice(), true)
|
||||
{
|
||||
ItipMessages::new(vec![messages])
|
||||
.queue(batch)
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
}
|
||||
|
||||
batch
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_tenant_id(access_token)
|
||||
.with_current(event),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DestroyArchive<Archive<&ArchivedCalendarEventNotification>> {
|
||||
|
||||
@@ -362,6 +362,28 @@ impl ItipError {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_jmap_error(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ItipError::MultipleOrganizer
|
||||
| ItipError::OrganizerIsLocalAddress
|
||||
| ItipError::SenderIsNotParticipant(_)
|
||||
| ItipError::OrganizerMismatch
|
||||
| ItipError::CannotModifyProperty(_)
|
||||
| ItipError::CannotModifyInstance
|
||||
| ItipError::CannotModifyAddress
|
||||
//| ItipError::MissingUid
|
||||
| ItipError::MultipleUid
|
||||
| ItipError::MultipleObjectTypes
|
||||
| ItipError::MultipleObjectInstances
|
||||
| ItipError::MissingMethod
|
||||
| ItipError::InvalidComponentType
|
||||
| ItipError::OutOfSequence
|
||||
| ItipError::UnknownParticipant(_)
|
||||
| ItipError::UnsupportedMethod(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ItipError {
|
||||
|
||||
Reference in New Issue
Block a user