Bug 1930206 - Store EWS messages copied from file to the local store. r=#thunderbird-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D229190
This commit is contained in:
Родитель
d1883a4aa8
Коммит
c5dabc2f98
|
@ -26,9 +26,8 @@ interface nsIMsgCopyServiceListener : nsISupports {
|
|||
|
||||
/**
|
||||
* Setting newly created message key. This method is tailored specifically
|
||||
* for nsIMsgCopyService::copyFileMessage() when saving Drafts/Templates.
|
||||
* We need to have a way to inform the client what's the key of the newly
|
||||
* created message.
|
||||
* for nsIMsgCopyService::copyFileMessage(). We need to have a way to inform
|
||||
* the client what's the key of the newly created message.
|
||||
* @param aKey - Message key.
|
||||
*/
|
||||
void setMessageKey(in nsMsgKey aKey);
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
#include "IEwsClient.h"
|
||||
#include "IEwsIncomingServer.h"
|
||||
#include "MailNewsTypes.h"
|
||||
#include "nsIMutableArray.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsIMsgDatabase.h"
|
||||
#include "nsIMsgPluggableStore.h"
|
||||
#include "nsString.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIMsgWindow.h"
|
||||
#include "nsMsgUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nscore.h"
|
||||
|
@ -97,6 +98,117 @@ NS_IMETHODIMP MessageDeletionCallbacks::OnError(IEwsClient::Error err,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
class MessageCreateCallbacks : public IEWSMessageCreateCallbacks {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_IEWSMESSAGECREATECALLBACKS
|
||||
|
||||
MessageCreateCallbacks(EwsFolder* folder, nsIFile* file,
|
||||
nsIMsgCopyServiceListener* copyListener)
|
||||
: mFolder(folder), mFile(file), mCopyListener(copyListener) {}
|
||||
|
||||
protected:
|
||||
virtual ~MessageCreateCallbacks() = default;
|
||||
|
||||
private:
|
||||
RefPtr<EwsFolder> mFolder;
|
||||
nsCOMPtr<nsIFile> mFile;
|
||||
nsCOMPtr<nsIMsgCopyServiceListener> mCopyListener;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(MessageCreateCallbacks, IEWSMessageCreateCallbacks)
|
||||
|
||||
NS_IMETHODIMP MessageCreateCallbacks::OnRemoteCreateSuccessful(
|
||||
const nsACString& ewsId, nsIMsgDBHdr** newHdr) {
|
||||
// Open an input stream on the file.
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mFile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Create a new header in the database for this message. We could do it in one
|
||||
// go via `nsIMsgPluggableStore::GetNewMsgOutputStream`, but we'll want the
|
||||
// message database and store to be more decoupled going forwards.
|
||||
nsCOMPtr<nsIMsgDatabase> msgDB;
|
||||
rv = mFolder->GetMsgDatabase(getter_AddRefs(msgDB));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIMsgDBHdr> hdr;
|
||||
rv = msgDB->CreateNewHdr(nsMsgKey_None, getter_AddRefs(hdr));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Create a new output stream to the folder's message store.
|
||||
nsCOMPtr<nsIOutputStream> outStream;
|
||||
rv = mFolder->GetOfflineStoreOutputStream(hdr, getter_AddRefs(outStream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Stream the message content to the store.
|
||||
uint64_t bytesCopied;
|
||||
rv = SyncCopyStream(inputStream, outStream, bytesCopied);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIMsgPluggableStore> store;
|
||||
rv = mFolder->GetMsgStore(getter_AddRefs(store));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = store->FinishNewMessage(outStream, hdr);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Udpate some of the header's metadata, such as the size, the offline flag
|
||||
// and the EWS ID.
|
||||
rv = hdr->SetMessageSize(bytesCopied);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = hdr->SetOfflineMessageSize(bytesCopied);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t unused;
|
||||
rv = hdr->OrFlags(nsMsgMessageFlags::Offline, &unused);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = hdr->SetStringProperty(ID_PROPERTY, ewsId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Return the newly-created header so that the consumer can update it with
|
||||
// metadata from the message headers before adding it to the message database.
|
||||
hdr.forget(newHdr);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageCreateCallbacks::CommitHeader(nsIMsgDBHdr* hdr) {
|
||||
nsCOMPtr<nsIMsgDatabase> msgDB;
|
||||
nsresult rv = mFolder->GetMsgDatabase(getter_AddRefs(msgDB));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = msgDB->AddNewHdrToDB(hdr, true);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = msgDB->Commit(nsMsgDBCommitType::kLargeCommit);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageCreateCallbacks::OnStartCreate() {
|
||||
return mCopyListener->OnStartCopy();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageCreateCallbacks::SetMessageKey(nsMsgKey aKey) {
|
||||
return mCopyListener->SetMessageKey(aKey);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP MessageCreateCallbacks::OnStopCreate(nsresult status) {
|
||||
nsresult rv = mCopyListener->OnStopCopy(status);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Note: at some point this will need to call
|
||||
// `nsMsgCopyService::NotifyCompletion` to let the copy service it can dequeue
|
||||
// the copy request. There seems to be a trick to it, so we'll take a look at
|
||||
// it in a later step, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1931599
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class MessageOperationCallbacks : public IEwsMessageCallbacks {
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
@ -367,9 +479,10 @@ NS_IMETHODIMP EwsFolder::CopyFileMessage(
|
|||
rv = GetEwsClient(getter_AddRefs(client));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
auto ewsMsgListener = RefPtr(new MessageOperationCallbacks(this, msgWindow));
|
||||
return client->SaveMessage(ewsId, isDraftOrTemplate, inputStream,
|
||||
copyListener, ewsMsgListener);
|
||||
RefPtr<MessageCreateCallbacks> ewsListener =
|
||||
new MessageCreateCallbacks(this, aFile, copyListener);
|
||||
return client->CreateMessage(ewsId, isDraftOrTemplate, inputStream,
|
||||
ewsListener);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP EwsFolder::DeleteMessages(
|
||||
|
|
|
@ -18,7 +18,22 @@ interface IEwsFolderCallbacks;
|
|||
interface IEwsMessageCallbacks;
|
||||
interface IEwsMessageDeleteCallbacks;
|
||||
interface IEWSMessageFetchCallbacks;
|
||||
interface IEWSMessageCreateCallbacks;
|
||||
|
||||
/**
|
||||
* An interface to communicate with an EWS server, and lives on
|
||||
* `EwsIncomingServer`.
|
||||
*
|
||||
* Its main role is to perform remote operations on the relevant EWS server,
|
||||
* which is passed to `initialize`. This same method also uses the provided
|
||||
* `server` to retrieve connection settings (such as authentication).
|
||||
*
|
||||
* Most of the code to run besides forming and sending requests to (and reading
|
||||
* responses from) the EWS server is defined by the consumer via the relevant
|
||||
* callback interface. However, `IEwsClient` will in some cases take care of
|
||||
* e.g. populating `nsIMsgDBHdr`s (instead of letting the consumer code do it)
|
||||
* due to architectural constraints.
|
||||
*/
|
||||
[uuid(4a117361-653b-48a5-9ddb-588482ef9dbb)]
|
||||
interface IEwsClient : nsISupports
|
||||
{
|
||||
|
@ -39,19 +54,19 @@ interface IEwsClient : nsISupports
|
|||
*
|
||||
* @param folderId The EWS ID of the folder.
|
||||
* @param isDraft Whether the message being created is an unsent
|
||||
* draft.
|
||||
* draft, so the correct flags can be set on the
|
||||
* server for the newly created message.
|
||||
* @param messageStream The input stream to read the message from.
|
||||
* @param copyListener A listener to provide updates on copying of
|
||||
* the message from the stream.
|
||||
* @param messageCallbacks Callbacks to use to communicate between the EWS
|
||||
* client and the EWS folder (e.g. to access its
|
||||
* database).
|
||||
* client and the EWS folder (e.g. to signal the
|
||||
* message can be added to its database), and to
|
||||
* provide updates on the copying of the message to
|
||||
* the server.
|
||||
*/
|
||||
void saveMessage(in AUTF8String folderId,
|
||||
void createMessage(in AUTF8String folderId,
|
||||
in boolean isDraft,
|
||||
in nsIInputStream messageStream,
|
||||
in nsIMsgCopyServiceListener copyListener,
|
||||
in IEwsMessageCallbacks messageCallbacks);
|
||||
in IEWSMessageCreateCallbacks messageCallbacks);
|
||||
|
||||
void deleteMessages(in Array<AUTF8String> messageEwsIds, in IEwsMessageDeleteCallbacks callbacks);
|
||||
};
|
||||
|
@ -101,3 +116,45 @@ interface IEWSMessageFetchCallbacks : nsISupports
|
|||
void onDataAvailable(in nsIInputStream aInputStream, in unsigned long aCount);
|
||||
void onFetchStop(in nsresult status);
|
||||
};
|
||||
|
||||
/**
|
||||
* A listener used when creating a new message on the server.
|
||||
*
|
||||
* The listener is expected to hold a handle on the temporary file that contains
|
||||
* the new message, so it can stream it to the store when it has been stored
|
||||
* correctly.
|
||||
*/
|
||||
[uuid(ff45569f-d618-4bb0-9686-6cb24b92b02b)]
|
||||
interface IEWSMessageCreateCallbacks : nsISupports
|
||||
{
|
||||
/**
|
||||
* These methods are mostly replicating the similarly-named ones in
|
||||
* `nsIMsgCopyServiceListener`, and at the moment just forward calls to an
|
||||
* instance of it. This is partly so to simplify `createMessage` so it only
|
||||
* takes one listener, but also because solving
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1931599 will require doing
|
||||
* more in `onStopCreate`.
|
||||
*/
|
||||
void onStartCreate();
|
||||
void onStopCreate(in nsresult status);
|
||||
void setMessageKey(in nsMsgKey aKey);
|
||||
|
||||
/**
|
||||
* Signals that the message was correctly created on the server.
|
||||
*
|
||||
* Returns the header object to update with the message's metadata and to
|
||||
* commit to the message database.
|
||||
*
|
||||
* `nsIMsgDBHdr` is a type quite strongly associated with the message database
|
||||
* and storage, and, going forwards, we'll want to decouple these interfaces
|
||||
* from local storage management. We use currently use it because we don't
|
||||
* have a better way to represent structured headers over the XPCOM boundary,
|
||||
* and parsing RFC822 messages is easier in Rust than using the C++ message
|
||||
* parser. We should revisit our use of `nsIMsgDBHdr` in client code the
|
||||
* situation improves.
|
||||
*/
|
||||
nsIMsgDBHdr onRemoteCreateSuccessful(in AUTF8String ewsId);
|
||||
|
||||
// Commits the given header to the relevant message database.
|
||||
void commitHeader(in nsIMsgDBHdr hdr);
|
||||
};
|
||||
|
|
|
@ -35,8 +35,8 @@ use uuid::Uuid;
|
|||
use xpcom::{
|
||||
getter_addrefs,
|
||||
interfaces::{
|
||||
nsIMsgCopyServiceListener, nsIMsgDBHdr, nsIMsgOutgoingListener, nsIStringInputStream,
|
||||
nsIURI, nsMsgFolderFlagType, nsMsgFolderFlags, nsMsgKey, nsMsgMessageFlags,
|
||||
nsIMsgDBHdr, nsIMsgOutgoingListener, nsIStringInputStream, nsIURI, nsMsgFolderFlagType,
|
||||
nsMsgFolderFlags, nsMsgKey, nsMsgMessageFlags, IEWSMessageCreateCallbacks,
|
||||
IEWSMessageFetchCallbacks, IEwsClient, IEwsFolderCallbacks, IEwsMessageCallbacks,
|
||||
IEwsMessageDeleteCallbacks,
|
||||
},
|
||||
|
@ -972,15 +972,14 @@ impl XpComEwsClient {
|
|||
/// All headers are expected to be included in the provided MIME content.
|
||||
///
|
||||
/// [`CreateItem`] https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/createitem-operation-email-message
|
||||
pub async fn save_message(
|
||||
pub async fn create_message(
|
||||
self,
|
||||
folder_id: String,
|
||||
is_draft: bool,
|
||||
content: Vec<u8>,
|
||||
copy_listener: RefPtr<nsIMsgCopyServiceListener>,
|
||||
message_callbacks: RefPtr<IEwsMessageCallbacks>,
|
||||
message_callbacks: RefPtr<IEWSMessageCreateCallbacks>,
|
||||
) {
|
||||
if let Err(status) = unsafe { copy_listener.OnStartCopy().to_result() } {
|
||||
if let Err(status) = unsafe { message_callbacks.OnStartCreate().to_result() } {
|
||||
log::error!("aborting copy: an error occurred while starting the listener: {status}");
|
||||
return;
|
||||
}
|
||||
|
@ -989,13 +988,7 @@ impl XpComEwsClient {
|
|||
// Use the return value to determine which status we should use when
|
||||
// notifying the end of the request.
|
||||
let status = match self
|
||||
.save_message_inner(
|
||||
folder_id,
|
||||
is_draft,
|
||||
content,
|
||||
copy_listener.clone(),
|
||||
message_callbacks,
|
||||
)
|
||||
.create_message_inner(folder_id, is_draft, content, message_callbacks.clone())
|
||||
.await
|
||||
{
|
||||
Ok(_) => nserror::NS_OK,
|
||||
|
@ -1006,18 +999,17 @@ impl XpComEwsClient {
|
|||
}
|
||||
};
|
||||
|
||||
if let Err(err) = unsafe { copy_listener.OnStopCopy(status) }.to_result() {
|
||||
if let Err(err) = unsafe { message_callbacks.OnStopCreate(status) }.to_result() {
|
||||
log::error!("aborting copy: an error occurred while stopping the listener: {err}")
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_message_inner(
|
||||
async fn create_message_inner(
|
||||
&self,
|
||||
folder_id: String,
|
||||
is_draft: bool,
|
||||
content: Vec<u8>,
|
||||
copy_listener: RefPtr<nsIMsgCopyServiceListener>,
|
||||
message_callbacks: RefPtr<IEwsMessageCallbacks>,
|
||||
message_callbacks: RefPtr<IEWSMessageCreateCallbacks>,
|
||||
) -> Result<(), XpComEwsError> {
|
||||
// Create a new message from the binary content we got.
|
||||
let mut message = Message {
|
||||
|
@ -1066,21 +1058,16 @@ impl XpComEwsClient {
|
|||
|
||||
let response_message = self.make_create_item_request(create_item).await?;
|
||||
|
||||
let hdr = create_and_populate_header_from_save_response(
|
||||
let hdr = create_and_populate_header_from_create_response(
|
||||
response_message,
|
||||
&content,
|
||||
message_callbacks,
|
||||
message_callbacks.clone(),
|
||||
)?;
|
||||
|
||||
if is_draft {
|
||||
// If we're dealing with a draft message, copy the message key to
|
||||
// the listener so that the draft can be replaced if a newer draft
|
||||
// of the message is saved.
|
||||
let mut key: nsMsgKey = 0;
|
||||
|
||||
unsafe { hdr.GetMessageKey(&mut key) }.to_result()?;
|
||||
unsafe { copy_listener.SetMessageKey(key) }.to_result()?;
|
||||
}
|
||||
// Let the listeners know of the local key for the newly created message.
|
||||
let mut key: nsMsgKey = 0;
|
||||
unsafe { hdr.GetMessageKey(&mut key) }.to_result()?;
|
||||
unsafe { message_callbacks.SetMessageKey(key) }.to_result()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1678,10 +1665,10 @@ fn validate_get_folder_response_message(
|
|||
|
||||
/// Uses the provided `CreateItemResponseMessage` to create, populate and commit
|
||||
/// an `nsIMsgDBHdr` for a newly created message.
|
||||
fn create_and_populate_header_from_save_response(
|
||||
fn create_and_populate_header_from_create_response(
|
||||
response_message: CreateItemResponseMessage,
|
||||
content: &[u8],
|
||||
message_callbacks: RefPtr<IEwsMessageCallbacks>,
|
||||
message_callbacks: RefPtr<IEWSMessageCreateCallbacks>,
|
||||
) -> Result<RefPtr<nsIMsgDBHdr>, XpComEwsError> {
|
||||
// If we're saving the message (rather than sending it), we must create a
|
||||
// new database entry for it and associate it with the message's EWS ID.
|
||||
|
@ -1702,8 +1689,10 @@ fn create_and_populate_header_from_save_response(
|
|||
.id;
|
||||
let ews_id = nsCString::from(ews_id);
|
||||
|
||||
// Signal that copying the message to the server has succeeded, which will
|
||||
// trigger its content to be streamed to the relevant message store.
|
||||
let hdr =
|
||||
getter_addrefs(|hdr| unsafe { message_callbacks.CreateNewHeaderForItem(&*ews_id, hdr) })?;
|
||||
getter_addrefs(|hdr| unsafe { message_callbacks.OnRemoteCreateSuccessful(&*ews_id, hdr) })?;
|
||||
|
||||
// Parse the message and use its headers to populate the `nsIMsgDBHdr`
|
||||
// before committing it to the database. We parse the original content
|
||||
|
|
|
@ -14,8 +14,9 @@ use thin_vec::ThinVec;
|
|||
use url::Url;
|
||||
use xpcom::{
|
||||
interfaces::{
|
||||
nsIInputStream, nsIMsgCopyServiceListener, nsIMsgIncomingServer, IEWSMessageFetchCallbacks,
|
||||
IEwsFolderCallbacks, IEwsMessageCallbacks, IEwsMessageDeleteCallbacks,
|
||||
nsIInputStream, nsIMsgIncomingServer, IEWSMessageCreateCallbacks,
|
||||
IEWSMessageFetchCallbacks, IEwsFolderCallbacks, IEwsMessageCallbacks,
|
||||
IEwsMessageDeleteCallbacks,
|
||||
},
|
||||
nsIID, xpcom_method, RefPtr,
|
||||
};
|
||||
|
@ -181,14 +182,13 @@ impl XpcomEwsBridge {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
xpcom_method!(save_message => SaveMessage(folder_id: *const nsACString, isDraft: bool, messageStream: *const nsIInputStream, copyListener: *const nsIMsgCopyServiceListener, messageCallbaks: *const IEwsMessageCallbacks));
|
||||
fn save_message(
|
||||
xpcom_method!(create_message => CreateMessage(folder_id: *const nsACString, isDraft: bool, messageStream: *const nsIInputStream, messageCallbacks: *const IEWSMessageCreateCallbacks));
|
||||
fn create_message(
|
||||
&self,
|
||||
folder_id: &nsACString,
|
||||
is_draft: bool,
|
||||
message_stream: &nsIInputStream,
|
||||
copy_listener: &nsIMsgCopyServiceListener,
|
||||
message_callbacks: &IEwsMessageCallbacks,
|
||||
message_callbacks: &IEWSMessageCreateCallbacks,
|
||||
) -> Result<(), nsresult> {
|
||||
let content = crate::xpcom_io::read_stream(message_stream)?;
|
||||
|
||||
|
@ -198,11 +198,10 @@ impl XpcomEwsBridge {
|
|||
// this scope, so spawn it as a detached `moz_task`.
|
||||
moz_task::spawn_local(
|
||||
"save_message",
|
||||
client.save_message(
|
||||
client.create_message(
|
||||
folder_id.to_utf8().into(),
|
||||
is_draft,
|
||||
content,
|
||||
RefPtr::new(copy_listener),
|
||||
RefPtr::new(message_callbacks),
|
||||
),
|
||||
)
|
||||
|
|
Загрузка…
Ссылка в новой задаче