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,198 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::changes::state::JmapCacheState;
|
||||
use calcard::{
|
||||
icalendar::{ArchivedICalendarProperty, ICalendar},
|
||||
jscalendar::import::ConversionOptions,
|
||||
};
|
||||
use common::{Server, auth::AccessToken};
|
||||
use groupware::{
|
||||
cache::GroupwareCache,
|
||||
calendar::{
|
||||
ArchivedChangedBy, CalendarEventNotification, EVENT_NOTIFICATION_IS_CHANGE,
|
||||
EVENT_NOTIFICATION_IS_DRAFT,
|
||||
},
|
||||
};
|
||||
use jmap_proto::{
|
||||
method::get::GetRequest,
|
||||
object::calendar_event_notification::{
|
||||
self, CalendarEventNotificationGetResponse, CalendarEventNotificationObject,
|
||||
CalendarEventNotificationProperty, CalendarEventNotificationType, PersonObject,
|
||||
},
|
||||
types::date::UTCDate,
|
||||
};
|
||||
use store::{
|
||||
ValueKey,
|
||||
write::{AlignedBytes, Archive, serialize::rkyv_deserialize},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
blob::BlobId,
|
||||
collection::{Collection, SyncCollection},
|
||||
id::Id,
|
||||
};
|
||||
|
||||
pub trait CalendarEventNotificationGet: Sync + Send {
|
||||
fn calendar_event_notification_get(
|
||||
&self,
|
||||
request: GetRequest<calendar_event_notification::CalendarEventNotification>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<CalendarEventNotificationGetResponse>> + Send;
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationGet for Server {
|
||||
async fn calendar_event_notification_get(
|
||||
&self,
|
||||
mut request: GetRequest<calendar_event_notification::CalendarEventNotification>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<CalendarEventNotificationGetResponse> {
|
||||
let (ids, not_found_ids) = request.unwrap_ids(self.core.jmap.get_max_objects)?;
|
||||
let properties = request.unwrap_properties(&[
|
||||
CalendarEventNotificationProperty::Id,
|
||||
CalendarEventNotificationProperty::Created,
|
||||
CalendarEventNotificationProperty::Type,
|
||||
CalendarEventNotificationProperty::ChangedBy,
|
||||
]);
|
||||
let account_id = request.account_id.document_id();
|
||||
let cache = self
|
||||
.fetch_dav_resources(
|
||||
access_token.account_id(),
|
||||
account_id,
|
||||
SyncCollection::CalendarEventNotification,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
let ids = if let Some(ids) = ids {
|
||||
ids
|
||||
} else {
|
||||
cache
|
||||
.document_ids(false)
|
||||
.take(self.core.jmap.get_max_objects)
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let mut response = CalendarEventNotificationGetResponse {
|
||||
account_id: request.account_id.into(),
|
||||
state: cache.get_state(false).into(),
|
||||
list: Vec::with_capacity(ids.len()),
|
||||
not_found: not_found_ids,
|
||||
};
|
||||
|
||||
for id in ids {
|
||||
// Obtain the event object
|
||||
let document_id = id.document_id();
|
||||
let _event = if let Some(event) = self
|
||||
.store()
|
||||
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
|
||||
account_id,
|
||||
Collection::CalendarEventNotification,
|
||||
document_id,
|
||||
))
|
||||
.await?
|
||||
{
|
||||
event
|
||||
} else {
|
||||
response.push_not_found(id);
|
||||
continue;
|
||||
};
|
||||
let event = _event
|
||||
.unarchive::<CalendarEventNotification>()
|
||||
.caused_by(trc::location!())?;
|
||||
let mut result = CalendarEventNotificationObject {
|
||||
id,
|
||||
..Default::default()
|
||||
};
|
||||
for property in &properties {
|
||||
match property {
|
||||
CalendarEventNotificationProperty::Id => {}
|
||||
CalendarEventNotificationProperty::Created => {
|
||||
result.created = Some(UTCDate::from_timestamp(event.created.to_native()));
|
||||
}
|
||||
CalendarEventNotificationProperty::CalendarEventId => {
|
||||
result.calendar_event_id =
|
||||
event.event_id.as_ref().map(|id| id.to_native().into());
|
||||
}
|
||||
CalendarEventNotificationProperty::ChangedBy => {
|
||||
let mut changed_by = PersonObject::default();
|
||||
|
||||
match &event.changed_by {
|
||||
ArchivedChangedBy::PrincipalId(id) => {
|
||||
if let Ok(account) = self.account(id.to_native()).await {
|
||||
changed_by.name =
|
||||
account.description().unwrap_or(account.name()).to_string();
|
||||
changed_by.email = account.name().to_string().into();
|
||||
}
|
||||
changed_by.principal_id = Some(id.to_native().into());
|
||||
}
|
||||
ArchivedChangedBy::CalendarAddress(email) => {
|
||||
changed_by.email = Some(email.to_string());
|
||||
changed_by.calendar_address = Some(format!("mailto:{email}"));
|
||||
}
|
||||
}
|
||||
|
||||
result.changed_by = Some(changed_by);
|
||||
}
|
||||
CalendarEventNotificationProperty::Comment => {
|
||||
result.comment = event
|
||||
.event
|
||||
.components
|
||||
.iter()
|
||||
.filter(|c| c.component_type.is_scheduling_object())
|
||||
.flat_map(|c| c.entries.iter())
|
||||
.find(|e| matches!(e.name, ArchivedICalendarProperty::Comment))
|
||||
.and_then(|e| e.values.first().and_then(|v| v.as_text()))
|
||||
.map(|v| v.to_string());
|
||||
}
|
||||
CalendarEventNotificationProperty::Type => {
|
||||
result.notification_type =
|
||||
Some(if event.flags & EVENT_NOTIFICATION_IS_CHANGE != 0 {
|
||||
CalendarEventNotificationType::Updated
|
||||
} else if !event.event.components.is_empty() {
|
||||
CalendarEventNotificationType::Created
|
||||
} else {
|
||||
CalendarEventNotificationType::Destroyed
|
||||
});
|
||||
}
|
||||
CalendarEventNotificationProperty::IsDraft => {
|
||||
result.is_draft = Some(event.flags & EVENT_NOTIFICATION_IS_DRAFT != 0);
|
||||
}
|
||||
CalendarEventNotificationProperty::Event => {
|
||||
if event.flags & EVENT_NOTIFICATION_IS_CHANGE == 0 && result.event.is_none()
|
||||
{
|
||||
let js_event = rkyv_deserialize::<_, ICalendar>(&event.event)
|
||||
.caused_by(trc::location!())?
|
||||
.into_jscalendar_with_opt::<Id, BlobId>(
|
||||
ConversionOptions::default()
|
||||
.include_ical_components(false)
|
||||
.return_first(true),
|
||||
);
|
||||
result.event = js_event.into();
|
||||
}
|
||||
}
|
||||
CalendarEventNotificationProperty::EventPatch => {
|
||||
if event.flags & EVENT_NOTIFICATION_IS_CHANGE != 0
|
||||
&& result.event_patch.is_none()
|
||||
{
|
||||
let js_event = rkyv_deserialize::<_, ICalendar>(&event.event)
|
||||
.caused_by(trc::location!())?
|
||||
.into_jscalendar_with_opt::<Id, BlobId>(
|
||||
ConversionOptions::default()
|
||||
.include_ical_components(false)
|
||||
.return_first(true),
|
||||
);
|
||||
result.event_patch = js_event.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response.list.push(result);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod get;
|
||||
pub mod query;
|
||||
pub mod set;
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{api::query::QueryResponseBuilder, changes::state::JmapCacheState};
|
||||
use common::{Server, auth::AccessToken};
|
||||
use groupware::cache::GroupwareCache;
|
||||
use jmap_proto::{
|
||||
method::query::{Filter, QueryRequest, QueryResponse},
|
||||
object::calendar_event_notification::{
|
||||
CalendarEventNotification, CalendarEventNotificationComparator,
|
||||
CalendarEventNotificationFilter,
|
||||
},
|
||||
request::IntoValid,
|
||||
};
|
||||
use store::{
|
||||
IterateParams, U32_LEN, U64_LEN, ValueKey,
|
||||
ahash::AHashSet,
|
||||
roaring::RoaringBitmap,
|
||||
search::{SearchFilter, SearchQuery},
|
||||
write::{IndexPropertyClass, SearchIndex, ValueClass, key::DeserializeBigEndian},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::{
|
||||
collection::{Collection, SyncCollection},
|
||||
field::CalendarNotificationField,
|
||||
};
|
||||
|
||||
pub trait CalendarEventNotificationQuery: Sync + Send {
|
||||
fn calendar_event_notification_query(
|
||||
&self,
|
||||
request: QueryRequest<CalendarEventNotification>,
|
||||
access_token: &AccessToken,
|
||||
) -> impl Future<Output = trc::Result<QueryResponse>> + Send;
|
||||
}
|
||||
|
||||
struct Notification {
|
||||
document_id: u32,
|
||||
created: u64,
|
||||
event_id: u32,
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationQuery for Server {
|
||||
async fn calendar_event_notification_query(
|
||||
&self,
|
||||
mut request: QueryRequest<CalendarEventNotification>,
|
||||
access_token: &AccessToken,
|
||||
) -> trc::Result<QueryResponse> {
|
||||
let account_id = request.account_id.document_id();
|
||||
let mut filters = Vec::with_capacity(request.filter.len());
|
||||
let cache = self
|
||||
.fetch_dav_resources(
|
||||
access_token.account_id(),
|
||||
account_id,
|
||||
SyncCollection::CalendarEventNotification,
|
||||
)
|
||||
.await?;
|
||||
let mut notifications = Vec::with_capacity(16);
|
||||
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,
|
||||
}),
|
||||
},
|
||||
)
|
||||
.ascending(),
|
||||
|key, value| {
|
||||
let document_id = key.deserialize_be_u32(key.len() - U32_LEN)?;
|
||||
notifications.push(Notification {
|
||||
document_id,
|
||||
created: key.deserialize_be_u64(key.len() - U32_LEN - U64_LEN)?,
|
||||
event_id: value.deserialize_be_u32(0)?,
|
||||
});
|
||||
document_ids.insert(document_id);
|
||||
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
for cond in std::mem::take(&mut request.filter) {
|
||||
match cond {
|
||||
Filter::Property(cond) => match cond {
|
||||
CalendarEventNotificationFilter::Before(before) => {
|
||||
let before = before.timestamp() as u64;
|
||||
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
|
||||
notifications
|
||||
.iter()
|
||||
.filter_map(|n| (n.created < before).then_some(n.document_id)),
|
||||
)))
|
||||
}
|
||||
CalendarEventNotificationFilter::After(after) => {
|
||||
let after = after.timestamp() as u64;
|
||||
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
|
||||
notifications
|
||||
.iter()
|
||||
.filter_map(|n| (n.created > after).then_some(n.document_id)),
|
||||
)))
|
||||
}
|
||||
CalendarEventNotificationFilter::CalendarEventIds(ids) => {
|
||||
let ids = ids
|
||||
.into_valid()
|
||||
.map(|id| id.document_id())
|
||||
.collect::<AHashSet<_>>();
|
||||
filters.push(SearchFilter::is_in_set(RoaringBitmap::from_iter(
|
||||
notifications
|
||||
.iter()
|
||||
.filter_map(|n| ids.contains(&n.event_id).then_some(n.document_id)),
|
||||
)))
|
||||
}
|
||||
unsupported => {
|
||||
return Err(trc::JmapEvent::UnsupportedFilter
|
||||
.into_err()
|
||||
.details(unsupported.into_string()));
|
||||
}
|
||||
},
|
||||
Filter::And => {
|
||||
filters.push(SearchFilter::And);
|
||||
}
|
||||
Filter::Or => {
|
||||
filters.push(SearchFilter::Or);
|
||||
}
|
||||
Filter::Not => {
|
||||
filters.push(SearchFilter::Not);
|
||||
}
|
||||
Filter::Close => {
|
||||
filters.push(SearchFilter::End);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse sort criteria
|
||||
let mut is_ascending = true;
|
||||
for comparator in request.sort.take().unwrap_or_default() {
|
||||
match comparator.property {
|
||||
CalendarEventNotificationComparator::Created => {
|
||||
is_ascending = comparator.is_ascending;
|
||||
}
|
||||
CalendarEventNotificationComparator::_T(unsupported) => {
|
||||
return Err(trc::JmapEvent::UnsupportedSort
|
||||
.into_err()
|
||||
.details(unsupported));
|
||||
}
|
||||
};
|
||||
}
|
||||
if !is_ascending {
|
||||
notifications.reverse();
|
||||
}
|
||||
|
||||
let results = SearchQuery::new(SearchIndex::InMemory)
|
||||
.with_filters(filters)
|
||||
.with_mask(document_ids)
|
||||
.filter()
|
||||
.into_bitmap();
|
||||
|
||||
let mut response = QueryResponseBuilder::new(
|
||||
results.len() as usize,
|
||||
self.core.jmap.query_max_results,
|
||||
cache.get_state(false),
|
||||
&request,
|
||||
);
|
||||
|
||||
if !results.is_empty() {
|
||||
let results = results.into_iter().collect::<AHashSet<_>>();
|
||||
for notification in notifications {
|
||||
if results.contains(¬ification.document_id)
|
||||
&& !response.add(0, notification.document_id)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken};
|
||||
use groupware::{DestroyArchive, cache::GroupwareCache, calendar::CalendarEventNotification};
|
||||
use http_proto::HttpSessionData;
|
||||
use jmap_proto::{
|
||||
error::set::SetError,
|
||||
method::set::{SetRequest, SetResponse},
|
||||
object::calendar_event_notification,
|
||||
request::IntoValid,
|
||||
types::state::State,
|
||||
};
|
||||
use store::{
|
||||
ValueKey,
|
||||
write::{AlignedBytes, Archive, BatchBuilder},
|
||||
};
|
||||
use trc::AddContext;
|
||||
use types::collection::{Collection, SyncCollection};
|
||||
|
||||
pub trait CalendarEventNotificationSet: Sync + Send {
|
||||
fn calendar_event_notification_set(
|
||||
&self,
|
||||
request: SetRequest<'_, calendar_event_notification::CalendarEventNotification>,
|
||||
access_token: &AccessToken,
|
||||
session: &HttpSessionData,
|
||||
) -> impl Future<
|
||||
Output = trc::Result<SetResponse<calendar_event_notification::CalendarEventNotification>>,
|
||||
> + Send;
|
||||
}
|
||||
|
||||
impl CalendarEventNotificationSet for Server {
|
||||
async fn calendar_event_notification_set(
|
||||
&self,
|
||||
mut request: SetRequest<'_, calendar_event_notification::CalendarEventNotification>,
|
||||
access_token: &AccessToken,
|
||||
_session: &HttpSessionData,
|
||||
) -> trc::Result<SetResponse<calendar_event_notification::CalendarEventNotification>> {
|
||||
let account_id = request.account_id.document_id();
|
||||
let cache = self
|
||||
.fetch_dav_resources(
|
||||
access_token.account_id(),
|
||||
account_id,
|
||||
SyncCollection::CalendarEventNotification,
|
||||
)
|
||||
.await?;
|
||||
let mut response = SetResponse::from_request(&request, self.core.jmap.set_max_objects)?;
|
||||
|
||||
let mut batch = BatchBuilder::new();
|
||||
for (id, _) in request.unwrap_create() {
|
||||
response.not_created.append(
|
||||
id,
|
||||
SetError::forbidden().with_description("Cannot create event notifications."),
|
||||
);
|
||||
}
|
||||
|
||||
// Process updates
|
||||
for (id, _) in request.unwrap_update().into_valid() {
|
||||
response.not_updated.append(
|
||||
id,
|
||||
SetError::forbidden().with_description("Cannot update event notifications."),
|
||||
);
|
||||
}
|
||||
|
||||
// Process deletions
|
||||
for id in request.unwrap_destroy().into_valid() {
|
||||
let document_id = id.document_id();
|
||||
|
||||
if !cache.has_item_id(&document_id) {
|
||||
response.not_destroyed.append(id, SetError::not_found());
|
||||
continue;
|
||||
};
|
||||
|
||||
let _event = if let Some(event) = self
|
||||
.store()
|
||||
.get_value::<Archive<AlignedBytes>>(ValueKey::archive(
|
||||
account_id,
|
||||
Collection::CalendarEventNotification,
|
||||
document_id,
|
||||
))
|
||||
.await?
|
||||
{
|
||||
event
|
||||
} else {
|
||||
response.not_destroyed.append(id, SetError::not_found());
|
||||
continue;
|
||||
};
|
||||
let event = _event
|
||||
.to_unarchived::<CalendarEventNotification>()
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
DestroyArchive(event)
|
||||
.delete(
|
||||
access_token.account_tenant_ids(),
|
||||
account_id,
|
||||
document_id,
|
||||
&mut batch,
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
response.destroyed.push(id);
|
||||
}
|
||||
|
||||
// Write changes
|
||||
if !batch.is_empty() {
|
||||
let change_id = self
|
||||
.commit_batch(batch)
|
||||
.await
|
||||
.and_then(|ids| ids.last_change_id(account_id))
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
response.new_state = State::Exact(change_id).into();
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user