CalDAV query, freebusy and expand-property reports
This commit is contained in:
262
crates/groupware/src/calendar/dates.rs
Normal file
262
crates/groupware/src/calendar/dates.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{ArchivedCalendarEventData, ArchivedTimezone, CalendarEventData, Timezone};
|
||||
use crate::calendar::ComponentTimeRange;
|
||||
use calcard::{
|
||||
common::timezone::Tz,
|
||||
icalendar::{
|
||||
ICalendar,
|
||||
dates::{CalendarEvent, TimeOrDelta},
|
||||
},
|
||||
};
|
||||
use chrono::{DateTime, TimeZone};
|
||||
use dav_proto::schema::property::TimeRange;
|
||||
use store::{
|
||||
ahash::AHashMap,
|
||||
write::{bitpack::BitpackIterator, key::KeySerializer},
|
||||
};
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
|
||||
impl CalendarEventData {
|
||||
pub fn new(event: ICalendar, max_expansions: usize) -> Self {
|
||||
let mut ranges = TimeRanges::default();
|
||||
|
||||
let expanded = event.expand_dates(Tz::Floating, max_expansions);
|
||||
let mut groups: AHashMap<(u16, u16, u16, i32), Vec<i64>> = 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,
|
||||
)
|
||||
}
|
||||
};
|
||||
ranges.update(start_timestamp_utc, start_timestamp_naive);
|
||||
ranges.update(end_timestamp_utc, end_timestamp_naive);
|
||||
groups
|
||||
.entry((
|
||||
start_tz,
|
||||
end_tz,
|
||||
event.comp_id,
|
||||
(end_timestamp_naive - start_timestamp_naive) 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 {
|
||||
let instances = if instances.len() > 1 {
|
||||
instances.sort_unstable();
|
||||
// Bitpack instances
|
||||
let mut instance_offsets = Vec::with_capacity(instances.len());
|
||||
for instance in instances {
|
||||
instance_offsets.push((ranges.base_offset - instance) as u32);
|
||||
}
|
||||
KeySerializer::new(instance_offsets.len() * std::mem::size_of::<u32>())
|
||||
.bitpack_sorted(&instance_offsets)
|
||||
.finalize()
|
||||
} else {
|
||||
KeySerializer::new(std::mem::size_of::<u32>())
|
||||
.write_leb128((ranges.base_offset - instances.first().unwrap()) as u32)
|
||||
.finalize()
|
||||
};
|
||||
|
||||
events.push(ComponentTimeRange {
|
||||
id,
|
||||
start_tz,
|
||||
end_tz,
|
||||
duration,
|
||||
instances: instances.into_boxed_slice(),
|
||||
});
|
||||
}
|
||||
|
||||
for error in expanded.errors {
|
||||
let todo = "log me";
|
||||
}
|
||||
|
||||
CalendarEventData {
|
||||
event,
|
||||
time_ranges: events.into_boxed_slice(),
|
||||
base_offset: ranges.base_offset,
|
||||
base_time_utc: (ranges.min_time_utc - ranges.base_offset) as u32,
|
||||
duration: (ranges.max_time_utc - ranges.min_time_utc) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarEventData {
|
||||
pub fn expand(&self, default_tz: Tz, limit: TimeRange) -> Option<Vec<CalendarEvent<i64, i64>>> {
|
||||
let mut expansion = Vec::with_capacity(self.time_ranges.len());
|
||||
let base_offset = self.base_offset.to_native();
|
||||
let expansion_limit = limit.start..=limit.end;
|
||||
|
||||
for range in self.time_ranges.iter() {
|
||||
let instances = range.instances.as_ref();
|
||||
let (offset_or_count, bytes_read) = instances.read_leb128::<u32>()?;
|
||||
|
||||
let comp_id = range.id.to_native();
|
||||
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 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();
|
||||
|
||||
if expansion_limit.contains(&start) || expansion_limit.contains(&end) {
|
||||
expansion.push(CalendarEvent {
|
||||
comp_id,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
} else if end > limit.end {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single event
|
||||
let start_date_naive = offset_or_count as i64 + base_offset;
|
||||
let end_date_naive = start_date_naive + duration;
|
||||
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();
|
||||
|
||||
if expansion_limit.contains(&start) || expansion_limit.contains(&end) {
|
||||
expansion.push(CalendarEvent {
|
||||
comp_id,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(expansion)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TimeRanges {
|
||||
max_time_utc: i64,
|
||||
min_time_utc: i64,
|
||||
base_offset: i64,
|
||||
}
|
||||
|
||||
impl TimeRanges {
|
||||
pub fn update(&mut self, utc_timestamp: i64, naive_timestamp: i64) {
|
||||
if utc_timestamp > self.max_time_utc {
|
||||
self.max_time_utc = utc_timestamp;
|
||||
}
|
||||
if utc_timestamp < self.min_time_utc || self.max_time_utc == 0 {
|
||||
self.min_time_utc = utc_timestamp;
|
||||
}
|
||||
let offset = std::cmp::min(utc_timestamp, naive_timestamp);
|
||||
if offset < self.base_offset || self.base_offset == 0 {
|
||||
self.base_offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Timezone {
|
||||
pub fn tz(&self) -> Option<Tz> {
|
||||
match self {
|
||||
Timezone::IANA(iana) => Tz::from_str(iana).ok(),
|
||||
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_str(iana).ok(),
|
||||
ArchivedTimezone::Custom(icalendar) => icalendar
|
||||
.timezones()
|
||||
.filter_map(|t| t.timezone().map(|x| x.1))
|
||||
.next(),
|
||||
ArchivedTimezone::Default => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,9 @@ use common::storage::index::{
|
||||
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
|
||||
};
|
||||
use jmap_proto::types::{collection::Collection, value::AclGrant};
|
||||
use store::SerializeInfallible;
|
||||
use store::{SerializeInfallible, write::key::KeySerializer};
|
||||
|
||||
use crate::{IDX_NAME, IDX_UID};
|
||||
use crate::{IDX_NAME, IDX_TIME, IDX_UID};
|
||||
|
||||
use super::{
|
||||
ArchivedCalendar, ArchivedCalendarEvent, ArchivedCalendarPreferences, ArchivedDefaultAlert,
|
||||
@@ -26,6 +26,15 @@ impl IndexableObject for Calendar {
|
||||
field: IDX_NAME,
|
||||
value: self.name.as_str().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_TIME,
|
||||
value: self
|
||||
.preferences
|
||||
.first()
|
||||
.and_then(|p| p.time_zone.tz())
|
||||
.map(|tz| tz.as_id().serialize())
|
||||
.into(),
|
||||
},
|
||||
IndexValue::Acl {
|
||||
value: (&self.acls).into(),
|
||||
},
|
||||
@@ -48,6 +57,15 @@ impl IndexableObject for &ArchivedCalendar {
|
||||
field: IDX_NAME,
|
||||
value: self.name.as_str().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_TIME,
|
||||
value: self
|
||||
.preferences
|
||||
.first()
|
||||
.and_then(|p| p.time_zone.tz())
|
||||
.map(|tz| tz.as_id().serialize())
|
||||
.into(),
|
||||
},
|
||||
IndexValue::Acl {
|
||||
value: self
|
||||
.acls
|
||||
@@ -83,7 +101,20 @@ impl IndexableObject for CalendarEvent {
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_UID,
|
||||
value: self.event.uids().next().into(),
|
||||
value: self.data.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_TIME,
|
||||
value: self
|
||||
.data
|
||||
.event_range()
|
||||
.map(|(start, duration)| {
|
||||
KeySerializer::new(std::mem::size_of::<i64>() + std::mem::size_of::<u32>())
|
||||
.write(start as u64)
|
||||
.write(duration)
|
||||
.finalize()
|
||||
})
|
||||
.into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
@@ -114,7 +145,20 @@ impl IndexableObject for &ArchivedCalendarEvent {
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_UID,
|
||||
value: self.event.uids().next().into(),
|
||||
value: self.data.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_TIME,
|
||||
value: self
|
||||
.data
|
||||
.event_range()
|
||||
.map(|(start, duration)| {
|
||||
KeySerializer::new(std::mem::size_of::<i64>() + std::mem::size_of::<u32>())
|
||||
.write(start as u64)
|
||||
.write(duration)
|
||||
.finalize()
|
||||
})
|
||||
.into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod dates;
|
||||
pub mod index;
|
||||
pub mod storage;
|
||||
|
||||
@@ -67,7 +68,7 @@ pub const EVENT_ORIGIN: u16 = 1 << 4;
|
||||
pub struct CalendarEvent {
|
||||
pub names: Vec<DavName>,
|
||||
pub display_name: Option<String>,
|
||||
pub event: ICalendar,
|
||||
pub data: CalendarEventData,
|
||||
pub user_properties: Vec<UserProperties>,
|
||||
pub flags: u16,
|
||||
pub dead_properties: DeadProperty,
|
||||
@@ -76,6 +77,28 @@ pub struct CalendarEvent {
|
||||
pub modified: i64,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarEventData {
|
||||
pub event: ICalendar,
|
||||
pub time_ranges: Box<[ComponentTimeRange]>,
|
||||
pub base_offset: i64,
|
||||
pub base_time_utc: u32,
|
||||
pub duration: u32,
|
||||
}
|
||||
|
||||
#[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,
|
||||
)]
|
||||
|
||||
@@ -4,27 +4,28 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::{DavResource, DavResourceId, DavResources, Server, auth::AccessToken};
|
||||
use crate::{
|
||||
DavName, DavResourceName, IDX_NAME, IDX_TIME,
|
||||
calendar::{Calendar, CalendarPreferences},
|
||||
contact::AddressBook,
|
||||
file::FileNode,
|
||||
};
|
||||
use calcard::common::timezone::Tz;
|
||||
use common::{
|
||||
DavResource, DavResourceId, DavResourceMetadata, DavResources, Server, auth::AccessToken,
|
||||
};
|
||||
use directory::backend::internal::manage::ManageDirectory;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use percent_encoding::NON_ALPHANUMERIC;
|
||||
use std::sync::Arc;
|
||||
use store::{
|
||||
Deserialize, IndexKey, IndexKeyPrefix, IterateParams, SerializeInfallible, U32_LEN,
|
||||
Deserialize, IndexKey, IndexKeyPrefix, IterateParams, SerializeInfallible, U32_LEN, U64_LEN,
|
||||
ahash::AHashMap,
|
||||
write::{BatchBuilder, key::DeserializeBigEndian},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use utils::bimap::IdBimap;
|
||||
|
||||
use crate::{
|
||||
DavName, DavResourceName, IDX_NAME,
|
||||
calendar::{Calendar, CalendarPreferences},
|
||||
contact::AddressBook,
|
||||
file::FileNode,
|
||||
};
|
||||
|
||||
pub trait DavHierarchy: Sync + Send {
|
||||
fn fetch_dav_resources(
|
||||
&self,
|
||||
@@ -44,6 +45,12 @@ pub trait DavHierarchy: Sync + Send {
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
) -> impl Future<Output = trc::Result<()>> + Send;
|
||||
|
||||
fn cached_dav_resources(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
) -> Option<Arc<DavResources>>;
|
||||
}
|
||||
|
||||
impl DavHierarchy for Server {
|
||||
@@ -154,18 +161,36 @@ impl DavHierarchy for Server {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cached_dav_resources(
|
||||
&self,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
) -> Option<Arc<DavResources>> {
|
||||
self.inner
|
||||
.cache
|
||||
.dav
|
||||
.get(&DavResourceId {
|
||||
account_id,
|
||||
collection: collection.into(),
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_hierarchy(
|
||||
server: &Server,
|
||||
account_id: u32,
|
||||
collection: Collection,
|
||||
collection_: Collection,
|
||||
) -> trc::Result<DavResources> {
|
||||
let base_path = DavResourceName::from(collection).base_path();
|
||||
let collection = u8::from(collection);
|
||||
let base_path = DavResourceName::from(collection_).base_path();
|
||||
let collection = u8::from(collection_);
|
||||
let mut containers: AHashMap<u32, String> = AHashMap::with_capacity(16);
|
||||
let mut resources: AHashMap<u32, Vec<DavName>> = AHashMap::with_capacity(16);
|
||||
|
||||
let mut time_ranges: AHashMap<u32, (i64, u32)> = AHashMap::new();
|
||||
let mut time_zones: AHashMap<u32, Tz> = AHashMap::new();
|
||||
|
||||
server
|
||||
.store()
|
||||
.iterate(
|
||||
@@ -181,7 +206,7 @@ async fn build_hierarchy(
|
||||
account_id,
|
||||
collection: collection + 1,
|
||||
document_id: u32::MAX,
|
||||
field: IDX_NAME,
|
||||
field: IDX_TIME,
|
||||
key: u32::MAX.serialize(),
|
||||
},
|
||||
)
|
||||
@@ -196,19 +221,39 @@ async fn build_hierarchy(
|
||||
.get(U32_LEN)
|
||||
.copied()
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
|
||||
let field = key
|
||||
.get(U32_LEN + 1)
|
||||
.copied()
|
||||
.ok_or_else(|| trc::Error::corrupted_key(key, None, trc::location!()))?;
|
||||
|
||||
if key_collection == collection {
|
||||
containers.insert(
|
||||
document_id,
|
||||
std::str::from_utf8(value)
|
||||
.map_err(|_| trc::Error::corrupted_key(key, None, trc::location!()))?
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
if field == IDX_NAME {
|
||||
containers.insert(
|
||||
document_id,
|
||||
std::str::from_utf8(value)
|
||||
.map_err(|_| {
|
||||
trc::Error::corrupted_key(key, None, trc::location!())
|
||||
})?
|
||||
.to_string(),
|
||||
);
|
||||
} else if field == IDX_TIME {
|
||||
let tz = Tz::from_id(key.deserialize_be_u16(IndexKeyPrefix::len())?)
|
||||
.ok_or_else(|| {
|
||||
trc::Error::corrupted_key(key, None, trc::location!())
|
||||
})?;
|
||||
|
||||
time_zones.insert(document_id, tz);
|
||||
}
|
||||
} else if field == IDX_NAME {
|
||||
resources.entry(document_id).or_default().push(
|
||||
DavName::deserialize(value)
|
||||
.map_err(|_| trc::Error::corrupted_key(key, None, trc::location!()))?,
|
||||
);
|
||||
} else if field == IDX_TIME {
|
||||
let start_time = key.deserialize_be_u64(IndexKeyPrefix::len())?;
|
||||
let duration = key.deserialize_be_u32(IndexKeyPrefix::len() + U64_LEN)?;
|
||||
|
||||
time_ranges.insert(document_id, (start_time as i64, duration));
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
@@ -246,9 +291,13 @@ async fn build_hierarchy(
|
||||
document_id,
|
||||
parent_id: dav_name.parent_id.into(),
|
||||
name,
|
||||
size: 0,
|
||||
is_container: false,
|
||||
hierarchy_sequence: 1,
|
||||
data: time_ranges
|
||||
.get(&document_id)
|
||||
.map(|(start, duration)| DavResourceMetadata::CalendarEvent {
|
||||
start: *start,
|
||||
duration: *duration,
|
||||
})
|
||||
.unwrap_or(DavResourceMetadata::None),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -261,9 +310,10 @@ async fn build_hierarchy(
|
||||
document_id,
|
||||
parent_id: None,
|
||||
name,
|
||||
size: 0,
|
||||
is_container: true,
|
||||
hierarchy_sequence: 0,
|
||||
data: time_zones
|
||||
.get(&document_id)
|
||||
.map(|tz| DavResourceMetadata::Calendar { tz: *tz })
|
||||
.unwrap_or(DavResourceMetadata::None),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,9 +350,11 @@ async fn build_file_hierarchy(server: &Server, account_id: u32) -> trc::Result<D
|
||||
document_id: expanded.document_id,
|
||||
parent_id: expanded.parent_id,
|
||||
name: expanded.name,
|
||||
size: expanded.size,
|
||||
is_container: expanded.is_container,
|
||||
hierarchy_sequence: expanded.hierarchy_sequence,
|
||||
data: DavResourceMetadata::File {
|
||||
size: expanded.size,
|
||||
hierarchy_sequence: expanded.hierarchy_sequence,
|
||||
is_container: expanded.is_container,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use calcard::common::timezone::Tz;
|
||||
use common::DavResources;
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use store::{Deserialize, SerializeInfallible, write::key::KeySerializer};
|
||||
use utils::codec::leb128::Leb128Reader;
|
||||
@@ -14,7 +16,8 @@ pub mod file;
|
||||
pub mod hierarchy;
|
||||
|
||||
pub const IDX_NAME: u8 = 0;
|
||||
pub const IDX_UID: u8 = 1;
|
||||
pub const IDX_TIME: u8 = 1;
|
||||
pub const IDX_UID: u8 = 2;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DavResourceName {
|
||||
@@ -132,3 +135,25 @@ impl From<Collection> for DavResourceName {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DavCalendarResource {
|
||||
fn calendar_default_tz(&self, calendar_id: u32) -> Option<Tz>;
|
||||
fn event_default_tz(&self, event_id: u32) -> Option<Tz>;
|
||||
}
|
||||
|
||||
impl DavCalendarResource for DavResources {
|
||||
fn calendar_default_tz(&self, calendar_id: u32) -> Option<Tz> {
|
||||
self.paths
|
||||
.iter()
|
||||
.find(|c| c.is_container() && c.document_id == calendar_id)
|
||||
.and_then(|c| c.timezone())
|
||||
}
|
||||
|
||||
fn event_default_tz(&self, event_id: u32) -> Option<Tz> {
|
||||
self.paths
|
||||
.iter()
|
||||
.find(|c| !c.is_container() && c.document_id == event_id)
|
||||
.and_then(|c| c.parent_id)
|
||||
.and_then(|parent_id| self.calendar_default_tz(parent_id))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user