/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use super::get::ChangesLookup; use crate::{ api::request::resolve_account_id, calendar_event::query::CalendarEventQuery, calendar_event_notification::query::CalendarEventNotificationQuery, contact::query::ContactCardQuery, email::query::EmailQuery, file::query::FileNodeQuery, mailbox::query::MailboxQuery, share_notification::query::ShareNotificationQuery, submission::query::EmailSubmissionQuery, }; use common::{Server, auth::AccessToken}; use jmap_proto::{ method::{ changes::{ChangesRequest, ChangesResponse}, query_changes::{AddedItem, QueryChangesRequest, QueryChangesResponse}, }, object::{JmapObject, NullObject}, request::{QueryChangesRequestMethod, method::MethodObject}, }; use std::future::Future; pub trait QueryChanges: Sync + Send { fn query_changes( &self, request: QueryChangesRequestMethod, access_token: &AccessToken, ) -> impl Future> + Send; } impl QueryChanges for Server { async fn query_changes( &self, request: QueryChangesRequestMethod, access_token: &AccessToken, ) -> trc::Result { let mut response; let mut is_mutable = true; let results; let changes; let has_changes; let up_to_id; match request { QueryChangesRequestMethod::Email(mut request) => { // Query changes resolve_account_id(&mut request.account_id, MethodObject::Email, access_token)?; changes = self .changes( build_changes_request(&request), MethodObject::Email, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; is_mutable = request.filter.iter().any(|f| !f.is_immutable()) || request .sort .as_ref() .is_some_and(|sort| sort.iter().any(|s| !s.is_immutable())); results = self.email_query((*request).into(), access_token).await?; } QueryChangesRequestMethod::Mailbox(mut request) => { // Query changes resolve_account_id(&mut request.account_id, MethodObject::Mailbox, access_token)?; changes = self .changes( build_changes_request(&request), MethodObject::Mailbox, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self.mailbox_query((*request).into(), access_token).await?; } QueryChangesRequestMethod::EmailSubmission(mut request) => { // Query changes resolve_account_id( &mut request.account_id, MethodObject::EmailSubmission, access_token, )?; changes = self .changes( build_changes_request(&request), MethodObject::EmailSubmission, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self.email_submission_query((*request).into()).await?; } QueryChangesRequestMethod::ContactCard(mut request) => { // Query changes resolve_account_id( &mut request.account_id, MethodObject::ContactCard, access_token, )?; changes = self .changes( build_changes_request(&request), MethodObject::ContactCard, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self .contact_card_query((*request).into(), access_token) .await?; } QueryChangesRequestMethod::FileNode(mut request) => { // Query changes resolve_account_id( &mut request.account_id, MethodObject::FileNode, access_token, )?; changes = self .changes( build_changes_request(&request), MethodObject::FileNode, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self .file_node_query((*request).into(), access_token) .await?; } QueryChangesRequestMethod::CalendarEvent(mut request) => { // Query changes resolve_account_id( &mut request.account_id, MethodObject::CalendarEvent, access_token, )?; changes = self .changes( build_changes_request(&request), MethodObject::CalendarEvent, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self .calendar_event_query((*request).into(), access_token) .await?; } QueryChangesRequestMethod::CalendarEventNotification(mut request) => { // Query changes resolve_account_id( &mut request.account_id, MethodObject::CalendarEventNotification, access_token, )?; changes = self .changes( build_changes_request(&request), MethodObject::CalendarEventNotification, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self .calendar_event_notification_query((*request).into(), access_token) .await?; } QueryChangesRequestMethod::ShareNotification(mut request) => { // Query changes resolve_account_id( &mut request.account_id, MethodObject::ShareNotification, access_token, )?; changes = self .changes( build_changes_request(&request), MethodObject::ShareNotification, access_token, ) .await? .response; let calculate_total = request.calculate_total.unwrap_or(false); has_changes = changes.has_changes(); response = build_query_changes_response(&request, &changes); if !has_changes && !calculate_total { return Ok(response); } up_to_id = request.up_to_id; results = self.share_notification_query((*request).into()).await?; } QueryChangesRequestMethod::Principal(_) => { return Err(trc::JmapEvent::CannotCalculateChanges.into_err()); } QueryChangesRequestMethod::Quota(_) => { return Err(trc::JmapEvent::CannotCalculateChanges.into_err()); } } if has_changes { if is_mutable { for (index, id) in results.ids.into_iter().enumerate() { if changes.created.contains(&id) || changes.updated.contains(&id) { response.added.push(AddedItem::new(id, index)); } } response.removed = changes.updated; } else { for (index, id) in results.ids.into_iter().enumerate() { if changes.created.contains(&id) { response.added.push(AddedItem::new(id, index)); } if matches!(up_to_id, Some(up_to_id) if up_to_id == id) { break; } } } if !changes.destroyed.is_empty() { response.removed.extend(changes.destroyed); } } response.total = results.total; Ok(response) } } fn build_changes_request(req: &QueryChangesRequest) -> ChangesRequest { ChangesRequest { account_id: req.account_id, since_state: req.since_query_state.clone(), max_changes: req.max_changes, } } fn build_query_changes_response( req: &QueryChangesRequest, changes: &ChangesResponse, ) -> QueryChangesResponse { QueryChangesResponse { account_id: req.account_id, old_query_state: changes.old_state.clone(), new_query_state: changes.new_state.clone(), total: None, removed: vec![], added: vec![], } }