Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "nsMsgCopyService.h"
#include "nsCOMArray.h"
#include "nspr.h"
#include "nsIFile.h"
#include "nsIMsgFolderNotificationService.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsMsgUtils.h"
#include "mozilla/Logging.h"
static mozilla::LazyLogModule gCopyServiceLog("MsgCopyService");
// ******************** nsCopySource ******************
nsCopySource::nsCopySource() : m_processed(false) {
MOZ_COUNT_CTOR(nsCopySource);
}
nsCopySource::nsCopySource(nsIMsgFolder* srcFolder) : m_processed(false) {
MOZ_COUNT_CTOR(nsCopySource);
m_msgFolder = srcFolder;
}
nsCopySource::~nsCopySource() { MOZ_COUNT_DTOR(nsCopySource); }
void nsCopySource::AddMessage(nsIMsgDBHdr* aMsg) {
m_messageArray.AppendElement(aMsg);
}
// ************ nsCopyRequest *****************
//
nsCopyRequest::nsCopyRequest()
: m_requestType(nsCopyMessagesType),
m_isMoveOrDraftOrTemplate(false),
m_processed(false),
m_newMsgFlags(0) {
MOZ_COUNT_CTOR(nsCopyRequest);
}
nsCopyRequest::~nsCopyRequest() {
MOZ_COUNT_DTOR(nsCopyRequest);
int32_t j = m_copySourceArray.Length();
while (j-- > 0) delete m_copySourceArray.ElementAt(j);
}
nsresult nsCopyRequest::Init(nsCopyRequestType type, nsISupports* aSupport,
nsIMsgFolder* dstFolder, bool bVal,
uint32_t newMsgFlags,
const nsACString& newMsgKeywords,
nsIMsgCopyServiceListener* listener,
nsIMsgWindow* msgWindow, bool allowUndo) {
nsresult rv = NS_OK;
m_requestType = type;
m_srcSupport = aSupport;
m_dstFolder = dstFolder;
m_isMoveOrDraftOrTemplate = bVal;
m_allowUndo = allowUndo;
m_newMsgFlags = newMsgFlags;
m_newMsgKeywords = newMsgKeywords;
if (listener) m_listener = listener;
if (msgWindow) {
m_msgWindow = msgWindow;
if (m_allowUndo) msgWindow->GetTransactionManager(getter_AddRefs(m_txnMgr));
}
if (type == nsCopyFoldersType) {
// To support multiple copy folder operations to the same destination, we
// need to save the leaf name of the src file spec so that FindRequest() is
// able to find the right request when copy finishes.
nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(aSupport, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsString folderName;
rv = srcFolder->GetName(folderName);
NS_ENSURE_SUCCESS(rv, rv);
m_dstFolderName = folderName;
}
return rv;
}
nsCopySource* nsCopyRequest::AddNewCopySource(nsIMsgFolder* srcFolder) {
nsCopySource* newSrc = new nsCopySource(srcFolder);
if (newSrc) {
m_copySourceArray.AppendElement(newSrc);
if (srcFolder == m_dstFolder) newSrc->m_processed = true;
}
return newSrc;
}
// ************* nsMsgCopyService ****************
//
nsMsgCopyService::nsMsgCopyService() {}
nsMsgCopyService::~nsMsgCopyService() {
int32_t i = m_copyRequests.Length();
while (i-- > 0) ClearRequest(m_copyRequests.ElementAt(i), NS_ERROR_FAILURE);
}
void nsMsgCopyService::LogCopyCompletion(nsISupports* aSrc,
nsIMsgFolder* aDest) {
nsCString srcFolderUri, destFolderUri;
nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aSrc));
if (srcFolder) srcFolder->GetURI(srcFolderUri);
aDest->GetURI(destFolderUri);
MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info,
("NotifyCompletion - src %s dest %s\n", srcFolderUri.get(),
destFolderUri.get()));
}
void nsMsgCopyService::LogCopyRequest(const char* logMsg,
nsCopyRequest* aRequest) {
nsCString srcFolderUri, destFolderUri;
nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aRequest->m_srcSupport));
if (srcFolder) srcFolder->GetURI(srcFolderUri);
aRequest->m_dstFolder->GetURI(destFolderUri);
uint32_t numMsgs = 0;
if (aRequest->m_requestType == nsCopyMessagesType &&
aRequest->m_copySourceArray.Length() > 0) {
numMsgs = aRequest->m_copySourceArray[0]->m_messageArray.Length();
}
MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info,
("request %p %s - src %s dest %s numItems %d type=%d", aRequest,
logMsg, srcFolderUri.get(), destFolderUri.get(), numMsgs,
aRequest->m_requestType));
}
nsresult nsMsgCopyService::ClearRequest(nsCopyRequest* aRequest, nsresult rv) {
if (aRequest) {
if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
LogCopyRequest(
NS_SUCCEEDED(rv) ? "Clearing OK request" : "Clearing failed request",
aRequest);
if (NS_SUCCEEDED(rv) && aRequest->m_requestType == nsCopyFoldersType) {
// Send folder copy/move notifications to nsIMsgFolderListeners.
// BAD SMELL ALERT: Seems odd that this is the only place the folder
// notification is invoked from the copyService.
// For message copy/move operations, the folder code handles the
// notification (to take one example).
// This suggests lack of clarity of responsibility.
nsCOMPtr<nsIMsgFolderNotificationService> notifier(
do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
if (notifier) {
for (nsCopySource* copySource : aRequest->m_copySourceArray) {
notifier->NotifyFolderMoveCopyCompleted(
aRequest->m_isMoveOrDraftOrTemplate, copySource->m_msgFolder,
aRequest->m_dstFolder);
}
}
}
// undo stuff
if (aRequest->m_allowUndo && aRequest->m_copySourceArray.Length() > 1 &&
aRequest->m_txnMgr)
aRequest->m_txnMgr->EndBatch(false);
m_copyRequests.RemoveElement(aRequest);
if (aRequest->m_listener) aRequest->m_listener->OnStopCopy(rv);
delete aRequest;
}
return rv;
}
nsresult nsMsgCopyService::QueueRequest(nsCopyRequest* aRequest,
bool* aCopyImmediately) {
NS_ENSURE_ARG_POINTER(aRequest);
NS_ENSURE_ARG_POINTER(aCopyImmediately);
*aCopyImmediately = true;
nsCopyRequest* copyRequest;
// Check through previous requests to see if the copy can start immediately.
uint32_t cnt = m_copyRequests.Length();
for (uint32_t i = 0; i < cnt; i++) {
copyRequest = m_copyRequests.ElementAt(i);
if (aRequest->m_requestType == nsCopyFoldersType) {
// For copy folder, see if both destination folder (root)
// (ie, Local Folder) and folder name (ie, abc) are the same.
if (copyRequest->m_dstFolderName == aRequest->m_dstFolderName &&
SameCOMIdentity(copyRequest->m_dstFolder, aRequest->m_dstFolder)) {
*aCopyImmediately = false;
break;
}
} else if (SameCOMIdentity(copyRequest->m_dstFolder,
aRequest->m_dstFolder)) {
// If dst are same and we already have a request, we cannot copy
// immediately.
*aCopyImmediately = false;
break;
}
}
// Queue it.
m_copyRequests.AppendElement(aRequest);
return NS_OK;
}
nsresult nsMsgCopyService::DoCopy(nsCopyRequest* aRequest) {
NS_ENSURE_ARG(aRequest);
bool copyImmediately;
QueueRequest(aRequest, &copyImmediately);
if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
LogCopyRequest(copyImmediately ? "DoCopy" : "QueueRequest", aRequest);
// if no active request for this dest folder then we can copy immediately
if (copyImmediately) return DoNextCopy();
return NS_OK;
}
nsresult nsMsgCopyService::DoNextCopy() {
nsresult rv = NS_OK;
nsCopyRequest* copyRequest = nullptr;
nsCopySource* copySource = nullptr;
uint32_t i, j, scnt;
uint32_t cnt = m_copyRequests.Length();
if (cnt > 0) {
nsCOMArray<nsIMsgFolder> activeTargets;
// ** jt -- always FIFO
for (i = 0; i < cnt; i++) {
copyRequest = m_copyRequests.ElementAt(i);
copySource = nullptr;
scnt = copyRequest->m_copySourceArray.Length();
if (!copyRequest->m_processed) {
// if the target folder of this request already has an active
// copy request, skip this request for now.
if (activeTargets.ContainsObject(copyRequest->m_dstFolder)) {
copyRequest = nullptr;
continue;
}
if (scnt <= 0) goto found; // must be CopyFileMessage
for (j = 0; j < scnt; j++) {
copySource = copyRequest->m_copySourceArray.ElementAt(j);
if (!copySource->m_processed) goto found;
}
if (j >= scnt) // all processed set the value
copyRequest->m_processed = true;
}
if (copyRequest->m_processed) {
// Keep track of folders actively getting copied to.
activeTargets.AppendObject(copyRequest->m_dstFolder);
}
}
found:
if (copyRequest && !copyRequest->m_processed) {
if (copyRequest->m_listener) copyRequest->m_listener->OnStartCopy();
if (copyRequest->m_requestType == nsCopyMessagesType && copySource) {
copySource->m_processed = true;
rv = copyRequest->m_dstFolder->CopyMessages(
copySource->m_msgFolder, copySource->m_messageArray,
copyRequest->m_isMoveOrDraftOrTemplate, copyRequest->m_msgWindow,
copyRequest->m_listener, false,
copyRequest->m_allowUndo); // isFolder operation false
} else if (copyRequest->m_requestType == nsCopyFoldersType) {
NS_ENSURE_STATE(copySource);
copySource->m_processed = true;
nsCOMPtr<nsIMsgFolder> dstFolder = copyRequest->m_dstFolder;
nsCOMPtr<nsIMsgFolder> srcFolder = copySource->m_msgFolder;
// If folder transfer is not within the same server and if a folder
// move was requested, set the request move flag false to avoid
// removing the list of marked deleted messages in the source folder.
bool isMove = copyRequest->m_isMoveOrDraftOrTemplate;
if (copyRequest->m_isMoveOrDraftOrTemplate) {
bool sameServer;
IsOnSameServer(dstFolder, srcFolder, &sameServer);
if (!sameServer) copyRequest->m_isMoveOrDraftOrTemplate = false;
}
// NOTE: The folder invokes NotifyCompletion() when the operation is
// complete. Some folders (localfolder!) invoke it before CopyFolder()
// even returns. This will likely delete the request object, so
// you have to assume that copyRequest is invalid when CopyFolder()
// returns.
rv = dstFolder->CopyFolder(srcFolder, isMove, copyRequest->m_msgWindow,
copyRequest->m_listener);
// If CopyFolder() fails (e.g. destination folder already exists),
// it won't send a completion notification (NotifyCompletion()).
// So copyRequest will still exist, and we need to ditch it.
if (NS_FAILED(rv)) {
ClearRequest(copyRequest, rv);
}
} else if (copyRequest->m_requestType == nsCopyFileMessageType) {
nsCOMPtr<nsIFile> aFile(
do_QueryInterface(copyRequest->m_srcSupport, &rv));
if (NS_SUCCEEDED(rv)) {
// ** in case of saving draft/template; the very first
// time we may not have the original message to replace
// with; if we do we shall have an instance of copySource
nsCOMPtr<nsIMsgDBHdr> aMessage;
if (copySource) {
aMessage = copySource->m_messageArray[0];
copySource->m_processed = true;
}
copyRequest->m_processed = true;
rv = copyRequest->m_dstFolder->CopyFileMessage(
aFile, aMessage, copyRequest->m_isMoveOrDraftOrTemplate,
copyRequest->m_newMsgFlags, copyRequest->m_newMsgKeywords,
copyRequest->m_msgWindow, copyRequest->m_listener);
}
}
}
}
return rv;
}
/**
* Find a request in m_copyRequests which matches the passed in source
* and destination folders.
*
* @param aSupport the iSupports of the source folder.
* @param dstFolder the destination folder of the copy request.
*/
nsCopyRequest* nsMsgCopyService::FindRequest(nsISupports* aSupport,
nsIMsgFolder* dstFolder) {
nsCopyRequest* copyRequest = nullptr;
uint32_t cnt = m_copyRequests.Length();
for (uint32_t i = 0; i < cnt; i++) {
copyRequest = m_copyRequests.ElementAt(i);
if (SameCOMIdentity(copyRequest->m_srcSupport, aSupport) &&
SameCOMIdentity(copyRequest->m_dstFolder.get(), dstFolder))
break;
// When copying folders the notification of the message copy serves as a
// proxy for the folder copy. Check for that here.
if (copyRequest->m_requestType == nsCopyFoldersType) {
// If the src is different then check next request.
if (!SameCOMIdentity(copyRequest->m_srcSupport, aSupport)) {
copyRequest = nullptr;
continue;
}
// See if the parent of the copied folder is the same as the one when the
// request was made. Note if the destination folder is already a server
// folder then no need to get parent.
nsCOMPtr<nsIMsgFolder> parentMsgFolder;
nsresult rv = NS_OK;
bool isServer = false;
dstFolder->GetIsServer(&isServer);
if (!isServer) rv = dstFolder->GetParent(getter_AddRefs(parentMsgFolder));
if ((NS_FAILED(rv)) || (!parentMsgFolder && !isServer) ||
(copyRequest->m_dstFolder.get() != parentMsgFolder)) {
copyRequest = nullptr;
continue;
}
// Now checks if the folder name is the same.
nsString folderName;
rv = dstFolder->GetName(folderName);
if (NS_FAILED(rv)) {
copyRequest = nullptr;
continue;
}
if (copyRequest->m_dstFolderName == folderName) break;
} else
copyRequest = nullptr;
}
return copyRequest;
}
NS_IMPL_ISUPPORTS(nsMsgCopyService, nsIMsgCopyService)
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsMsgCopyService::CopyMessages(
nsIMsgFolder* srcFolder, /* UI src folder */
nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, nsIMsgFolder* dstFolder,
bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow* window,
bool allowUndo) {
NS_ENSURE_ARG_POINTER(srcFolder);
NS_ENSURE_ARG_POINTER(dstFolder);
MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Debug, ("CopyMessages"));
if (srcFolder == dstFolder) {
NS_ERROR("src and dest folders for msg copy can't be the same");
return NS_ERROR_FAILURE;
}
nsCopyRequest* copyRequest;
nsCopySource* copySource = nullptr;
nsIMsgDBHdr* msg;
nsCOMPtr<nsIMsgFolder> curFolder;
nsCOMPtr<nsISupports> aSupport;
int cnt;
nsresult rv;
// XXX TODO
// JUNK MAIL RELATED
// make sure dest folder exists
// and has proper flags, before we start copying?
// bail early if nothing to do
if (messages.IsEmpty()) {
if (listener) {
listener->OnStartCopy();
listener->OnStopCopy(NS_OK);
}
return NS_OK;
}
copyRequest = new nsCopyRequest();
if (!copyRequest) return NS_ERROR_OUT_OF_MEMORY;
nsTArray<RefPtr<nsIMsgDBHdr>> unprocessed = messages.Clone();
aSupport = srcFolder;
rv = copyRequest->Init(nsCopyMessagesType, aSupport, dstFolder, isMove,
0 /* new msg flags, not used */, EmptyCString(),
listener, window, allowUndo);
if (NS_FAILED(rv)) goto done;
if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
LogCopyRequest("CopyMessages request", copyRequest);
// Build up multiple nsCopySource objects. Each holds a single source folder
// and all the messages in the folder that are to be copied.
cnt = unprocessed.Length();
while (cnt-- > 0) {
msg = unprocessed[cnt];
rv = msg->GetFolder(getter_AddRefs(curFolder));
if (NS_FAILED(rv)) goto done;
if (!copySource) {
// Begin a folder grouping.
copySource = copyRequest->AddNewCopySource(curFolder);
if (!copySource) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto done;
}
}
// Stash message if in the current folder grouping.
if (curFolder == copySource->m_msgFolder) {
copySource->AddMessage(msg);
unprocessed.RemoveElementAt((size_t)cnt);
}
if (cnt == 0) {
// Finished a folder. Start a new pass to handle any remaining messages
// in other folders.
cnt = unprocessed.Length();
if (cnt > 0) {
// Force to create a new one and continue grouping the messages.
copySource = nullptr;
}
}
}
// undo stuff
if (NS_SUCCEEDED(rv) && copyRequest->m_allowUndo &&
copyRequest->m_copySourceArray.Length() > 1 && copyRequest->m_txnMgr) {
nsCOMPtr<nsITransactionManager> txnMgr = copyRequest->m_txnMgr;
txnMgr->BeginBatch(nullptr);
}
done:
if (NS_FAILED(rv))
delete copyRequest;
else
rv = DoCopy(copyRequest);
return rv;
}
NS_IMETHODIMP
nsMsgCopyService::CopyFolder(nsIMsgFolder* srcFolder, nsIMsgFolder* dstFolder,
bool isMove, nsIMsgCopyServiceListener* listener,
nsIMsgWindow* window) {
NS_ENSURE_ARG_POINTER(srcFolder);
NS_ENSURE_ARG_POINTER(dstFolder);
nsCopyRequest* copyRequest;
nsresult rv;
nsCOMPtr<nsIMsgFolder> curFolder;
copyRequest = new nsCopyRequest();
rv = copyRequest->Init(nsCopyFoldersType, srcFolder, dstFolder, isMove,
0 /* new msg flags, not used */, EmptyCString(),
listener, window, false);
NS_ENSURE_SUCCESS(rv, rv);
copyRequest->AddNewCopySource(srcFolder);
return DoCopy(copyRequest);
}
NS_IMETHODIMP
nsMsgCopyService::CopyFileMessage(nsIFile* file, nsIMsgFolder* dstFolder,
nsIMsgDBHdr* msgToReplace, bool isDraft,
uint32_t aMsgFlags,
const nsACString& aNewMsgKeywords,
nsIMsgCopyServiceListener* listener,
nsIMsgWindow* window) {
nsresult rv = NS_ERROR_NULL_POINTER;
nsCopyRequest* copyRequest;
nsCopySource* copySource = nullptr;
NS_ENSURE_ARG_POINTER(file);
NS_ENSURE_ARG_POINTER(dstFolder);
copyRequest = new nsCopyRequest();
if (!copyRequest) return rv;
rv = copyRequest->Init(nsCopyFileMessageType, file, dstFolder, isDraft,
aMsgFlags, aNewMsgKeywords, listener, window, false);
if (NS_FAILED(rv)) goto done;
if (msgToReplace) {
// The actual source of the message is a file not a folder, but
// we still need an nsCopySource to reference the old message header
// which will be used to recover message metadata.
copySource = copyRequest->AddNewCopySource(nullptr);
if (!copySource) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto done;
}
copySource->AddMessage(msgToReplace);
}
done:
if (NS_FAILED(rv)) {
delete copyRequest;
} else {
rv = DoCopy(copyRequest);
}
return rv;
}
NS_IMETHODIMP
nsMsgCopyService::NotifyCompletion(nsISupports* aSupport,
nsIMsgFolder* dstFolder, nsresult result) {
if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
LogCopyCompletion(aSupport, dstFolder);
nsCopyRequest* copyRequest = nullptr;
uint32_t numOrigRequests = m_copyRequests.Length();
do {
// loop for copy requests, because if we do a cross server folder copy,
// we'll have a copy request for the folder copy, which will in turn
// generate a copy request for the messages in the folder, which
// will have the same src support.
copyRequest = FindRequest(aSupport, dstFolder);
if (copyRequest) {
// ClearRequest can cause a new request to get added to m_copyRequests
// with matching source and dest folders if the copy listener starts
// a new copy. We want to ignore any such request here, because it wasn't
// the one that was completed. So we keep track of how many original
// requests there were.
if (m_copyRequests.IndexOf(copyRequest) >= numOrigRequests) break;
// check if this copy request is done by making sure all the
// sources have been processed.
int32_t sourceIndex, sourceCount;
sourceCount = copyRequest->m_copySourceArray.Length();
for (sourceIndex = 0; sourceIndex < sourceCount;) {
if (!(copyRequest->m_copySourceArray.ElementAt(sourceIndex))
->m_processed)
break;
sourceIndex++;
}
// if all sources processed, mark the request as processed
if (sourceIndex >= sourceCount) copyRequest->m_processed = true;
// if this request is done, or failed, clear it.
if (copyRequest->m_processed || NS_FAILED(result)) {
ClearRequest(copyRequest, result);
numOrigRequests--;
} else
break;
} else
break;
} while (copyRequest);
return DoNextCopy();
}