Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use ews::{
update_item::{
ConflictResolution, ItemChange, ItemChangeDescription, ItemChangeInner, UpdateItem, Updates,
},
BaseItemId, Message, MessageDisposition, Operation, OperationResponse, PathToElement,
};
use mailnews_ui_glue::UserInteractiveServer;
use nsstring::nsCString;
use thin_vec::ThinVec;
use xpcom::RefCounted;
use crate::{
authentication::credentials::AuthenticationProvider,
client::{process_response_message_class, DoOperation, XpComEwsClient, XpComEwsError},
safe_xpcom::{
handle_error, SafeEwsSimpleOperationListener, SafeListener, SimpleOperationSuccessArgs,
},
};
struct DoChangeReadStatus<'a> {
listener: &'a SafeEwsSimpleOperationListener,
message_ids: ThinVec<nsCString>,
is_read: bool,
}
impl DoOperation for DoChangeReadStatus<'_> {
const NAME: &'static str = "change read status";
type Okay = ();
type Listener = SafeEwsSimpleOperationListener;
async fn do_operation<ServerT>(
&mut self,
client: &XpComEwsClient<ServerT>,
) -> Result<Self::Okay, XpComEwsError>
where
ServerT: AuthenticationProvider + UserInteractiveServer + RefCounted,
{
// Create the structure for setting the messages as read/unread.
let item_changes: Vec<ItemChange> = self
.message_ids
.clone()
.into_iter()
.map(|message_id| {
let updates = Updates {
inner: vec![ItemChangeDescription::SetItemField {
field_uri: PathToElement::FieldURI {
field_URI: "message:IsRead".to_string(),
},
message: Message {
is_read: Some(self.is_read),
..Default::default()
},
}],
};
ItemChange {
item_change: ItemChangeInner {
item_id: BaseItemId::ItemId {
id: message_id.to_string(),
// TODO: We should be able to get the change key from the
// database or server, but we don't have a way to do that yet.
change_key: None,
},
updates,
},
}
})
.collect();
let update_item = UpdateItem {
item_changes,
message_disposition: MessageDisposition::SaveOnly,
// If we don't provide a ChangeKey as part of the ItemChange, then
// we cannot use the default value of `AutoResolve` for
// `ConflictResolution`. Instead, we will use `AlwaysOverwrite` for now.
conflict_resolution: Some(ConflictResolution::AlwaysOverwrite),
};
let response = client.make_update_item_request(update_item).await?;
let response_messages = response.into_response_messages();
let (successes, errors): (Vec<_>, Vec<_>) = response_messages
.into_iter()
.map(|r| process_response_message_class(UpdateItem::NAME, r))
.enumerate()
.partition(|(_index, result)| result.is_ok());
let successes: ThinVec<nsCString> = successes
.into_iter()
.flat_map(|(_, success)| {
let message = success.expect("partition should only populate this with okays");
message.items.inner.into_iter()
})
.filter_map(|item| item.into_inner_message().item_id)
.map(|item_id| item_id.id.into())
.collect();
let ret = if !successes.is_empty() {
self.listener.on_success((successes, false).into())
} else {
// This branch only happens if no messages were requested,
// or we're about to return an aggregated error in the next block.
Ok(())
};
// If there were errors, return an aggregated error.
if !errors.is_empty() {
let num_errs = errors.len();
let (index, ref first_err) = errors[0];
let first_error = first_err
.as_ref()
.expect_err("partition should only populate this with errs");
return Err(XpComEwsError::Processing {
message: format!("response contained {num_errs} errors; the first error (at index {index}) was: {first_error:?}"),
});
}
Ok(ret?)
}
fn into_success_arg(self, _ok: Self::Okay) -> SimpleOperationSuccessArgs {
// this isn't actually used in this case
(ThinVec::<nsCString>::new(), false).into()
}
fn into_failure_arg(self) {}
/// This uses a custom implementation, since this operation has the unusual
/// behavior of returning any successful responses to the success listener,
/// even if the operation had failures.
async fn handle_operation<ServerT>(
mut self,
client: &XpComEwsClient<ServerT>,
listener: &Self::Listener,
) where
ServerT: AuthenticationProvider + UserInteractiveServer + RefCounted,
{
match self.do_operation(client).await {
Ok(()) => {
// the operation has already called on_success
}
Err(err) => {
handle_error(listener, Self::NAME, &err, ());
}
}
}
}
impl<ServerT> XpComEwsClient<ServerT>
where
ServerT: AuthenticationProvider + UserInteractiveServer + RefCounted,
{
/// Mark a message as read or unread by performing an [`UpdateItem` operation] via EWS.
///
pub async fn change_read_status(
self,
listener: SafeEwsSimpleOperationListener,
message_ids: ThinVec<nsCString>,
is_read: bool,
) {
let operation = DoChangeReadStatus {
listener: &listener,
message_ids,
is_read,
};
operation.handle_operation(&self, &listener).await;
}
}