summaryrefslogtreecommitdiffstats
path: root/mailnews/base/util/nsMsgDBFolder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/util/nsMsgDBFolder.cpp')
-rw-r--r--mailnews/base/util/nsMsgDBFolder.cpp6040
1 files changed, 6040 insertions, 0 deletions
diff --git a/mailnews/base/util/nsMsgDBFolder.cpp b/mailnews/base/util/nsMsgDBFolder.cpp
new file mode 100644
index 000000000..3a1a571e2
--- /dev/null
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -0,0 +1,6040 @@
+/* -*- 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 "msgCore.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgDBFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsRDFCID.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgDatabase.h"
+#include "nsIMsgAccountManager.h"
+#include "nsISeekableStream.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIChannel.h"
+#include "nsITransport.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsIDocShell.h"
+#include "nsIMsgWindow.h"
+#include "nsIPrompt.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#include "nsCollationCID.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIMsgMailSession.h"
+#include "nsIRDFService.h"
+#include "nsTextFormatter.h"
+#include "nsMsgDBCID.h"
+#include "nsReadLine.h"
+#include "nsLayoutCID.h"
+#include "nsIParserUtils.h"
+#include "nsIDocumentEncoder.h"
+#include "nsMsgI18N.h"
+#include "nsIMIMEHeaderParam.h"
+#include "plbase64.h"
+#include "nsArrayEnumerator.h"
+#include <time.h>
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsIMimeHeaders.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgTraitService.h"
+#include "nsIMessenger.h"
+#include "nsThreadUtils.h"
+#include "nsITransactionManager.h"
+#include "nsMsgReadStateTxn.h"
+#include "nsAutoPtr.h"
+#include "prmem.h"
+#include "nsIPK11TokenDB.h"
+#include "nsIPK11Token.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include <algorithm>
+#define oneHour 3600000000U
+#include "nsMsgUtils.h"
+#include "nsIMsgFilterService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Services.h"
+#include "nsMimeTypes.h"
+#include "nsIMsgFilter.h"
+
+static PRTime gtimeOfLastPurgeCheck; //variable to know when to check for purge_threshhold
+
+#define PREF_MAIL_PROMPT_PURGE_THRESHOLD "mail.prompt_purge_threshhold"
+#define PREF_MAIL_PURGE_THRESHOLD "mail.purge_threshhold"
+#define PREF_MAIL_PURGE_THRESHOLD_MB "mail.purge_threshhold_mb"
+#define PREF_MAIL_PURGE_MIGRATED "mail.purge_threshold_migrated"
+#define PREF_MAIL_PURGE_ASK "mail.purge.ask"
+#define PREF_MAIL_WARN_FILTER_CHANGED "mail.warn_filter_changed"
+
+const char *kUseServerRetentionProp = "useServerRetention";
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsICollation * nsMsgDBFolder::gCollationKeyGenerator = nullptr;
+
+char16_t *nsMsgDBFolder::kLocalizedInboxName;
+char16_t *nsMsgDBFolder::kLocalizedTrashName;
+char16_t *nsMsgDBFolder::kLocalizedSentName;
+char16_t *nsMsgDBFolder::kLocalizedDraftsName;
+char16_t *nsMsgDBFolder::kLocalizedTemplatesName;
+char16_t *nsMsgDBFolder::kLocalizedUnsentName;
+char16_t *nsMsgDBFolder::kLocalizedJunkName;
+char16_t *nsMsgDBFolder::kLocalizedArchivesName;
+
+char16_t *nsMsgDBFolder::kLocalizedBrandShortName;
+
+nsrefcnt nsMsgDBFolder::mInstanceCount=0;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgDBFolder, nsRDFResource,
+ nsISupportsWeakReference, nsIMsgFolder,
+ nsIDBChangeListener, nsIUrlListener,
+ nsIJunkMailClassificationListener,
+ nsIMsgTraitClassificationListener)
+
+#define MSGDBFOLDER_ATOM(name_, value_) nsIAtom* nsMsgDBFolder::name_ = nullptr;
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+
+nsMsgDBFolder::nsMsgDBFolder(void)
+: mAddListener(true),
+ mNewMessages(false),
+ mGettingNewMessages(false),
+ mLastMessageLoaded(nsMsgKey_None),
+ mFlags(0),
+ mNumUnreadMessages(-1),
+ mNumTotalMessages(-1),
+ mNotifyCountChanges(true),
+ mExpungedBytes(0),
+ mInitializedFromCache(false),
+ mSemaphoreHolder(nullptr),
+ mNumPendingUnreadMessages(0),
+ mNumPendingTotalMessages(0),
+ mFolderSize(kSizeUnknown),
+ mNumNewBiffMessages(0),
+ mHaveParsedURI(false),
+ mIsServerIsValid(false),
+ mIsServer(false),
+ mInVFEditSearchScope (false)
+{
+ if (mInstanceCount++ <=0) {
+#define MSGDBFOLDER_ATOM(name_, value_) name_ = MsgNewAtom(value_).take();
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+ initializeStrings();
+ createCollationKeyGenerator();
+ gtimeOfLastPurgeCheck = 0;
+ }
+
+ mProcessingFlag[0].bit = nsMsgProcessingFlags::ClassifyJunk;
+ mProcessingFlag[1].bit = nsMsgProcessingFlags::ClassifyTraits;
+ mProcessingFlag[2].bit = nsMsgProcessingFlags::TraitsDone;
+ mProcessingFlag[3].bit = nsMsgProcessingFlags::FiltersDone;
+ mProcessingFlag[4].bit = nsMsgProcessingFlags::FilterToMove;
+ mProcessingFlag[5].bit = nsMsgProcessingFlags::NotReportedClassified;
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ mProcessingFlag[i].keys = nsMsgKeySetU::Create();
+}
+
+nsMsgDBFolder::~nsMsgDBFolder(void)
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ delete mProcessingFlag[i].keys;
+
+ if (--mInstanceCount == 0) {
+ NS_IF_RELEASE(gCollationKeyGenerator);
+ NS_Free(kLocalizedInboxName);
+ NS_Free(kLocalizedTrashName);
+ NS_Free(kLocalizedSentName);
+ NS_Free(kLocalizedDraftsName);
+ NS_Free(kLocalizedTemplatesName);
+ NS_Free(kLocalizedUnsentName);
+ NS_Free(kLocalizedJunkName);
+ NS_Free(kLocalizedArchivesName);
+ NS_Free(kLocalizedBrandShortName);
+
+#define MSGDBFOLDER_ATOM(name_, value_) NS_RELEASE(name_);
+#include "nsMsgDBFolderAtomList.h"
+#undef MSGDBFOLDER_ATOM
+ }
+ //shutdown but don't shutdown children.
+ Shutdown(false);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Shutdown(bool shutdownChildren)
+{
+ if(mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ if (mBackupDatabase)
+ {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+ }
+
+ if(shutdownChildren)
+ {
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++)
+ mSubFolders[i]->Shutdown(true);
+
+ // Reset incoming server pointer and pathname.
+ mServer = nullptr;
+ mPath = nullptr;
+ mHaveParsedURI = false;
+ mName.Truncate();
+ mSubFolders.Clear();
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::ForceDBClosed()
+{
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ mSubFolders[i]->ForceDBClosed();
+
+ if (mDatabase)
+ {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBService> mailDBFactory(do_GetService(NS_MSGDB_SERVICE_CONTRACTID));
+ if (mailDBFactory)
+ mailDBFactory->ForceFolderDBClosed(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CloseAndBackupFolderDB(const nsACString& newName)
+{
+ ForceDBClosed();
+
+ // We only support backup for mail at the moment
+ if ( !(mFlags & nsMsgFolderFlags::Mail))
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetBackupSummaryFile(getter_AddRefs(backupDBFile), newName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBackupDatabase)
+ {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+
+ backupDBFile->Remove(false);
+ bool backupExists;
+ backupDBFile->Exists(&backupExists);
+ NS_ASSERTION(!backupExists, "Couldn't delete database backup");
+ if (backupExists)
+ return NS_ERROR_FAILURE;
+
+ if (!newName.IsEmpty())
+ {
+ nsAutoCString backupName;
+ rv = backupDBFile->GetNativeLeafName(backupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dbFile->CopyToNative(backupDir, backupName);
+ }
+ else
+ return dbFile->CopyToNative(backupDir, EmptyCString());
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OpenBackupMsgDatabase()
+{
+ if (mBackupDatabase)
+ return NS_OK;
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = folderPath->GetLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(
+ backupDBDummyFolder, this, false, true,
+ getter_AddRefs(mBackupDatabase));
+ // we add a listener so that we can close the db during OnAnnouncerGoingAway. There should
+ // not be any other calls to the listener with the backup database
+ if (NS_SUCCEEDED(rv) && mBackupDatabase)
+ mBackupDatabase->AddListener(this);
+
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ // this is normal in reparsing
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveBackupMsgDatabase()
+{
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = folderPath->GetLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBackupDatabase)
+ {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+
+ return backupDBFile->Remove(false);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::StartFolderLoading(void)
+{
+ if(mDatabase)
+ mDatabase->RemoveListener(this);
+ mAddListener = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::EndFolderLoading(void)
+{
+ if(mDatabase)
+ mDatabase->AddListener(this);
+ mAddListener = true;
+ UpdateSummaryTotals(true);
+
+ //GGGG check for new mail here and call SetNewMessages...?? -- ONE OF THE 2 PLACES
+ if(mDatabase)
+ m_newMsgs.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetExpungedBytes(int64_t *count)
+{
+ NS_ENSURE_ARG_POINTER(count);
+
+ if (mDatabase)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_FAILED(rv)) return rv;
+ rv = folderInfo->GetExpungedBytes(count);
+ if (NS_SUCCEEDED(rv))
+ mExpungedBytes = *count; // sync up with the database
+ return rv;
+ }
+ else
+ {
+ ReadDBFolderInfo(false);
+ *count = mExpungedBytes;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetCharset(nsACString& aCharset)
+{
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ rv = folderInfo->GetEffectiveCharacterSet(aCharset);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetCharset(const nsACString& aCharset)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ {
+ rv = folderInfo->SetCharacterSet(aCharset);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ mCharset = aCharset;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetCharsetOverride(bool *aCharsetOverride)
+{
+ NS_ENSURE_ARG_POINTER(aCharsetOverride);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ rv = folderInfo->GetCharacterSetOverride(aCharsetOverride);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetCharsetOverride(bool aCharsetOverride)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ {
+ rv = folderInfo->SetCharacterSetOverride(aCharsetOverride);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ mCharsetOverride = aCharsetOverride; // synchronize member variable
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHasNewMessages(bool *hasNewMessages)
+{
+ NS_ENSURE_ARG_POINTER(hasNewMessages);
+ *hasNewMessages = mNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetHasNewMessages(bool curNewMessages)
+{
+ if (curNewMessages != mNewMessages)
+ {
+ // Only change mru time if we're going from doesn't have new to has new.
+ // technically, we should probably update mru time for every new message
+ // but we would pay a performance penalty for that. If the user
+ // opens the folder, the mrutime will get updated anyway.
+ if (curNewMessages)
+ SetMRUTime();
+ bool oldNewMessages = mNewMessages;
+ mNewMessages = curNewMessages;
+ NotifyBoolPropertyChanged(kNewMessagesAtom, oldNewMessages, curNewMessages);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHasFolderOrSubfolderNewMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ bool hasNewMessages = mNewMessages;
+
+ if (!hasNewMessages)
+ {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ bool hasNew = false;
+ mSubFolders[i]->GetHasFolderOrSubfolderNewMessages(&hasNew);
+ if (hasNew)
+ {
+ hasNewMessages = true;
+ break;
+ }
+ }
+ }
+
+ *aResult = hasNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetGettingNewMessages(bool *gettingNewMessages)
+{
+ NS_ENSURE_ARG_POINTER(gettingNewMessages);
+ *gettingNewMessages = mGettingNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetGettingNewMessages(bool gettingNewMessages)
+{
+ mGettingNewMessages = gettingNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFirstNewMessage(nsIMsgDBHdr **firstNewMessage)
+{
+ //If there's not a db then there can't be new messages. Return failure since you
+ //should use HasNewMessages first.
+ if(!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsMsgKey key;
+ rv = mDatabase->GetFirstNew(&key);
+ if(NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
+ if(NS_FAILED(rv))
+ return rv;
+
+ return mDatabase->GetMsgHdrForKey(key, firstNewMessage);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ClearNewMessages()
+{
+ nsresult rv = NS_OK;
+ bool dbWasCached = mDatabase != nullptr;
+ if (!dbWasCached)
+ GetDatabase();
+
+ if (mDatabase)
+ {
+ uint32_t numNewKeys;
+ nsMsgKey *newMessageKeys;
+ rv = mDatabase->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv) && newMessageKeys)
+ {
+ m_saveNewMsgs.Clear();
+ m_saveNewMsgs.AppendElements(newMessageKeys, numNewKeys);
+ NS_Free(newMessageKeys);
+ }
+ mDatabase->ClearNewList(true);
+ }
+ if (!dbWasCached)
+ SetMsgDatabase(nullptr);
+
+ m_newMsgs.Clear();
+ mNumNewBiffMessages = 0;
+ return rv;
+}
+
+void nsMsgDBFolder::UpdateNewMessages()
+{
+ if (! (mFlags & nsMsgFolderFlags::Virtual))
+ {
+ bool hasNewMessages = false;
+ for (uint32_t keyIndex = 0; keyIndex < m_newMsgs.Length(); keyIndex++)
+ {
+ bool containsKey = false;
+ mDatabase->ContainsKey(m_newMsgs[keyIndex], &containsKey);
+ if (!containsKey)
+ continue;
+ bool isRead = false;
+ nsresult rv2 = mDatabase->IsRead(m_newMsgs[keyIndex], &isRead);
+ if (NS_SUCCEEDED(rv2) && !isRead)
+ {
+ hasNewMessages = true;
+ mDatabase->AddToNewList(m_newMsgs[keyIndex]);
+ }
+ }
+ SetHasNewMessages(hasNewMessages);
+ }
+}
+
+// helper function that gets the cache element that corresponds to the passed in file spec.
+// This could be static, or could live in another class - it's not specific to the current
+// nsMsgDBFolder. If it lived at a higher level, we could cache the account manager and folder cache.
+nsresult nsMsgDBFolder::GetFolderCacheElemFromFile(nsIFile *file, nsIMsgFolderCacheElement **cacheElement)
+{
+ nsresult result;
+ NS_ENSURE_ARG_POINTER(file);
+ NS_ENSURE_ARG_POINTER(cacheElement);
+ nsCOMPtr <nsIMsgFolderCache> folderCache;
+#ifdef DEBUG_bienvenu1
+ bool exists;
+ NS_ASSERTION(NS_SUCCEEDED(fileSpec->Exists(&exists)) && exists, "whoops, file doesn't exist, mac will break");
+#endif
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
+ if(NS_SUCCEEDED(result))
+ {
+ result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(result) && folderCache)
+ {
+ nsCString persistentPath;
+ result = file->GetPersistentDescriptor(persistentPath);
+ NS_ENSURE_SUCCESS(result, result);
+ result = folderCache->GetCacheElement(persistentPath, false, cacheElement);
+ }
+ }
+ return result;
+}
+
+nsresult nsMsgDBFolder::ReadDBFolderInfo(bool force)
+{
+ // Since it turns out to be pretty expensive to open and close
+ // the DBs all the time, if we have to open it once, get everything
+ // we might need while we're here
+ nsresult result = NS_OK;
+
+ // don't need to reload from cache if we've already read from cache,
+ // and, we might get stale info, so don't do it.
+ if (!mInitializedFromCache)
+ {
+ nsCOMPtr <nsIFile> dbPath;
+ result = GetFolderCacheKey(getter_AddRefs(dbPath), true /* createDBIfMissing */);
+ if (dbPath)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ result = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(result) && cacheElement)
+ result = ReadFromFolderCacheElem(cacheElement);
+ }
+ }
+
+ if (force || !mInitializedFromCache)
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ result = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(result))
+ {
+ if (folderInfo)
+ {
+ if (!mInitializedFromCache)
+ {
+ folderInfo->GetFlags((int32_t *)&mFlags);
+#ifdef DEBUG_bienvenu1
+ nsString name;
+ GetName(name);
+ NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & nsMsgFolderFlags::Trash), "lost trash flag");
+#endif
+ mInitializedFromCache = true;
+ }
+
+ folderInfo->GetNumMessages(&mNumTotalMessages);
+ folderInfo->GetNumUnreadMessages(&mNumUnreadMessages);
+ folderInfo->GetExpungedBytes(&mExpungedBytes);
+
+ nsCString utf8Name;
+ folderInfo->GetFolderName(utf8Name);
+ if (!utf8Name.IsEmpty())
+ CopyUTF8toUTF16(utf8Name, mName);
+
+ //These should be put in IMAP folder only.
+ //folderInfo->GetImapTotalPendingMessages(&mNumPendingTotalMessages);
+ //folderInfo->GetImapUnreadPendingMessages(&mNumPendingUnreadMessages);
+
+ folderInfo->GetCharacterSet(mCharset);
+ folderInfo->GetCharacterSetOverride(&mCharsetOverride);
+
+ if (db) {
+ bool hasnew;
+ nsresult rv;
+ rv = db->HasNew(&hasnew);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ }
+ else {
+ // we tried to open DB but failed - don't keep trying.
+ // If a DB is created, we will call this method with force == TRUE,
+ // and read from the db that way.
+ mInitializedFromCache = true;
+ }
+ if (db)
+ db->Close(false);
+ }
+ return result;
+}
+
+nsresult nsMsgDBFolder::SendFlagNotifications(nsIMsgDBHdr *item, uint32_t oldFlags, uint32_t newFlags)
+{
+ nsresult rv = NS_OK;
+ uint32_t changedFlags = oldFlags ^ newFlags;
+ if((changedFlags & nsMsgMessageFlags::Read) && (changedFlags & nsMsgMessageFlags::New))
+ {
+ //..so..if the msg is read in the folder and the folder has new msgs clear the account level and status bar biffs.
+ rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags);
+ rv = SetBiffState(nsMsgBiffState_NoMail);
+ }
+ else if(changedFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded
+ | nsMsgMessageFlags::IMAPDeleted | nsMsgMessageFlags::New | nsMsgMessageFlags::Offline))
+ rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags);
+ else if((changedFlags & nsMsgMessageFlags::Marked))
+ rv = NotifyPropertyFlagChanged(item, kFlaggedAtom, oldFlags, newFlags);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DownloadMessagesForOffline(nsIArray *messages, nsIMsgWindow *)
+{
+ NS_ASSERTION(false, "imap and news need to override this");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
+{
+ NS_ASSERTION(false, "imap and news need to override this");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMsgStore(nsIMsgPluggableStore **aStore)
+{
+ NS_ENSURE_ARG_POINTER(aStore);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ return server->GetMsgStore(aStore);
+}
+
+bool nsMsgDBFolder::VerifyOfflineMessage(nsIMsgDBHdr *msgHdr, nsIInputStream *fileStream)
+{
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(fileStream);
+ if (seekableStream)
+ {
+ uint64_t offset;
+ msgHdr->GetMessageOffset(&offset);
+ nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, offset);
+ char startOfMsg[100];
+ uint32_t bytesRead = 0;
+ uint32_t bytesToRead = sizeof(startOfMsg) - 1;
+ if (NS_SUCCEEDED(rv))
+ rv = fileStream->Read(startOfMsg, bytesToRead, &bytesRead);
+ startOfMsg[bytesRead] = '\0';
+ // check if message starts with From, or is a draft and starts with FCC
+ if (NS_FAILED(rv) || bytesRead != bytesToRead ||
+ (strncmp(startOfMsg, "From ", 5) && (! (mFlags & nsMsgFolderFlags::Drafts) || strncmp(startOfMsg, "FCC", 3))))
+ return false;
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream)
+{
+ NS_ENSURE_ARG(aFileStream);
+
+ *offset = *size = 0;
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ NS_ENSURE_TRUE(hdr, NS_OK);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hdr)
+ hdr->GetOfflineMessageSize(size);
+
+ bool reusable;
+ rv = GetMsgInputStream(hdr, &reusable, aFileStream);
+ // check if offline store really has the correct offset into the offline
+ // store by reading the first few bytes. If it doesn't, clear the offline
+ // flag on the msg and return false, which will fall back to reading the message
+ // from the server.
+ // We'll also advance the offset past the envelope header and
+ // X-Mozilla-Status lines.
+ nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(*aFileStream);
+ if (seekableStream)
+ {
+ seekableStream->Tell(offset);
+ char startOfMsg[200];
+ uint32_t bytesRead = 0;
+ uint32_t bytesToRead = sizeof(startOfMsg) - 1;
+ if (NS_SUCCEEDED(rv))
+ rv = (*aFileStream)->Read(startOfMsg, bytesToRead, &bytesRead);
+ startOfMsg[bytesRead] = '\0';
+ // check if message starts with From, or is a draft and starts with FCC
+ if (NS_FAILED(rv) || bytesRead != bytesToRead ||
+ (strncmp(startOfMsg, "From ", 5) && (! (mFlags & nsMsgFolderFlags::Drafts) || strncmp(startOfMsg, "FCC", 3))))
+ rv = NS_ERROR_FAILURE;
+ else
+ {
+ uint32_t msgOffset = 0;
+ // skip "From "/FCC line
+ bool foundNextLine = MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ if (foundNextLine && !strncmp(startOfMsg + msgOffset,
+ X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
+ {
+ // skip X-Mozilla-Status line
+ if (MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1))
+ {
+ if (!strncmp(startOfMsg + msgOffset,
+ X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN))
+ MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ }
+ }
+ int32_t findPos = MsgFindCharInSet(nsDependentCString(startOfMsg),
+ ":\n\r", msgOffset);
+ // Check that the first line is a header line, i.e., with a ':' in it.
+ // Or, the line starts with "From " - I've seen IMAP servers return
+ // a bogus "From " line without a ':'.
+ if (findPos != -1 && (startOfMsg[findPos] == ':' ||
+ !(strncmp(startOfMsg, "From ", 5))))
+ {
+ *offset += msgOffset;
+ *size -= msgOffset;
+ }
+ else
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, *offset);
+ else if (mDatabase)
+ mDatabase->MarkOffline(msgKey, false, nullptr);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetOfflineStoreOutputStream(nsIMsgDBHdr *aHdr,
+ nsIOutputStream **aOutputStream)
+{
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aHdr);
+
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(offlineStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool reusable;
+ rv = offlineStore->GetNewMsgOutputStream(this, &aHdr, &reusable,
+ aOutputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMsgInputStream(nsIMsgDBHdr *aMsgHdr, bool *aReusable,
+ nsIInputStream **aInputStream)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aReusable);
+ NS_ENSURE_ARG_POINTER(aInputStream);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString storeToken;
+ rv = aMsgHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t offset;
+ rv = msgStore->GetMsgInputStream(this, storeToken, &offset, aMsgHdr, aReusable,
+ aInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(*aInputStream));
+ if (seekableStream)
+ rv = seekableStream->Seek(PR_SEEK_SET, offset);
+ NS_WARNING_ASSERTION(seekableStream || !offset,
+ "non-zero offset w/ non-seekable stream");
+ return rv;
+}
+
+// path coming in is the root path without the leaf name,
+// on the way out, it's the whole path.
+nsresult nsMsgDBFolder::CreateFileForDB(const nsAString& userLeafName, nsIFile *path, nsIFile **dbFile)
+{
+ NS_ENSURE_ARG_POINTER(dbFile);
+
+ nsAutoString proposedDBName(userLeafName);
+ NS_MsgHashIfNecessary(proposedDBName);
+
+ // (note, the caller of this will be using the dbFile to call db->Open()
+ // will turn the path into summary file path, and append the ".msf" extension)
+ //
+ // we want db->Open() to create a new summary file
+ // so we have to jump through some hoops to make sure the .msf it will
+ // create is unique. now that we've got the "safe" proposedDBName,
+ // we append ".msf" to see if the file exists. if so, we make the name
+ // unique and then string off the ".msf" so that we pass the right thing
+ // into Open(). this isn't ideal, since this is not atomic
+ // but it will make do.
+ nsresult rv;
+ nsCOMPtr <nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath->InitWithFile(path);
+ proposedDBName.AppendLiteral(SUMMARY_SUFFIX);
+ dbPath->Append(proposedDBName);
+ bool exists;
+ dbPath->Exists(&exists);
+ if (exists)
+ {
+ rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath->GetLeafName(proposedDBName);
+ }
+ // now, take the ".msf" off
+ proposedDBName.SetLength(proposedDBName.Length() - NS_LITERAL_CSTRING(SUMMARY_SUFFIX).Length());
+ dbPath->SetLeafName(proposedDBName);
+
+ dbPath.swap(*dbFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase)
+{
+ NS_ENSURE_ARG_POINTER(aMsgDatabase);
+ GetDatabase();
+ if (!mDatabase)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(*aMsgDatabase = mDatabase);
+ mDatabase->SetLastUseTime(PR_Now());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase *aMsgDatabase)
+{
+ if (mDatabase)
+ {
+ // commit here - db might go away when all these refs are released.
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ mDatabase->ClearCachedHdrs();
+ if (!aMsgDatabase)
+ {
+ uint32_t numNewKeys;
+ nsMsgKey *newMessageKeys;
+ nsresult rv = mDatabase->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv) && newMessageKeys)
+ {
+ m_newMsgs.Clear();
+ m_newMsgs.AppendElements(newMessageKeys, numNewKeys);
+ }
+ NS_Free(newMessageKeys);
+ }
+ }
+ mDatabase = aMsgDatabase;
+
+ if (aMsgDatabase)
+ aMsgDatabase->AddListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDatabaseOpen(bool *aOpen)
+{
+ NS_ENSURE_ARG_POINTER(aOpen);
+
+ *aOpen = (mDatabase != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetBackupMsgDatabase(nsIMsgDatabase** aMsgDatabase)
+{
+ NS_ENSURE_ARG_POINTER(aMsgDatabase);
+ nsresult rv = OpenBackupMsgDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mBackupDatabase)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aMsgDatabase = mBackupDatabase);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **database)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnReadChanged(nsIDBChangeListener * aInstigator)
+{
+ /* do nothing. if you care about this, override it. see nsNewsFolder.cpp */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnJunkScoreChanged(nsIDBChangeListener * aInstigator)
+{
+ NotifyFolderEvent(mJunkStatusChangedAtom);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener *aInstigator)
+{
+ /* do nothing. if you care about this, override it.*/
+ return NS_OK;
+}
+
+// 1. When the status of a message changes.
+NS_IMETHODIMP nsMsgDBFolder::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener * aInstigator)
+{
+ if(aHdrChanged)
+ {
+ SendFlagNotifications(aHdrChanged, aOldFlags, aNewFlags);
+ UpdateSummaryTotals(true);
+ }
+
+ // The old state was new message state
+ // We check and see if this state has changed
+ if(aOldFlags & nsMsgMessageFlags::New)
+ {
+ // state changing from new to something else
+ if (!(aNewFlags & nsMsgMessageFlags::New))
+ CheckWithNewMessagesStatus(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::CheckWithNewMessagesStatus(bool messageAdded)
+{
+ bool hasNewMessages;
+ if (messageAdded)
+ SetHasNewMessages(true);
+ else // message modified or deleted
+ {
+ if(mDatabase)
+ {
+ nsresult rv = mDatabase->HasNew(&hasNewMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetHasNewMessages(hasNewMessages);
+ }
+ }
+
+ return NS_OK;
+}
+
+// 3. When a message gets deleted, we need to see if it was new
+// When we lose a new message we need to check if there are still new messages
+NS_IMETHODIMP nsMsgDBFolder::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener * aInstigator)
+{
+ // check to see if a new message is being deleted
+ // as in this case, if there is only one new message and it's being deleted
+ // the folder newness has to be cleared.
+ CheckWithNewMessagesStatus(false);
+ // Remove all processing flags. This is generally a good thing although
+ // undo-ing a message back into position will not re-gain the flags.
+ nsMsgKey msgKey;
+ aHdrChanged->GetMessageKey(&msgKey);
+ AndProcessingFlags(msgKey, 0);
+ return OnHdrAddedOrDeleted(aHdrChanged, false);
+}
+
+// 2. When a new messages gets added, we need to see if it's new.
+NS_IMETHODIMP nsMsgDBFolder::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey , int32_t aFlags,
+ nsIDBChangeListener * aInstigator)
+{
+ if(aFlags & nsMsgMessageFlags::New)
+ CheckWithNewMessagesStatus(true);
+ return OnHdrAddedOrDeleted(aHdrChanged, true);
+}
+
+nsresult nsMsgDBFolder::OnHdrAddedOrDeleted(nsIMsgDBHdr *aHdrChanged, bool added)
+{
+ if(added)
+ NotifyItemAdded(aHdrChanged);
+ else
+ NotifyItemRemoved(aHdrChanged);
+ UpdateSummaryTotals(true);
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent,
+ nsIDBChangeListener * aInstigator)
+{
+ nsCOMPtr<nsIMsgDBHdr> hdrChanged;
+ mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(hdrChanged));
+ //In reality we probably want to just change the parent because otherwise we will lose things like
+ //selection.
+ if (hdrChanged)
+ {
+ //First delete the child from the old threadParent
+ OnHdrAddedOrDeleted(hdrChanged, false);
+ //Then add it to the new threadParent
+ OnHdrAddedOrDeleted(hdrChanged, true);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ if (mBackupDatabase && instigator == mBackupDatabase)
+ {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ }
+ else if (mDatabase)
+ {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(bool *retval)
+{
+ NS_ENSURE_ARG_POINTER(retval);
+ int32_t numTotalMessages;
+
+ // is there any reason to return false?
+ if (!mDatabase)
+ *retval = true;
+ else if (NS_SUCCEEDED(GetTotalMessages(false, &numTotalMessages)) && numTotalMessages <= 0)
+ *retval = true;
+ else
+ *retval = false;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::MsgFitsDownloadCriteria(nsMsgKey msgKey, bool *result)
+{
+ if(!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if(NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // check if we already have this message body offline
+ if (! (msgFlags & nsMsgMessageFlags::Offline))
+ {
+ *result = true;
+ // check against the server download size limit .
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ {
+ bool limitDownloadSize = false;
+ rv = incomingServer->GetLimitOfflineMessageSize(&limitDownloadSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (limitDownloadSize)
+ {
+ int32_t maxDownloadMsgSize = 0;
+ uint32_t msgSize;
+ hdr->GetMessageSize(&msgSize);
+ rv = incomingServer->GetMaxMessageSize(&maxDownloadMsgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ maxDownloadMsgSize *= 1024;
+ if (msgSize > (uint32_t) maxDownloadMsgSize)
+ *result = false;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSupportsOffline(bool *aSupportsOffline)
+{
+ NS_ENSURE_ARG_POINTER(aSupportsOffline);
+ if (mFlags & nsMsgFolderFlags::Virtual)
+ {
+ *aSupportsOffline = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!server)
+ return NS_ERROR_FAILURE;
+
+ int32_t offlineSupportLevel;
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ *aSupportsOffline = (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ShouldStoreMsgOffline(nsMsgKey msgKey, bool *result)
+{
+ NS_ENSURE_ARG(result);
+ uint32_t flags = 0;
+ *result = false;
+ GetFlags(&flags);
+ return flags & nsMsgFolderFlags::Offline ? MsgFitsDownloadCriteria(msgKey, result) : NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::HasMsgOffline(nsMsgKey msgKey, bool *result)
+{
+ NS_ENSURE_ARG(result);
+ *result = false;
+ GetDatabase();
+ if(!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if(NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline))
+ *result = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsCOMPtr<nsIMsgFolder> subMsgFolder;
+ GetDatabase();
+ if (!mDatabase)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ nsresult rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (hdr)
+ {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // Check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline))
+ {
+ NS_IF_ADDREF(*aMsgFolder = this);
+ return NS_OK;
+ }
+ }
+ // it's okay to not get a folder. Folder is remain unchanged in that case.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFlags(uint32_t *_retval)
+{
+ ReadDBFolderInfo(false);
+ *_retval = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = NS_OK;
+ nsCString charset;
+
+ element->GetInt32Property("flags", (int32_t *) &mFlags);
+ element->GetInt32Property("totalMsgs", &mNumTotalMessages);
+ element->GetInt32Property("totalUnreadMsgs", &mNumUnreadMessages);
+ element->GetInt32Property("pendingUnreadMsgs", &mNumPendingUnreadMessages);
+ element->GetInt32Property("pendingMsgs", &mNumPendingTotalMessages);
+ element->GetInt64Property("expungedBytes", &mExpungedBytes);
+ element->GetInt64Property("folderSize", &mFolderSize);
+ element->GetStringProperty("charset", mCharset);
+
+#ifdef DEBUG_bienvenu1
+ nsCString uri;
+ GetURI(uri);
+ printf("read total %ld for %s\n", mNumTotalMessages, uri.get());
+#endif
+ mInitializedFromCache = true;
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetFolderCacheKey(nsIFile **aFile, bool createDBIfMissing /* = false */)
+{
+ nsresult rv;
+ nsCOMPtr <nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+
+ // now we put a new file in aFile, because we're going to change it.
+ nsCOMPtr <nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dbPath)
+ {
+ dbPath->InitWithFile(path);
+ // if not a server, we need to convert to a db Path with .msf on the end
+ bool isServer = false;
+ GetIsServer(&isServer);
+
+ // if it's a server, we don't need the .msf appended to the name
+ if (!isServer)
+ {
+ nsCOMPtr <nsIFile> summaryName;
+ rv = GetSummaryFileLocation(dbPath, getter_AddRefs(summaryName));
+ dbPath->InitWithFile(summaryName);
+
+ // create the .msf file
+ // see bug #244217 for details
+ bool exists;
+ if (createDBIfMissing && NS_SUCCEEDED(dbPath->Exists(&exists)) && !exists) {
+ rv = dbPath->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ NS_IF_ADDREF(*aFile = dbPath);
+ return rv;
+}
+
+nsresult nsMsgDBFolder::FlushToFolderCache()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && accountManager)
+ {
+ nsCOMPtr<nsIMsgFolderCache> folderCache;
+ rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(rv) && folderCache)
+ rv = WriteToFolderCache(folderCache, false);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCache(nsIMsgFolderCache *folderCache, bool deep)
+{
+ nsresult rv = NS_OK;
+
+ if (folderCache)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ nsCOMPtr <nsIFile> dbPath;
+ rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+#ifdef DEBUG_bienvenu1
+ bool exists;
+ NS_ASSERTION(NS_SUCCEEDED(dbPath->Exists(&exists)) && exists, "file spec we're adding to cache should exist");
+#endif
+ if (NS_SUCCEEDED(rv) && dbPath)
+ {
+ nsCString persistentPath;
+ rv = dbPath->GetPersistentDescriptor(persistentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderCache->GetCacheElement(persistentPath, true, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement)
+ rv = WriteToFolderCacheElem(cacheElement);
+ }
+ }
+
+ if (!deep)
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ continue;
+
+ if (folderCache)
+ {
+ rv = msgFolder->WriteToFolderCache(folderCache, true);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = NS_OK;
+
+ element->SetInt32Property("flags", (int32_t) mFlags);
+ element->SetInt32Property("totalMsgs", mNumTotalMessages);
+ element->SetInt32Property("totalUnreadMsgs", mNumUnreadMessages);
+ element->SetInt32Property("pendingUnreadMsgs", mNumPendingUnreadMessages);
+ element->SetInt32Property("pendingMsgs", mNumPendingTotalMessages);
+ element->SetInt64Property("expungedBytes", mExpungedBytes);
+ element->SetInt64Property("folderSize", mFolderSize);
+ element->SetStringProperty("charset", mCharset);
+
+#ifdef DEBUG_bienvenu1
+ nsCString uri;
+ GetURI(uri);
+ printf("writing total %ld for %s\n", mNumTotalMessages, uri.get());
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
+{
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsMsgKey msgKey;
+ aMessage->GetMessageKey(&msgKey);
+
+ if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
+ mDatabase->MarkReplied(msgKey, true, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
+ mDatabase->MarkForwarded(msgKey, true, nullptr);
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::AddMarkAllReadUndoAction(nsIMsgWindow *msgWindow,
+ nsMsgKey *thoseMarked,
+ uint32_t numMarked)
+{
+ RefPtr<nsMsgReadStateTxn> readStateTxn = new nsMsgReadStateTxn();
+ if (!readStateTxn)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = readStateTxn->Init(this, numMarked, thoseMarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = readStateTxn->SetTransactionType(nsIMessenger::eMarkAllMsg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ rv = msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = txnMgr->DoTransaction(readStateTxn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv = GetDatabase();
+ m_newMsgs.Clear();
+
+ if (NS_SUCCEEDED(rv))
+ {
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+ nsMsgKey *thoseMarked;
+ uint32_t numMarked;
+ rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setup a undo-state
+ if (aMsgWindow && numMarked)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
+ free(thoseMarked);
+ }
+
+ SetHasNewMessages(false);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::MarkThreadRead(nsIMsgThread *thread)
+{
+ nsresult rv = GetDatabase();
+ if(NS_SUCCEEDED(rv))
+ {
+ nsMsgKey *keys;
+ uint32_t numKeys;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, &numKeys, &keys);
+ free(keys);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnStartRunningUrl(nsIURI *aUrl)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl)
+ {
+ bool updatingFolder = false;
+ if (NS_SUCCEEDED(mailUrl->GetUpdatingFolder(&updatingFolder)) && updatingFolder)
+ NotifyFolderEvent(mFolderLoadedAtom);
+
+ // be sure to remove ourselves as a url listener
+ mailUrl->UnRegisterListener(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetRetentionSettings(nsIMsgRetentionSettings **settings)
+{
+ NS_ENSURE_ARG_POINTER(settings);
+ *settings = nullptr;
+ nsresult rv = NS_OK;
+ bool useServerDefaults = false;
+ if (!m_retentionSettings)
+ {
+ nsCString useServerRetention;
+ GetStringProperty(kUseServerRetentionProp, useServerRetention);
+ if (useServerRetention.EqualsLiteral("1"))
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ {
+ rv = incomingServer->GetRetentionSettings(settings);
+ useServerDefaults = true;
+ }
+ }
+ else
+ {
+ GetDatabase();
+ if (mDatabase)
+ {
+ // get the settings from the db - if the settings from the db say the folder
+ // is not overriding the incoming server settings, get the settings from the
+ // server.
+ rv = mDatabase->GetMsgRetentionSettings(settings);
+ if (NS_SUCCEEDED(rv) && *settings)
+ {
+ (*settings)->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ NS_IF_RELEASE(*settings);
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ incomingServer->GetRetentionSettings(settings);
+ }
+ if (useServerRetention.EqualsLiteral("1") != useServerDefaults)
+ {
+ if (useServerDefaults)
+ useServerRetention.AssignLiteral("1");
+ else
+ useServerRetention.AssignLiteral("0");
+ SetStringProperty(kUseServerRetentionProp, useServerRetention);
+ }
+ }
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ // Only cache the retention settings if we've overridden the server
+ // settings (otherwise, we won't notice changes to the server settings).
+ if (!useServerDefaults)
+ m_retentionSettings = *settings;
+ }
+ else
+ NS_IF_ADDREF(*settings = m_retentionSettings);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetRetentionSettings(nsIMsgRetentionSettings *settings)
+{
+ bool useServerDefaults;
+ nsCString useServerRetention;
+
+ settings->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults)
+ {
+ useServerRetention.AssignLiteral("1");
+ m_retentionSettings = nullptr;
+ }
+ else
+ {
+ useServerRetention.AssignLiteral("0");
+ m_retentionSettings = settings;
+ }
+ SetStringProperty(kUseServerRetentionProp, useServerRetention);
+ GetDatabase();
+ if (mDatabase)
+ mDatabase->SetMsgRetentionSettings(settings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDownloadSettings(nsIMsgDownloadSettings **settings)
+{
+ NS_ENSURE_ARG_POINTER(settings);
+ nsresult rv = NS_OK;
+ if (!m_downloadSettings)
+ {
+ GetDatabase();
+ if (mDatabase)
+ {
+ // get the settings from the db - if the settings from the db say the folder
+ // is not overriding the incoming server settings, get the settings from the
+ // server.
+ rv = mDatabase->GetMsgDownloadSettings(getter_AddRefs(m_downloadSettings));
+ if (NS_SUCCEEDED(rv) && m_downloadSettings)
+ {
+ bool useServerDefaults;
+ m_downloadSettings->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ incomingServer->GetDownloadSettings(getter_AddRefs(m_downloadSettings));
+ }
+ }
+ }
+ }
+ NS_IF_ADDREF(*settings = m_downloadSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetDownloadSettings(nsIMsgDownloadSettings *settings)
+{
+ m_downloadSettings = settings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsCommandEnabled(const nsACString& command, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = true;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::WriteStartOfNewLocalMessage()
+{
+ nsAutoCString result;
+ uint32_t writeCount;
+ time_t now = time ((time_t*) 0);
+ char *ct = ctime(&now);
+ ct[24] = 0;
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+ m_bytesAddedToLocalMsg = result.Length();
+
+ nsCOMPtr <nsISeekableStream> seekable;
+
+ if (m_offlineHeader)
+ seekable = do_QueryInterface(m_tempMessageStream);
+
+ m_tempMessageStream->Write(result.get(), result.Length(),
+ &writeCount);
+
+ NS_NAMED_LITERAL_CSTRING(MozillaStatus, "X-Mozilla-Status: 0001" MSG_LINEBREAK);
+ m_tempMessageStream->Write(MozillaStatus.get(), MozillaStatus.Length(),
+ &writeCount);
+ m_bytesAddedToLocalMsg += writeCount;
+ NS_NAMED_LITERAL_CSTRING(MozillaStatus2, "X-Mozilla-Status2: 00000000" MSG_LINEBREAK);
+ m_bytesAddedToLocalMsg += MozillaStatus2.Length();
+ return m_tempMessageStream->Write(MozillaStatus2.get(),
+ MozillaStatus2.Length(), &writeCount);
+}
+
+nsresult nsMsgDBFolder::StartNewOfflineMessage()
+{
+ bool isLocked;
+ GetLocked(&isLocked);
+ bool hasSemaphore = false;
+ if (isLocked)
+ {
+ // it's OK if we, the folder, have the semaphore.
+ TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
+ if (!hasSemaphore)
+ {
+ NS_WARNING("folder locked trying to download offline");
+ return NS_MSG_FOLDER_BUSY;
+ }
+ }
+ nsresult rv = GetOfflineStoreOutputStream(m_offlineHeader,
+ getter_AddRefs(m_tempMessageStream));
+ if (NS_SUCCEEDED(rv) && !hasSemaphore)
+ AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_SUCCEEDED(rv))
+ WriteStartOfNewLocalMessage();
+ m_numOfflineMsgLines = 0;
+ return rv;
+}
+
+nsresult nsMsgDBFolder::EndNewOfflineMessage()
+{
+ nsCOMPtr <nsISeekableStream> seekable;
+ int64_t curStorePos;
+ uint64_t messageOffset;
+ uint32_t messageSize;
+
+ nsMsgKey messageKey;
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_offlineHeader->GetMessageKey(&messageKey);
+ if (m_tempMessageStream)
+ seekable = do_QueryInterface(m_tempMessageStream);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+
+ if (seekable)
+ {
+ mDatabase->MarkOffline(messageKey, true, nullptr);
+ m_tempMessageStream->Flush();
+ int64_t tellPos;
+ seekable->Tell(&tellPos);
+ curStorePos = tellPos;
+
+ // N.B. This only works if we've set the offline flag for the message,
+ // so be careful about moving the call to MarkOffline above.
+ m_offlineHeader->GetMessageOffset(&messageOffset);
+ curStorePos -= messageOffset;
+ m_offlineHeader->SetOfflineMessageSize(curStorePos);
+ m_offlineHeader->GetMessageSize(&messageSize);
+ messageSize += m_bytesAddedToLocalMsg;
+ // unix/mac has a one byte line ending, but the imap server returns
+ // crlf terminated lines.
+ if (MSG_LINEBREAK_LEN == 1)
+ messageSize -= m_numOfflineMsgLines;
+
+ // We clear the offline flag on the message if the size
+ // looks wrong. Check if we're off by more than one byte per line.
+ if (messageSize > (uint32_t) curStorePos &&
+ (messageSize - (uint32_t) curStorePos) > (uint32_t) m_numOfflineMsgLines)
+ {
+ mDatabase->MarkOffline(messageKey, false, nullptr);
+ // we should truncate the offline store at messageOffset
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (msgStore)
+ // this closes the stream
+ msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader);
+ else
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+#ifdef _DEBUG
+ nsAutoCString message("Offline message too small: messageSize=");
+ message.AppendInt(messageSize);
+ message.Append(" curStorePos=");
+ message.AppendInt(curStorePos);
+ message.Append(" numOfflineMsgLines=");
+ message.AppendInt(m_numOfflineMsgLines);
+ message.Append(" bytesAdded=");
+ message.AppendInt(m_bytesAddedToLocalMsg);
+ NS_ERROR(message.get());
+#endif
+ m_offlineHeader = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ else
+ m_offlineHeader->SetLineCount(m_numOfflineMsgLines);
+ }
+ if (msgStore)
+ msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader);
+
+ m_offlineHeader = nullptr;
+ if (m_tempMessageStream)
+ {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::CompactOfflineStore(nsIMsgWindow *inWindow, nsIUrlListener *aListener)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolderCompactor> folderCompactor = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->Compact(this, true, aListener, inWindow);
+}
+
+class AutoCompactEvent : public mozilla::Runnable
+{
+public:
+ AutoCompactEvent(nsIMsgWindow *aMsgWindow, nsMsgDBFolder *aFolder)
+ : mMsgWindow(aMsgWindow), mFolder(aFolder)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mFolder)
+ mFolder->HandleAutoCompactEvent(mMsgWindow);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ RefPtr<nsMsgDBFolder> mFolder;
+};
+
+nsresult nsMsgDBFolder::HandleAutoCompactEvent(nsIMsgWindow *aWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIArray> allServers;
+ rv = accountMgr->GetAllServers(getter_AddRefs(allServers));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numServers = 0, serverIndex = 0;
+ rv = allServers->GetLength(&numServers);
+ int32_t offlineSupportLevel;
+ if (numServers > 0)
+ {
+ nsCOMPtr<nsIMutableArray> folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMutableArray> offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t totalExpungedBytes = 0;
+ int64_t offlineExpungedBytes = 0;
+ int64_t localExpungedBytes = 0;
+ do
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!msgStore)
+ continue;
+ bool supportsCompaction;
+ msgStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction)
+ continue;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if(NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIArray> allDescendents;
+ rootFolder->GetDescendants(getter_AddRefs(allDescendents));
+ uint32_t cnt = 0;
+ rv = allDescendents->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t expungedBytes = 0;
+ if (offlineSupportLevel > 0)
+ {
+ uint32_t flags;
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i);
+ expungedBytes = 0;
+ folder->GetFlags(&flags);
+ if (flags & nsMsgFolderFlags::Offline)
+ folder->GetExpungedBytes(&expungedBytes);
+ if (expungedBytes > 0 )
+ {
+ offlineFolderArray->AppendElement(folder, false);
+ offlineExpungedBytes += expungedBytes;
+ }
+ }
+ }
+ else //pop or local
+ {
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i);
+ expungedBytes = 0;
+ folder->GetExpungedBytes(&expungedBytes);
+ if (expungedBytes > 0 )
+ {
+ folderArray->AppendElement(folder, false);
+ localExpungedBytes += expungedBytes;
+ }
+ }
+ }
+ }
+ }
+ while (++serverIndex < numServers);
+ totalExpungedBytes = localExpungedBytes + offlineExpungedBytes;
+ int32_t purgeThreshold;
+ rv = GetPurgeThreshold(&purgeThreshold);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (totalExpungedBytes > (purgeThreshold * 1024))
+ {
+ bool okToCompact = false;
+ nsCOMPtr<nsIPrefService> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIPrefBranch> branch;
+ pref->GetBranch("", getter_AddRefs(branch));
+
+ bool askBeforePurge;
+ branch->GetBoolPref(PREF_MAIL_PURGE_ASK, &askBeforePurge);
+ if (askBeforePurge && aWindow)
+ {
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString dialogTitle;
+ nsString confirmString;
+ nsString checkboxText;
+ nsString buttonCompactNowText;
+ nsAutoString compactSize;
+ FormatFileSize(totalExpungedBytes, true, compactSize);
+ const char16_t* params[] = { compactSize.get() };
+ rv = bundle->GetStringFromName(u"autoCompactAllFoldersTitle", getter_Copies(dialogTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->FormatStringFromName(u"autoCompactAllFoldersText",
+ params, 1, getter_Copies(confirmString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"autoCompactAlwaysAskCheckbox",
+ getter_Copies(checkboxText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName(u"compactNowButton",
+ getter_Copies(buttonCompactNowText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool alwaysAsk = true; // "Always ask..." - checked by default.
+ int32_t buttonPressed = 0;
+
+ nsCOMPtr<nsIPrompt> dialog;
+ rv = aWindow->GetPromptDialog(getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+ rv = dialog->ConfirmEx(dialogTitle.get(), confirmString.get(), buttonFlags,
+ buttonCompactNowText.get(), nullptr, nullptr,
+ checkboxText.get(), &alwaysAsk, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!buttonPressed)
+ {
+ okToCompact = true;
+ if (!alwaysAsk) // [ ] Always ask me before compacting folders automatically
+ branch->SetBoolPref(PREF_MAIL_PURGE_ASK, false);
+ }
+ }
+ else
+ okToCompact = aWindow || !askBeforePurge;
+
+ if (okToCompact)
+ {
+ nsCOMPtr <nsIAtom> aboutToCompactAtom = MsgGetAtom("AboutToCompact");
+ NotifyFolderEvent(aboutToCompactAtom);
+
+ if (localExpungedBytes > 0)
+ {
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (offlineExpungedBytes > 0)
+ folderCompactor->CompactFolders(folderArray, offlineFolderArray, nullptr, aWindow);
+ else
+ folderCompactor->CompactFolders(folderArray, nullptr, nullptr, aWindow);
+ }
+ else if (offlineExpungedBytes > 0)
+ CompactAllOfflineStores(nullptr, aWindow, offlineFolderArray);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow)
+{
+ // we don't check for null aWindow, because this routine can get called
+ // in unit tests where we have no window. Just assume not OK if no window.
+ bool prompt;
+ nsresult rv = GetPromptPurgeThreshold(&prompt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PRTime timeNow = PR_Now(); //time in microseconds
+ PRTime timeAfterOneHourOfLastPurgeCheck = gtimeOfLastPurgeCheck + oneHour;
+ if (timeAfterOneHourOfLastPurgeCheck < timeNow && prompt)
+ {
+ gtimeOfLastPurgeCheck = timeNow;
+ nsCOMPtr<nsIRunnable> event = new AutoCompactEvent(aWindow, this);
+ // Post this as an event because it can put up an alert, which
+ // might cause issues depending on the stack when we are called.
+ if (event)
+ NS_DispatchToCurrentThread(event);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CompactAllOfflineStores(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aWindow,
+ nsIArray *aOfflineFolderArray)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor
+ = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->CompactFolders(nullptr, aOfflineFolderArray, aUrlListener, aWindow);
+}
+
+nsresult
+nsMsgDBFolder::GetPromptPurgeThreshold(bool *aPrompt)
+{
+ NS_ENSURE_ARG(aPrompt);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ rv = prefBranch->GetBoolPref(PREF_MAIL_PROMPT_PURGE_THRESHOLD, aPrompt);
+ if (NS_FAILED(rv))
+ {
+ *aPrompt = false;
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::GetPurgeThreshold(int32_t *aThreshold)
+{
+ NS_ENSURE_ARG(aThreshold);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ int32_t thresholdMB = 20;
+ bool thresholdMigrated = false;
+ prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, &thresholdMB);
+ prefBranch->GetBoolPref(PREF_MAIL_PURGE_MIGRATED, &thresholdMigrated);
+ if (!thresholdMigrated)
+ {
+ *aThreshold = 20480;
+ (void) prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD, aThreshold);
+ if (*aThreshold/1024 != thresholdMB)
+ {
+ thresholdMB = std::max(1, *aThreshold/1024);
+ prefBranch->SetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, thresholdMB);
+ }
+ prefBranch->SetBoolPref(PREF_MAIL_PURGE_MIGRATED, true);
+ }
+ *aThreshold = thresholdMB * 1024;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP //called on the folder that is renamed or about to be deleted
+nsMsgDBFolder::MatchOrChangeFilterDestination(nsIMsgFolder *newFolder, bool caseInsensitive, bool *found)
+{
+ NS_ENSURE_ARG_POINTER(found);
+ nsCString oldUri;
+ nsresult rv = GetURI(oldUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString newUri;
+ if (newFolder) //for matching uri's this will be null
+ {
+ rv = newFolder->GetURI(newUri);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIArray> allServers;
+ rv = accountMgr->GetAllServers(getter_AddRefs(allServers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numServers;
+ rv = allServers->GetLength(&numServers);
+ for (uint32_t serverIndex = 0; serverIndex < numServers; serverIndex++)
+ {
+ nsCOMPtr <nsIMsgIncomingServer> server = do_QueryElementAt(allServers, serverIndex);
+ if (server)
+ {
+ bool canHaveFilters;
+ rv = server->GetCanHaveFilters(&canHaveFilters);
+ if (NS_SUCCEEDED(rv) && canHaveFilters)
+ {
+ // update the filterlist to match the new folder name
+ rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList)
+ {
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
+ if (NS_SUCCEEDED(rv) && *found && newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ // update the editable filterlist to match the new folder name
+ rv = server->GetEditableFilterList(nullptr, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList)
+ {
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
+ if (NS_SUCCEEDED(rv) && *found && newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDBTransferInfo(nsIDBFolderInfo **aTransferInfo)
+{
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr <nsIMsgDatabase> db;
+ GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
+ if (dbFolderInfo)
+ dbFolderInfo->GetTransferInfo(aTransferInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetDBTransferInfo(nsIDBFolderInfo *aTransferInfo)
+{
+ NS_ENSURE_ARG(aTransferInfo);
+ nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr <nsIMsgDatabase> db;
+ GetMsgDatabase(getter_AddRefs(db));
+ if (db)
+ {
+ db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if(dbFolderInfo)
+ {
+ dbFolderInfo->InitFromTransferInfo(aTransferInfo);
+ dbFolderInfo->SetBooleanProperty("forceReparse", false);
+ }
+ db->SetSummaryValid(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetStringProperty(const char *propertyName, nsACString& propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ nsCOMPtr <nsIFile> dbPath;
+ nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ rv = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (cacheElement) //try to get from cache
+ rv = cacheElement->GetStringProperty(propertyName, propertyValue);
+ if (NS_FAILED(rv)) //if failed, then try to get from db
+ {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ bool exists;
+ rv = dbPath->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_MSG_ERROR_FOLDER_MISSING;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv))
+ rv = folderInfo->GetCharProperty(propertyName, propertyValue);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetStringProperty(const char *propertyName, const nsACString& propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ nsCOMPtr <nsIFile> dbPath;
+ GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (cacheElement) //try to set in the cache
+ cacheElement->SetStringProperty(propertyName, propertyValue);
+ }
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if(NS_SUCCEEDED(rv))
+ {
+ folderInfo->SetCharProperty(propertyName, propertyValue);
+ db->Commit(nsMsgDBCommitType::kLargeCommit); //commiting the db also commits the cache
+ }
+ return NS_OK;
+}
+
+// Get/Set ForcePropertyEmpty is only used with inherited properties
+NS_IMETHODIMP
+nsMsgDBFolder::GetForcePropertyEmpty(const char *aPropertyName, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.Append(NS_LITERAL_CSTRING(".empty"));
+ nsCString value;
+ GetStringProperty(nameEmpty.get(), value);
+ *_retval = value.EqualsLiteral("true");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetForcePropertyEmpty(const char *aPropertyName, bool aValue)
+{
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.Append(NS_LITERAL_CSTRING(".empty"));
+ return SetStringProperty(nameEmpty.get(),
+ aValue ? NS_LITERAL_CSTRING("true") : NS_LITERAL_CSTRING(""));
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetInheritedStringProperty(const char *aPropertyName, nsACString& aPropertyValue)
+{
+ NS_ENSURE_ARG_POINTER(aPropertyName);
+ nsCString value;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ bool forceEmpty = false;
+
+ if (!mIsServer)
+ {
+ GetForcePropertyEmpty(aPropertyName, &forceEmpty);
+ }
+ else
+ {
+ // root folders must get their values from the server
+ GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetForcePropertyEmpty(aPropertyName, &forceEmpty);
+ }
+
+ if (forceEmpty)
+ {
+ aPropertyValue.Truncate();
+ return NS_OK;
+ }
+
+ // servers will automatically inherit from the preference mail.server.default.(propertyName)
+ if (server)
+ return server->GetCharValue(aPropertyName, aPropertyValue);
+
+ GetStringProperty(aPropertyName, value);
+ if (value.IsEmpty())
+ {
+ // inherit from the parent
+ nsCOMPtr<nsIMsgFolder> parent;
+ GetParent(getter_AddRefs(parent));
+ if (parent)
+ return parent->GetInheritedStringProperty(aPropertyName, aPropertyValue);
+ }
+
+ aPropertyValue.Assign(value);
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ uint32_t *proIndices;
+ uint32_t *antiIndices;
+ rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aJunkMailPlugin->ClassifyTraitsInMessage(aURI, count, proIndices, antiIndices, this, aMsgWindow, this);
+ NS_Free(proIndices);
+ NS_Free(antiIndices);
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::SpamFilterClassifyMessages(const char **aURIArray, uint32_t aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin)
+{
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgTraitService> traitService(do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count;
+ uint32_t *proIndices;
+ uint32_t *antiIndices;
+ rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aJunkMailPlugin->ClassifyTraitsInMessages(aURICount, aURIArray, count,
+ proIndices, antiIndices, this, aMsgWindow, this);
+ NS_Free(proIndices);
+ NS_Free(antiIndices);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnMessageClassified(const char *aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+{
+ if (!aMsgURI) // This signifies end of batch.
+ {
+ nsresult rv = NS_OK;
+ // Apply filters if needed.
+ uint32_t length;
+ if (mPostBayesMessagesToFilter &&
+ NS_SUCCEEDED(mPostBayesMessagesToFilter->GetLength(&length)) &&
+ length)
+ {
+ // Apply post-bayes filtering.
+ nsCOMPtr<nsIMsgFilterService>
+ filterService(do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ // We use a null nsIMsgWindow because we don't want some sort of ui
+ // appearing in the middle of automatic filtering (plus I really don't
+ // want to propagate that value.)
+ rv = filterService->ApplyFilters(nsMsgFilterType::PostPlugin,
+ mPostBayesMessagesToFilter,
+ this, nullptr, nullptr);
+ mPostBayesMessagesToFilter->Clear();
+ }
+
+ // Bail if we didn't actually classify any messages.
+ if (mClassifiedMsgKeys.IsEmpty())
+ return rv;
+
+ // Notify that we classified some messages.
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMutableArray> classifiedMsgHdrs =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numKeys = mClassifiedMsgKeys.Length();
+ for (uint32_t i = 0 ; i < numKeys ; ++i)
+ {
+ nsMsgKey msgKey = mClassifiedMsgKeys[i];
+ bool hasKey;
+ // It is very possible for a message header to no longer be around because
+ // a filter moved it.
+ rv = mDatabase->ContainsKey(msgKey, &hasKey);
+ if (!NS_SUCCEEDED(rv) || !hasKey)
+ continue;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (!NS_SUCCEEDED(rv))
+ continue;
+ classifiedMsgHdrs->AppendElement(msgHdr, false);
+ }
+
+ // only generate the notification if there are some classified messages
+ if (NS_SUCCEEDED(classifiedMsgHdrs->GetLength(&length)) && length)
+ notifier->NotifyMsgsClassified(classifiedMsgHdrs,
+ mBayesJunkClassifying,
+ mBayesTraitClassifying);
+ mClassifiedMsgKeys.Clear();
+
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk)
+ {
+ mClassifiedMsgKeys.AppendElement(msgKey);
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyJunk);
+
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(aClassification == nsIJunkMailPlugin::JUNK ?
+ nsIJunkMailPlugin::IS_SPAM_SCORE:
+ nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "plugin");
+
+ nsAutoCString strPercent;
+ strPercent.AppendInt(aJunkPercent);
+ mDatabase->SetStringProperty(msgKey, "junkpercent", strPercent.get());
+
+ if (aClassification == nsIJunkMailPlugin::JUNK)
+ {
+ // IMAP has its own way of marking read.
+ if (!(mFlags & nsMsgFolderFlags::ImapBox))
+ {
+ bool markAsReadOnSpam;
+ (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam)
+ {
+ rv = mDatabase->MarkRead(msgKey, true, this);
+ if (!NS_SUCCEEDED(rv))
+ NS_WARNING("failed marking spam message as read");
+ }
+ }
+ // mail folders will log junk hits with move info. Perhaps we should
+ // add a log here for non-mail folders as well, that don't override
+ // onMessageClassified
+ //rv = spamSettings->LogJunkHit(msgHdr, false);
+ //NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnMessageTraitsClassified(const char *aMsgURI,
+ uint32_t aTraitCount,
+ uint32_t *aTraits,
+ uint32_t *aPercents)
+{
+ if (!aMsgURI) // This signifies end of batch
+ return NS_OK; // We are not handling batching
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (!(processingFlags & nsMsgProcessingFlags::ClassifyTraits))
+ return NS_OK;
+
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyTraits);
+
+ nsCOMPtr<nsIMsgTraitService> traitService;
+ traitService = do_GetService("@mozilla.org/msg-trait-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aTraitCount; i++)
+ {
+ if (aTraits[i] == nsIJunkMailPlugin::JUNK_TRAIT)
+ continue; // junk is processed by the junk listener
+ nsAutoCString traitId;
+ rv = traitService->GetId(aTraits[i], traitId);
+ traitId.Insert(NS_LITERAL_CSTRING("bayespercent/"), 0);
+ nsAutoCString strPercent;
+ strPercent.AppendInt(aPercents[i]);
+ mDatabase->SetStringPropertyByHdr(msgHdr, traitId.get(), strPercent.get());
+ }
+ return NS_OK;
+}
+
+/**
+ * Call the filter plugins (XXX currently just one)
+ */
+NS_IMETHODIMP
+nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow *aMsgWindow, bool *aFiltersRun)
+{
+ NS_ENSURE_ARG_POINTER(aFiltersRun);
+ *aFiltersRun = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ int32_t spamLevel = 0;
+
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverType;
+ server->GetType(serverType);
+
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ nsCOMPtr <nsIMsgFilterPlugin> filterPlugin;
+ server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+ if (!filterPlugin) // it's not an error not to have the filter plugin.
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIJunkMailPlugin> junkMailPlugin = do_QueryInterface(filterPlugin);
+ if (!junkMailPlugin) // we currently only support the junk mail plugin
+ return NS_OK;
+
+ // if it's a news folder, then we really don't support junk in the ui
+ // yet the legacy spamLevel seems to think we should analyze it.
+ // Maybe we should upgrade that, but for now let's not analyze. We'll
+ // let an extension set an inherited property if they really want us to
+ // analyze this. We need that anyway to allow extension-based overrides.
+ // When we finalize adding junk in news to core, we'll deal with the
+ // spamLevel issue
+
+ // if this is the junk folder, or the trash folder
+ // don't analyze for spam, because we don't care
+ //
+ // if it's the sent, unsent, templates, or drafts,
+ // don't analyze for spam, because the user
+ // created that message
+ //
+ // if it's a public imap folder, or another users
+ // imap folder, don't analyze for spam, because
+ // it's not ours to analyze
+ //
+
+ bool filterForJunk = true;
+ if (serverType.EqualsLiteral("rss") ||
+ (mFlags & (nsMsgFolderFlags::SpecialUse |
+ nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::Newsgroup |
+ nsMsgFolderFlags::ImapOtherUser) &&
+ !(mFlags & nsMsgFolderFlags::Inbox)))
+ filterForJunk = false;
+
+ spamSettings->GetLevel(&spamLevel);
+ if (!spamLevel)
+ filterForJunk = false;
+
+ /*
+ * We'll use inherited folder properties for the junk trait to override the
+ * standard server-based activation of junk processing. This provides a
+ * hook for extensions to customize the application of junk filtering.
+ * Set inherited property "dobayes.mailnews@mozilla.org#junk" to "true"
+ * to force junk processing, and "false" to skip junk processing.
+ */
+
+ nsAutoCString junkEnableOverride;
+ GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk", junkEnableOverride);
+ if (junkEnableOverride.EqualsLiteral("true"))
+ filterForJunk = true;
+ else if (junkEnableOverride.EqualsLiteral("false"))
+ filterForJunk = false;
+
+ bool userHasClassified = false;
+ // if the user has not classified any messages yet, then we shouldn't bother
+ // running the junk mail controls. This creates a better first use experience.
+ // See Bug #250084.
+ junkMailPlugin->GetUserHasClassified(&userHasClassified);
+ if (!userHasClassified)
+ filterForJunk = false;
+
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ rv = GetMsgDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if trait processing needed
+
+ nsCOMPtr<nsIMsgTraitService> traitService(
+ do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count = 0, *proIndices, *antiIndices;
+ rv = traitService->GetEnabledIndices(&count, &proIndices, &antiIndices);
+ bool filterForOther = false;
+ if (NS_SUCCEEDED(rv)) // We just skip this on failure, since it is rarely used
+ {
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // The trait service determines which traits are globally enabled or
+ // disabled. If a trait is enabled, it can still be made inactive
+ // on a particular folder using an inherited property. To do that,
+ // set "dobayes." + trait proID as an inherited folder property with
+ // the string value "false"
+ //
+ // If any non-junk traits are active on the folder, then the bayes
+ // processing will calculate probabilities for all enabled traits.
+
+ if (proIndices[i] != nsIJunkMailPlugin::JUNK_TRAIT)
+ {
+ filterForOther = true;
+ nsAutoCString traitId;
+ nsAutoCString property("dobayes.");
+ traitService->GetId(proIndices[i], traitId);
+ property.Append(traitId);
+ nsAutoCString isEnabledOnFolder;
+ GetInheritedStringProperty(property.get(), isEnabledOnFolder);
+ if (isEnabledOnFolder.EqualsLiteral("false"))
+ filterForOther = false;
+ // We might have to allow a "true" override in the future, but
+ // for now there is no way for that to affect the processing
+ break;
+ }
+ }
+ NS_Free(proIndices);
+ NS_Free(antiIndices);
+ }
+
+ // Do we need to apply message filters?
+ bool filterPostPlugin = false; // Do we have a post-analysis filter?
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ GetFilterList(aMsgWindow, getter_AddRefs(filterList));
+ if (filterList)
+ {
+ uint32_t filterCount = 0;
+ filterList->GetFilterCount(&filterCount);
+ for (uint32_t index = 0; index < filterCount && !filterPostPlugin; ++index)
+ {
+ nsCOMPtr<nsIMsgFilter> filter;
+ filterList->GetFilterAt(index, getter_AddRefs(filter));
+ if (!filter)
+ continue;
+ nsMsgFilterTypeType filterType;
+ filter->GetFilterType(&filterType);
+ if (!(filterType & nsMsgFilterType::PostPlugin))
+ continue;
+ bool enabled = false;
+ filter->GetEnabled(&enabled);
+ if (!enabled)
+ continue;
+ filterPostPlugin = true;
+ }
+ }
+
+ // If there is nothing to do, leave now but let NotifyHdrsNotBeingClassified
+ // generate the msgsClassified notification for all newly added messages as
+ // tracked by the NotReportedClassified processing flag.
+ if (!filterForOther && !filterForJunk && !filterPostPlugin)
+ {
+ NotifyHdrsNotBeingClassified();
+ return NS_OK;
+ }
+
+ // get the list of new messages
+ //
+ uint32_t numNewKeys;
+ nsMsgKey *newKeys;
+ rv = database->GetNewList(&numNewKeys, &newKeys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsMsgKey> newMessageKeys;
+ // Start from m_saveNewMsgs (and clear its current state). m_saveNewMsgs is
+ // where we stash the list of new messages when we are told to clear the list
+ // of new messages by the UI (which purges the list from the nsMsgDatabase).
+ newMessageKeys.SwapElements(m_saveNewMsgs);
+ if (numNewKeys)
+ newMessageKeys.AppendElements(newKeys, numNewKeys);
+
+ NS_Free(newKeys);
+
+ // build up list of keys to classify
+ nsTArray<nsMsgKey> classifyMsgKeys;
+ nsCString uri;
+
+ uint32_t numNewMessages = newMessageKeys.Length();
+ for (uint32_t i = 0 ; i < numNewMessages ; ++i)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsMsgKey msgKey = newMessageKeys[i];
+ rv = database->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (!NS_SUCCEEDED(rv))
+ continue;
+ // per-message junk tests.
+ bool filterMessageForJunk = false;
+ while (filterForJunk) // we'll break from this at the end
+ {
+ nsCString junkScore;
+ msgHdr->GetStringProperty("junkscore", getter_Copies(junkScore));
+ if (!junkScore.IsEmpty()) // ignore already scored messages.
+ break;
+
+ bool whiteListMessage = false;
+ spamSettings->CheckWhiteList(msgHdr, &whiteListMessage);
+ if (whiteListMessage)
+ {
+ // mark this msg as non-junk, because we whitelisted it.
+
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ database->SetStringProperty(msgKey, "junkscore", msgJunkScore.get());
+ database->SetStringProperty(msgKey, "junkscoreorigin", "whitelist");
+ break; // skip this msg since it's in the white list
+ }
+ filterMessageForJunk = true;
+
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyJunk);
+ // Since we are junk processing, we want to defer the msgsClassified
+ // notification until the junk classification has occurred. The event
+ // is sufficiently reliable that we know this will be handled in
+ // OnMessageClassified at the end of the batch. We clear the
+ // NotReportedClassified flag since we know the message is in good hands.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::NotReportedClassified);
+ break;
+ }
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ bool filterMessageForOther = false;
+ // trait processing
+ if (!(processingFlags & nsMsgProcessingFlags::TraitsDone))
+ {
+ // don't do trait processing on this message again
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::TraitsDone);
+ if (filterForOther)
+ {
+ filterMessageForOther = true;
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyTraits);
+ }
+ }
+
+ if (filterMessageForJunk || filterMessageForOther)
+ classifyMsgKeys.AppendElement(newMessageKeys[i]);
+
+ // Set messages to filter post-bayes.
+ // Have we already filtered this message?
+ if (!(processingFlags & nsMsgProcessingFlags::FiltersDone))
+ {
+ if (filterPostPlugin)
+ {
+ // Don't do filters on this message again.
+ // (Only set this if we are actually filtering since this is
+ // tantamount to a memory leak.)
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::FiltersDone);
+ // Lazily create the array.
+ if (!mPostBayesMessagesToFilter)
+ mPostBayesMessagesToFilter = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ mPostBayesMessagesToFilter->AppendElement(msgHdr, false);
+ }
+ }
+ }
+
+ NotifyHdrsNotBeingClassified();
+ // If there weren't any new messages, just return.
+ if (newMessageKeys.IsEmpty())
+ return NS_OK;
+
+ // If we do not need to do any work, leave.
+ // (We needed to get the list of new messages so we could get their headers so
+ // we can send notifications about them here.)
+
+ if (!classifyMsgKeys.IsEmpty())
+ {
+ // Remember what classifications are the source of this decision for when
+ // we perform the notification in OnMessageClassified at the conclusion of
+ // classification.
+ mBayesJunkClassifying = filterForJunk;
+ mBayesTraitClassifying = filterForOther;
+
+ uint32_t numMessagesToClassify = classifyMsgKeys.Length();
+ char ** messageURIs = (char **) PR_MALLOC(sizeof(const char *) * numMessagesToClassify);
+ if (!messageURIs)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t msgIndex = 0; msgIndex < numMessagesToClassify ; ++msgIndex )
+ {
+ nsCString tmpStr;
+ rv = GenerateMessageURI(classifyMsgKeys[msgIndex], tmpStr);
+ messageURIs[msgIndex] = ToNewCString(tmpStr);
+ if (NS_FAILED(rv))
+ NS_WARNING("nsMsgDBFolder::CallFilterPlugins(): could not"
+ " generate URI for message");
+ }
+ // filterMsgs
+ *aFiltersRun = true;
+ rv = SpamFilterClassifyMessages((const char **) messageURIs, numMessagesToClassify, aMsgWindow, junkMailPlugin);
+ for ( uint32_t freeIndex=0 ; freeIndex < numMessagesToClassify ; ++freeIndex )
+ PR_Free(messageURIs[freeIndex]);
+ PR_Free(messageURIs);
+ }
+ else if (filterPostPlugin)
+ {
+ // Nothing to classify, so need to end batch ourselves. We do this so that
+ // post analysis filters will run consistently on a folder, even if
+ // disabled junk processing, which could be dynamic through whitelisting,
+ // makes the bayes analysis unnecessary.
+ OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0);
+ }
+
+ return rv;
+}
+
+/**
+ * Adds the messages in the NotReportedClassified mProcessing set to the
+ * (possibly empty) array of msgHdrsNotBeingClassified, and send the
+ * nsIMsgFolderNotificationService notification.
+ */
+nsresult nsMsgDBFolder::NotifyHdrsNotBeingClassified()
+{
+ nsCOMPtr<nsIMutableArray> msgHdrsNotBeingClassified;
+
+ if (mProcessingFlag[5].keys)
+ {
+ nsTArray<nsMsgKey> keys;
+ mProcessingFlag[5].keys->ToMsgKeyArray(keys);
+ if (keys.Length())
+ {
+ msgHdrsNotBeingClassified = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (!msgHdrsNotBeingClassified)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ MsgGetHeadersFromKeys(mDatabase, keys, msgHdrsNotBeingClassified);
+
+ // Since we know we've handled all the NotReportedClassified messages,
+ // we clear the set by deleting and recreating it.
+ delete mProcessingFlag[5].keys;
+ mProcessingFlag[5].keys = nsMsgKeySetU::Create();
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsClassified(msgHdrsNotBeingClassified,
+ // no classification is being performed
+ false, false);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetLastMessageLoaded(nsMsgKey *aMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+ *aMsgKey = mLastMessageLoaded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetLastMessageLoaded(nsMsgKey aMsgKey)
+{
+ mLastMessageLoaded = aMsgKey;
+ return NS_OK;
+}
+
+// Returns true if: a) there is no need to prompt or b) the user is already
+// logged in or c) the user logged in successfully.
+bool nsMsgDBFolder::PromptForMasterPasswordIfNecessary()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool userNeedsToAuthenticate = false;
+ // if we're PasswordProtectLocalCache, then we need to find out if the server
+ // is authenticated.
+ (void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
+ if (!userNeedsToAuthenticate)
+ return true;
+
+ // Do we have a master password?
+ nsCOMPtr<nsIPK11TokenDB> tokenDB =
+ do_GetService(NS_PK11TOKENDB_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIPK11Token> token;
+ rv = tokenDB->GetInternalKeyToken(getter_AddRefs(token));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool result;
+ rv = token->CheckPassword(EmptyCString(), &result);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (result)
+ {
+ // We don't have a master password, so this function isn't supported,
+ // therefore just tell account manager we've authenticated and return true.
+ accountManager->SetUserNeedsToAuthenticate(false);
+ return true;
+ }
+
+ // We have a master password, so try and login to the slot.
+ rv = token->Login(false);
+ if (NS_FAILED(rv))
+ // Login failed, so we didn't get a password (e.g. prompt cancelled).
+ return false;
+
+ // Double-check that we are now logged in
+ rv = token->IsLoggedIn(&result);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ accountManager->SetUserNeedsToAuthenticate(!result);
+ return result;
+}
+
+// this gets called after the last junk mail classification has run.
+nsresult nsMsgDBFolder::PerformBiffNotifications(void)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t numBiffMsgs = 0;
+ nsCOMPtr<nsIMsgFolder> root;
+ rv = GetRootFolder(getter_AddRefs(root));
+ root->GetNumNewMessages(true, &numBiffMsgs);
+ if (numBiffMsgs > 0)
+ {
+ server->SetPerformingBiff(true);
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ server->SetPerformingBiff(false);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::initializeStrings()
+{
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bundle->GetStringFromName(u"inboxFolderName",
+ &kLocalizedInboxName);
+ bundle->GetStringFromName(u"trashFolderName",
+ &kLocalizedTrashName);
+ bundle->GetStringFromName(u"sentFolderName",
+ &kLocalizedSentName);
+ bundle->GetStringFromName(u"draftsFolderName",
+ &kLocalizedDraftsName);
+ bundle->GetStringFromName(u"templatesFolderName",
+ &kLocalizedTemplatesName);
+ bundle->GetStringFromName(u"junkFolderName",
+ &kLocalizedJunkName);
+ bundle->GetStringFromName(u"outboxFolderName",
+ &kLocalizedUnsentName);
+ bundle->GetStringFromName(u"archivesFolderName",
+ &kLocalizedArchivesName);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bundle->GetStringFromName(u"brandShortName",
+ &kLocalizedBrandShortName);
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::createCollationKeyGenerator()
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocale> locale;
+ rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = factory->CreateCollation(locale, &gCollationKeyGenerator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::Init(const char* aURI)
+{
+ // for now, just initialize everything during Init()
+ nsresult rv;
+ rv = nsRDFResource::Init(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return CreateBaseMessageURI(nsDependentCString(aURI));
+}
+
+nsresult nsMsgDBFolder::CreateBaseMessageURI(const nsACString& aURI)
+{
+ // Each folder needs to implement this.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetURI(nsACString& name)
+{
+ return nsRDFResource::GetValueUTF8(name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+#if 0
+typedef bool
+(*nsArrayFilter)(nsISupports* element, void* data);
+#endif
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::FindSubFolder(const nsACString& aEscapedSubFolderName, nsIMsgFolder **aFolder)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXX use necko here
+ nsAutoCString uri;
+ uri.Append(mURI);
+ uri.Append('/');
+ uri.Append(aEscapedSubFolderName);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ folder.swap(*aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetHasSubFolders(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mSubFolders.Count() > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetNumSubFolders(uint32_t *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSubFolders.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddFolderListener(nsIFolderListener * listener)
+{
+ return mListeners.AppendElement(listener) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveFolderListener(nsIFolderListener * listener)
+{
+ mListeners.RemoveElement(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetParent(nsIMsgFolder *aParent)
+{
+ mParent = do_GetWeakReference(aParent);
+ if (aParent)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder = do_QueryInterface(aParent, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ // servers do not have parents, so we must not be a server
+ mIsServer = false;
+ mIsServerIsValid = true;
+
+ // also set the server itself while we're here.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = parentMsgFolder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ mServer = do_GetWeakReference(server);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetParent(nsIMsgFolder **aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+ nsCOMPtr<nsIMsgFolder> parent = do_QueryReferent(mParent);
+ parent.swap(*aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMessages(nsISimpleEnumerator **result)
+{
+ // XXX should this return an empty enumeration?
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::UpdateFolder(nsIMsgWindow *)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgDBFolder::GetFolderURL(nsACString& url)
+{
+ url.Assign(EmptyCString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetServer(nsIMsgIncomingServer ** aServer)
+{
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsresult rv;
+ // short circut the server if we have it.
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ if (NS_FAILED(rv))
+ {
+ // try again after parsing the URI
+ rv = parseURI(true);
+ server = do_QueryReferent(mServer);
+ }
+ server.swap(*aServer);
+ return *aServer ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgDBFolder::parseURI(bool needServer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIURL> url;
+
+ url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = url->SetSpec(mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // empty path tells us it's a server.
+ if (!mIsServerIsValid)
+ {
+ nsAutoCString path;
+ rv = url->GetPath(path);
+ if (NS_SUCCEEDED(rv))
+ mIsServer = path.EqualsLiteral("/");
+ mIsServerIsValid = true;
+ }
+
+ // grab the name off the leaf of the server
+ if (mName.IsEmpty())
+ {
+ // mName:
+ // the name is the trailing directory in the path
+ nsAutoCString fileName;
+ nsAutoCString escapedFileName;
+ url->GetFileName(escapedFileName);
+ if (!escapedFileName.IsEmpty())
+ {
+ // XXX conversion to unicode here? is fileName in UTF8?
+ // yes, let's say it is in utf8
+ MsgUnescapeString(escapedFileName, 0, fileName);
+ NS_ASSERTION(MsgIsUTF8(fileName), "fileName is not in UTF-8");
+ CopyUTF8toUTF16(fileName, mName);
+ }
+ }
+
+ // grab the server by parsing the URI and looking it up
+ // in the account manager...
+ // But avoid this extra work by first asking the parent, if any
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ if (NS_FAILED(rv))
+ {
+ // first try asking the parent instead of the URI
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ GetParent(getter_AddRefs(parentMsgFolder));
+
+ if (parentMsgFolder)
+ rv = parentMsgFolder->GetServer(getter_AddRefs(server));
+
+ // no parent. do the extra work of asking
+ if (!server && needServer)
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverType;
+ GetIncomingServerType(serverType);
+ if (serverType.IsEmpty())
+ {
+ NS_WARNING("can't determine folder's server type");
+ return NS_ERROR_FAILURE;
+ }
+
+ url->SetScheme(serverType);
+ rv = accountManager->FindServerByURI(url, false,
+ getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mServer = do_GetWeakReference(server);
+ } /* !mServer */
+
+ // now try to find the local path for this folder
+ if (server)
+ {
+ nsAutoCString newPath;
+ nsAutoCString escapedUrlPath;
+ nsAutoCString urlPath;
+ url->GetFilePath(escapedUrlPath);
+ if (!escapedUrlPath.IsEmpty())
+ {
+ MsgUnescapeString(escapedUrlPath, 0, urlPath);
+
+ // transform the filepath from the URI, such as
+ // "/folder1/folder2/foldern"
+ // to
+ // "folder1.sbd/folder2.sbd/foldern"
+ // (remove leading / and add .sbd to first n-1 folders)
+ // to be appended onto the server's path
+ bool isNewsFolder = false;
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(url->GetScheme(scheme)))
+ {
+ isNewsFolder = scheme.EqualsLiteral("news") ||
+ scheme.EqualsLiteral("snews") ||
+ scheme.EqualsLiteral("nntp");
+ }
+ NS_MsgCreatePathStringFromFolderURI(urlPath.get(), newPath, scheme,
+ isNewsFolder);
+ }
+
+ // now append munged path onto server path
+ nsCOMPtr<nsIFile> serverPath;
+ rv = server->GetLocalPath(getter_AddRefs(serverPath));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mPath && serverPath)
+ {
+ if (!newPath.IsEmpty())
+ {
+ // I hope this is temporary - Ultimately,
+ // NS_MsgCreatePathStringFromFolderURI will need to be fixed.
+#if defined(XP_WIN)
+ MsgReplaceChar(newPath, '/', '\\');
+#endif
+ rv = serverPath->AppendRelativeNativePath(newPath);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to append to the serverPath");
+ if (NS_FAILED(rv))
+ {
+ mPath = nullptr;
+ return rv;
+ }
+ }
+ mPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPath->InitWithFile(serverPath);
+ }
+ // URI is completely parsed when we've attempted to get the server
+ mHaveParsedURI=true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetIsServer(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // make sure we've parsed the URI
+ if (!mIsServerIsValid)
+ {
+ nsresult rv = parseURI();
+ if (NS_FAILED(rv) || !mIsServerIsValid)
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = mIsServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetNoSelect(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetImapShared(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ return GetFlag(nsMsgFolderFlags::PersonalShared, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanSubscribe(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // by default, you can't subscribe.
+ // if otherwise, override it.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanFileMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ //varada - checking folder flag to see if it is the "Unsent Messages"
+ //and if so return FALSE
+ if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual))
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ // by default, you can't file messages into servers, only to folders
+ // if otherwise, override it.
+ *aResult = !isServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanDeleteMessages(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanCreateSubfolders(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ //Checking folder flag to see if it is the "Unsent Messages"
+ //or a virtual folder, and if so return FALSE
+ if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual))
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // by default, you can create subfolders on server and folders
+ // if otherwise, override it.
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanRename(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+ // by default, you can't rename servers, only folders
+ // if otherwise, override it.
+ //
+ // check if the folder is a special folder
+ // (Trash, Drafts, Unsent Messages, Inbox, Sent, Templates, Junk, Archives)
+ // if it is, don't allow the user to rename it
+ // (which includes dnd moving it with in the same server)
+ //
+ // this errors on the side of caution. we'll return false a lot
+ // more often if we use flags,
+ // instead of checking if the folder really is being used as a
+ // special folder by looking at the "copies and folders" prefs on the
+ // identities.
+ *aResult = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanCompact(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv,rv);
+ // servers cannot be compacted --> 4.x
+ // virtual search folders cannot be compacted
+ *aResult = !isServer && !(mFlags & nsMsgFolderFlags::Virtual);
+ // Check if the store supports compaction
+ if (*aResult)
+ {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ if (msgStore)
+ msgStore->GetSupportsCompaction(aResult);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetPrettyName(nsAString& name)
+{
+ return GetName(name);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetPrettyName(const nsAString& name)
+{
+ nsresult rv;
+
+ //Set pretty name only if special flag is set and if it the default folder name
+ if (mFlags & nsMsgFolderFlags::Inbox && name.LowerCaseEqualsLiteral("inbox"))
+ rv = SetName(nsDependentString(kLocalizedInboxName));
+ else if (mFlags & nsMsgFolderFlags::SentMail && name.LowerCaseEqualsLiteral("sent"))
+ rv = SetName(nsDependentString(kLocalizedSentName));
+ else if (mFlags & nsMsgFolderFlags::Drafts && name.LowerCaseEqualsLiteral("drafts"))
+ rv = SetName(nsDependentString(kLocalizedDraftsName));
+ else if (mFlags & nsMsgFolderFlags::Templates && name.LowerCaseEqualsLiteral("templates"))
+ rv = SetName(nsDependentString(kLocalizedTemplatesName));
+ else if (mFlags & nsMsgFolderFlags::Trash && name.LowerCaseEqualsLiteral("trash"))
+ rv = SetName(nsDependentString(kLocalizedTrashName));
+ else if (mFlags & nsMsgFolderFlags::Queue && name.LowerCaseEqualsLiteral("unsent messages"))
+ rv = SetName(nsDependentString(kLocalizedUnsentName));
+ else if (mFlags & nsMsgFolderFlags::Junk && name.LowerCaseEqualsLiteral("junk"))
+ rv = SetName(nsDependentString(kLocalizedJunkName));
+ else if (mFlags & nsMsgFolderFlags::Archive && name.LowerCaseEqualsLiteral("archives"))
+ rv = SetName(nsDependentString(kLocalizedArchivesName));
+ else
+ rv = SetName(name);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetName(nsAString& name)
+{
+ nsresult rv;
+ if (!mHaveParsedURI && mName.IsEmpty())
+ {
+ rv = parseURI();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // if it's a server, just forward the call
+ if (mIsServer)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ return server->GetPrettyName(name);
+ }
+
+ name = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetName(const nsAString& name)
+{
+ // override the URI-generated name
+ if (!mName.Equals(name))
+ {
+ mName = name;
+ // old/new value doesn't matter here
+ NotifyUnicharPropertyChanged(kNameAtom, name, name);
+ }
+ return NS_OK;
+}
+
+//For default, just return name
+NS_IMETHODIMP nsMsgDBFolder::GetAbbreviatedName(nsAString& aAbbreviatedName)
+{
+ return GetName(aAbbreviatedName);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetChildNamed(const nsAString& aName, nsIMsgFolder **aChild)
+{
+ NS_ENSURE_ARG_POINTER(aChild);
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+ *aChild = nullptr;
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsString folderName;
+ nsresult rv = mSubFolders[i]->GetName(folderName);
+ // case-insensitive compare is probably LCD across OS filesystems
+ if (NS_SUCCEEDED(rv) &&
+ folderName.Equals(aName, nsCaseInsensitiveStringComparator()))
+ {
+ NS_ADDREF(*aChild = mSubFolders[i]);
+ return NS_OK;
+ }
+ }
+ // don't return NS_OK if we didn't find the folder
+ // see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c15
+ // and http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetChildWithURI(const nsACString& uri, bool deep, bool caseInsensitive, nsIMsgFolder ** child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ // will return nullptr if we can't find it
+ *child = nullptr;
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMore;
+ while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIRDFResource> folderResource(do_QueryInterface(item));
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item));
+ if (folderResource && folder)
+ {
+ const char *folderURI;
+ rv = folderResource->GetValueConst(&folderURI);
+ if (NS_FAILED(rv)) return rv;
+ bool equal = folderURI && (caseInsensitive ? uri.Equals(folderURI, nsCaseInsensitiveCStringComparator())
+ : uri.Equals(folderURI));
+ if (equal)
+ {
+ *child = folder;
+ NS_ADDREF(*child);
+ return NS_OK;
+ }
+ if (deep)
+ {
+ rv = folder->GetChildWithURI(uri, deep, caseInsensitive, child);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (*child)
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetPrettiestName(nsAString& name)
+{
+ if (NS_SUCCEEDED(GetPrettyName(name)))
+ return NS_OK;
+ return GetName(name);
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetShowDeletedMessages(bool *showDeletedMessages)
+{
+ NS_ENSURE_ARG_POINTER(showDeletedMessages);
+ *showDeletedMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Delete()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DeleteSubFolders(nsIArray *folders,
+ nsIMsgWindow *msgWindow)
+{
+ uint32_t count;
+ nsresult rv = folders->GetLength(&count);
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(folders, i, &rv));
+ if (folder)
+ PropagateDelete(folder, true, msgWindow);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CreateStorageIfMissing(nsIUrlListener* /* urlListener */)
+{
+ NS_ASSERTION(false, "needs to be overridden");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::PropagateDelete(nsIMsgFolder *folder, bool deleteStorage, nsIMsgWindow *msgWindow)
+{
+ // first, find the folder we're looking to delete
+ nsresult rv = NS_OK;
+
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]);
+ if (folder == child.get())
+ {
+ // Remove self as parent
+ child->SetParent(nullptr);
+ // maybe delete disk storage for it, and its subfolders
+ rv = child->RecursiveDelete(deleteStorage, msgWindow);
+ if (NS_SUCCEEDED(rv))
+ {
+ // Remove from list of subfolders.
+ mSubFolders.RemoveObjectAt(i);
+ NotifyItemRemoved(child);
+ break;
+ }
+ else // setting parent back if we failed
+ child->SetParent(this);
+ }
+ else
+ rv = child->PropagateDelete(folder, deleteStorage, msgWindow);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RecursiveDelete(bool deleteStorage, nsIMsgWindow *msgWindow)
+{
+ // If deleteStorage is true, recursively deletes disk storage for this folder
+ // and all its subfolders.
+ // Regardless of deleteStorage, always unlinks them from the children lists and
+ // frees memory for the subfolders but NOT for _this_
+
+ nsresult status = NS_OK;
+ nsCOMPtr <nsIFile> dbPath;
+
+ // first remove the deleted folder from the folder cache;
+ nsresult result = GetFolderCacheKey(getter_AddRefs(dbPath));
+
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
+ if(NS_SUCCEEDED(result))
+ {
+ nsCOMPtr <nsIMsgFolderCache> folderCache;
+ result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(result) && folderCache)
+ {
+ nsCString persistentPath;
+ result = dbPath->GetPersistentDescriptor(persistentPath);
+ if (NS_SUCCEEDED(result))
+ folderCache->RemoveElement(persistentPath);
+ }
+ }
+
+ int32_t count = mSubFolders.Count();
+ while (count > 0)
+ {
+ nsIMsgFolder *child = mSubFolders[0];
+
+ child->SetParent(nullptr);
+ status = child->RecursiveDelete(deleteStorage, msgWindow); // recur
+ if (NS_SUCCEEDED(status))
+ // unlink it from this child's list
+ mSubFolders.RemoveObjectAt(0);
+ else
+ {
+ // setting parent back if we failed for some reason
+ child->SetParent(this);
+ break;
+ }
+
+ count--;
+ }
+
+ // now delete the disk storage for _this_
+ if (deleteStorage && NS_SUCCEEDED(status))
+ {
+ // All delete commands use deleteStorage = true, and local moves use false.
+ // IMAP moves use true, leaving this here in the hope that bug 439108
+ // works out.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderDeleted(this);
+ status = Delete();
+ }
+ return status;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CreateSubfolder(const nsAString& folderName, nsIMsgWindow *msgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddSubfolder(const nsAString& name,
+ nsIMsgFolder** child)
+{
+ NS_ENSURE_ARG_POINTER(child);
+
+ int32_t flags = 0;
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+
+ // URI should use UTF-8
+ // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(name, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // fix for #192780
+ // if this is the root folder
+ // make sure the the special folders
+ // have the right uri.
+ // on disk, host\INBOX should be a folder with the uri mailbox://user@host/Inbox"
+ // as mailbox://user@host/Inbox != mailbox://user@host/INBOX
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder && (rootFolder.get() == (nsIMsgFolder *)this))
+ {
+ if (MsgLowerCaseEqualsLiteral(escapedName, "inbox"))
+ uri += "Inbox";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "unsent%20messages"))
+ uri += "Unsent%20Messages";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "drafts"))
+ uri += "Drafts";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "trash"))
+ uri += "Trash";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "sent"))
+ uri += "Sent";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "templates"))
+ uri +="Templates";
+ else if (MsgLowerCaseEqualsLiteral(escapedName, "archives"))
+ uri += "Archives";
+ else
+ uri += escapedName.get();
+ }
+ else
+ uri += escapedName.get();
+
+ nsCOMPtr <nsIMsgFolder> msgFolder;
+ rv = GetChildWithURI(uri, false/*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder)
+ return NS_MSG_FOLDER_EXISTS;
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ folder->GetFlags((uint32_t *)&flags);
+ flags |= nsMsgFolderFlags::Mail;
+ folder->SetParent(this);
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+
+ //Only set these if these are top level children.
+ if(NS_SUCCEEDED(rv) && isServer)
+ {
+ if(name.LowerCaseEqualsLiteral("inbox"))
+ {
+ flags |= nsMsgFolderFlags::Inbox;
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_Unknown);
+ }
+ else if (name.LowerCaseEqualsLiteral("trash"))
+ flags |= nsMsgFolderFlags::Trash;
+ else if (name.LowerCaseEqualsLiteral("unsent messages") ||
+ name.LowerCaseEqualsLiteral("outbox"))
+ flags |= nsMsgFolderFlags::Queue;
+ }
+
+ folder->SetFlags(flags);
+
+ if (folder)
+ mSubFolders.AppendObject(folder);
+
+ folder.swap(*child);
+ // at this point we must be ok and we don't want to return failure in case
+ // GetIsServer failed.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow, bool aCompactOfflineAlso)
+{
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMsgDBFolder::CheckIfFolderExists(const nsAString& newFolderName, nsIMsgFolder *parentFolder, nsIMsgWindow *msgWindow)
+{
+ NS_ENSURE_ARG_POINTER(parentFolder);
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = subFolders->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ break;
+
+ nsString folderName;
+
+ msgFolder->GetName(folderName);
+ if (folderName.Equals(newFolderName, nsCaseInsensitiveStringComparator()))
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ }
+ return NS_OK;
+}
+
+bool
+nsMsgDBFolder::ConfirmAutoFolderRename(nsIMsgWindow *msgWindow,
+ const nsString& aOldName,
+ const nsString& aNewName)
+{
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsString folderName;
+ GetName(folderName);
+ const char16_t *formatStrings[] =
+ {
+ aOldName.get(),
+ folderName.get(),
+ aNewName.get()
+ };
+
+ nsString confirmString;
+ rv = bundle->FormatStringFromName(u"confirmDuplicateFolderRename",
+ formatStrings, 3, getter_Copies(confirmString));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ bool confirmed = false;
+ rv = ThrowConfirmationPrompt(msgWindow, confirmString, &confirmed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ return confirmed;
+}
+
+nsresult
+nsMsgDBFolder::AddDirectorySeparator(nsIFile *path)
+{
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+/* Finds the directory associated with this folder. That is if the path is
+ c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't
+ currently exist then it will create it. Path is strictly an out parameter.
+ */
+nsresult nsMsgDBFolder::CreateDirectoryForFolder(nsIFile **resultFile)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIFile> path;
+ rv = GetFilePath(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ bool pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+
+ bool isServer;
+ GetIsServer(&isServer);
+
+ // Make sure this is REALLY the parent for subdirectories
+ if (pathIsDirectory && !isServer)
+ {
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ nsAutoString ext;
+ int32_t idx = leafName.RFindChar('.');
+ if (idx != -1)
+ ext = Substring(leafName, idx);
+ if (!ext.EqualsLiteral(FOLDER_SUFFIX))
+ pathIsDirectory = false;
+ }
+
+ if(!pathIsDirectory)
+ {
+ //If the current path isn't a directory, add directory separator
+ //and test it out.
+ rv = AddDirectorySeparator(path);
+ if(NS_FAILED(rv))
+ return rv;
+
+ //If that doesn't exist, then we have to create this directory
+ pathIsDirectory = false;
+ path->IsDirectory(&pathIsDirectory);
+ if(!pathIsDirectory)
+ {
+ bool pathExists;
+ path->Exists(&pathExists);
+ //If for some reason there's a file with the directory separator
+ //then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ path.swap(*resultFile);
+ return rv;
+}
+
+/* Finds the backup directory associated with this folder, stored on the temp
+ drive. If that path doesn't currently exist then it will create it. Path is
+ strictly an out parameter.
+ */
+nsresult nsMsgDBFolder::CreateBackupDirectory(nsIFile **resultFile)
+{
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+ getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = path->Append(NS_LITERAL_STRING("MozillaMailnews"));
+ bool pathIsDirectory;
+ path->IsDirectory(&pathIsDirectory);
+
+ // If that doesn't exist, then we have to create this directory
+ if (!pathIsDirectory)
+ {
+ bool pathExists;
+ path->Exists(&pathExists);
+ // If for some reason there's a file with the directory separator
+ // then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY :
+ path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ if (NS_SUCCEEDED(rv))
+ path.swap(*resultFile);
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetBackupSummaryFile(nsIFile **aBackupFile, const nsACString& newName)
+{
+ nsCOMPtr<nsIFile> backupDir;
+ nsresult rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!newName.IsEmpty())
+ {
+ rv = backupDBDummyFolder->AppendNative(newName);
+ }
+ else // if newName is null, use the folder name
+ {
+ nsCOMPtr<nsIFile> folderPath;
+ rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString folderName;
+ rv = folderPath->GetNativeLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->AppendNative(folderName);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ backupDBFile.swap(*aBackupFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Rename(const nsAString& aNewName, nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsCOMPtr<nsIAtom> folderRenameAtom;
+ nsresult rv = GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder)
+ return NS_ERROR_FAILURE;
+ nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder);
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dirFile;
+ int32_t count = mSubFolders.Count();
+
+ if (count > 0)
+ rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
+
+ nsAutoString newDiskName(aNewName);
+ NS_MsgHashIfNecessary(newDiskName);
+
+ if (mName.Equals(aNewName, nsCaseInsensitiveStringComparator()))
+ {
+ rv = ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ else
+ {
+ nsCOMPtr <nsIFile> parentPathFile;
+ parentFolder->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory)
+ AddDirectorySeparator(parentPathFile);
+
+ rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ ForceDBClosed();
+
+ // Save of dir name before appending .msf
+ nsAutoString newNameDirStr(newDiskName);
+
+ if (! (mFlags & nsMsgFolderFlags::Virtual))
+ rv = oldPathFile->MoveTo(nullptr, newDiskName);
+ if (NS_SUCCEEDED(rv))
+ {
+ newDiskName.AppendLiteral(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, newDiskName);
+ }
+ else
+ {
+ ThrowAlertMsg("folderRenameFailed", msgWindow);
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv) && count > 0)
+ {
+ // rename "*.sbd" directory
+ newNameDirStr.AppendLiteral(".sbd");
+ dirFile->MoveTo(nullptr, newNameDirStr);
+ }
+
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ if (parentSupport)
+ {
+ rv = parentFolder->AddSubfolder(aNewName, getter_AddRefs(newFolder));
+ if (newFolder)
+ {
+ newFolder->SetPrettyName(EmptyString());
+ newFolder->SetPrettyName(aNewName);
+ newFolder->SetFlags(mFlags);
+ bool changed = false;
+ MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &changed);
+ if (changed)
+ AlertFilterChanged(msgWindow);
+
+ if (count > 0)
+ newFolder->RenameSubFolders(msgWindow, this);
+
+ if (parentFolder)
+ {
+ SetParent(nullptr);
+ parentFolder->PropagateDelete(this, false, msgWindow);
+ parentFolder->NotifyItemAdded(newFolder);
+ }
+ folderRenameAtom = MsgGetAtom("RenameCompleted");
+ newFolder->NotifyFolderEvent(folderRenameAtom);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ContainsChildNamed(const nsAString& name, bool* containsChild)
+{
+ NS_ENSURE_ARG_POINTER(containsChild);
+ nsCOMPtr<nsIMsgFolder> child;
+ GetChildNamed(name, getter_AddRefs(child));
+ *containsChild = child != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsAncestorOf(nsIMsgFolder *child, bool *isAncestor)
+{
+ NS_ENSURE_ARG_POINTER(isAncestor);
+ nsresult rv = NS_OK;
+
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ if (folder.get() == child)
+ *isAncestor = true;
+ else
+ folder->IsAncestorOf(child, isAncestor);
+
+ if (*isAncestor)
+ return NS_OK;
+ }
+ *isAncestor = false;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GenerateUniqueSubfolderName(const nsAString& prefix,
+ nsIMsgFolder *otherFolder,
+ nsAString& name)
+{
+ /* only try 256 times */
+ for (int count = 0; count < 256; count++)
+ {
+ nsAutoString uniqueName;
+ uniqueName.Assign(prefix);
+ uniqueName.AppendInt(count);
+ bool containsChild;
+ bool otherContainsChild = false;
+ ContainsChildNamed(uniqueName, &containsChild);
+ if (otherFolder)
+ otherFolder->ContainsChildNamed(uniqueName, &otherContainsChild);
+
+ if (!containsChild && !otherContainsChild)
+ {
+ name = uniqueName;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::UpdateSummaryTotals(bool force)
+{
+ if (!mNotifyCountChanges)
+ return NS_OK;
+
+ int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+ //We need to read this info from the database
+ nsresult rv = ReadDBFolderInfo(force);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+
+ //Need to notify listeners that total count changed.
+ if(oldTotalMessages != newTotalMessages)
+ NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages);
+
+ if(oldUnreadMessages != newUnreadMessages)
+ NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, newUnreadMessages);
+
+ FlushToFolderCache();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SummaryChanged()
+{
+ UpdateSummaryTotals(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumUnread(bool deep, int32_t *numUnread)
+{
+ NS_ENSURE_ARG_POINTER(numUnread);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t total = isServer ? 0 : mNumUnreadMessages + mNumPendingUnreadMessages;
+
+ if (deep)
+ {
+ if (total < 0) // deep search never returns negative counts
+ total = 0;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ int32_t num;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags & nsMsgFolderFlags::Virtual))
+ {
+ folder->GetNumUnread(deep, &num);
+ total += num;
+ }
+ }
+ }
+ *numUnread = total;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetTotalMessages(bool deep, int32_t *totalMessages)
+{
+ NS_ENSURE_ARG_POINTER(totalMessages);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t total = isServer ? 0 : mNumTotalMessages + mNumPendingTotalMessages;
+
+ if (deep)
+ {
+ if (total < 0) // deep search never returns negative counts
+ total = 0;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ int32_t num;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags & nsMsgFolderFlags::Virtual))
+ {
+ folder->GetTotalMessages(deep, &num);
+ total += num;
+ }
+ }
+ }
+ *totalMessages = total;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumPendingUnread(int32_t *aPendingUnread)
+{
+ *aPendingUnread = mNumPendingUnreadMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumPendingTotalMessages(int32_t *aPendingTotal)
+{
+ *aPendingTotal = mNumPendingTotalMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingUnread(int32_t delta)
+{
+ if (delta)
+ {
+ int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ mNumPendingUnreadMessages += delta;
+ int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ NS_ASSERTION(newUnreadMessages >= 0, "shouldn't have negative unread message count");
+ if (newUnreadMessages >= 0)
+ {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ folderInfo->SetImapUnreadPendingMessages(mNumPendingUnreadMessages);
+ NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, newUnreadMessages);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingTotalMessages(int32_t delta)
+{
+ if (delta)
+ {
+ int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+ mNumPendingTotalMessages += delta;
+ int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ folderInfo->SetImapTotalPendingMessages(mNumPendingTotalMessages);
+ NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, newTotalMessages);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetFlag(uint32_t flag)
+{
+ // If calling this function causes us to open the db (i.e., it was not
+ // open before), we're going to close the db before returning.
+ bool dbWasOpen = mDatabase != nullptr;
+
+ ReadDBFolderInfo(false);
+ // OnFlagChange can be expensive, so don't call it if we don't need to
+ bool flagSet;
+ nsresult rv;
+
+ if (NS_FAILED(rv = GetFlag(flag, &flagSet)))
+ return rv;
+
+ if (!flagSet)
+ {
+ mFlags |= flag;
+ OnFlagChange(flag);
+ }
+ if (!dbWasOpen && mDatabase)
+ SetMsgDatabase(nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ClearFlag(uint32_t flag)
+{
+ // OnFlagChange can be expensive, so don't call it if we don't need to
+ bool flagSet;
+ nsresult rv;
+
+ if (NS_FAILED(rv = GetFlag(flag, &flagSet)))
+ return rv;
+
+ if (flagSet)
+ {
+ mFlags &= ~flag;
+ OnFlagChange (flag);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFlag(uint32_t flag, bool *_retval)
+{
+ *_retval = ((mFlags & flag) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ToggleFlag(uint32_t flag)
+{
+ mFlags ^= flag;
+ OnFlagChange (flag);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnFlagChange(uint32_t flag)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ {
+#ifdef DEBUG_bienvenu1
+ nsString name;
+ rv = GetName(name);
+ NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & nsMsgFolderFlags::Trash), "lost trash flag");
+#endif
+ folderInfo->SetFlags((int32_t) mFlags);
+ if (db)
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ if (mFlags & flag)
+ NotifyIntPropertyChanged(mFolderFlagAtom, mFlags & ~flag, mFlags);
+ else
+ NotifyIntPropertyChanged(mFolderFlagAtom, mFlags | flag, mFlags);
+
+ if (flag & nsMsgFolderFlags::Offline)
+ {
+ bool newValue = mFlags & nsMsgFolderFlags::Offline;
+ rv = NotifyBoolPropertyChanged(kSynchronizeAtom, !newValue, !!newValue);
+ }
+ else if (flag & nsMsgFolderFlags::Elided)
+ {
+ bool newValue = mFlags & nsMsgFolderFlags::Elided;
+ rv = NotifyBoolPropertyChanged(kOpenAtom, !!newValue, !newValue);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetFlags(uint32_t aFlags)
+{
+ if (mFlags != aFlags)
+ {
+ uint32_t changedFlags = aFlags ^ mFlags;
+ mFlags = aFlags;
+ OnFlagChange(changedFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFolderWithFlags(uint32_t aFlags, nsIMsgFolder** aResult)
+{
+ if ((mFlags & aFlags) == aFlags)
+ {
+ NS_ADDREF(*aResult = this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+
+ int32_t count = mSubFolders.Count();
+ *aResult = nullptr;
+ for (int32_t i = 0; !*aResult && i < count; ++i)
+ mSubFolders[i]->GetFolderWithFlags(aFlags, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFoldersWithFlags(uint32_t aFlags, nsIArray** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ListFoldersWithFlags(aFlags, array);
+ NS_ADDREF(*aResult = array);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ListFoldersWithFlags(uint32_t aFlags, nsIMutableArray* aFolders)
+{
+ NS_ENSURE_ARG_POINTER(aFolders);
+ if ((mFlags & aFlags) == aFlags)
+ aFolders->AppendElement(static_cast<nsRDFResource*>(this), false);
+
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; ++i)
+ mSubFolders[i]->ListFoldersWithFlags(aFlags, aFolders);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsSpecialFolder(uint32_t aFlags,
+ bool aCheckAncestors,
+ bool *aIsSpecial)
+{
+ NS_ENSURE_ARG_POINTER(aIsSpecial);
+
+ if ((mFlags & aFlags) == 0)
+ {
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ GetParent(getter_AddRefs(parentMsgFolder));
+
+ if (parentMsgFolder && aCheckAncestors)
+ parentMsgFolder->IsSpecialFolder(aFlags, aCheckAncestors, aIsSpecial);
+ else
+ *aIsSpecial = false;
+ }
+ else
+ {
+ // The user can set their INBOX to be their SENT folder.
+ // in that case, we want this folder to act like an INBOX,
+ // and not a SENT folder
+ *aIsSpecial = !((aFlags & nsMsgFolderFlags::SentMail) &&
+ (mFlags & nsMsgFolderFlags::Inbox));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDeletable(bool *deletable)
+{
+ NS_ENSURE_ARG_POINTER(deletable);
+ *deletable = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDisplayRecipients(bool *displayRecipients)
+{
+ *displayRecipients = false;
+ if (mFlags & nsMsgFolderFlags::SentMail && !(mFlags & nsMsgFolderFlags::Inbox))
+ *displayRecipients = true;
+ else if (mFlags & nsMsgFolderFlags::Queue)
+ *displayRecipients = true;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::AcquireSemaphore(nsISupports *semHolder)
+{
+ nsresult rv = NS_OK;
+ if (mSemaphoreHolder == NULL)
+ mSemaphoreHolder = semHolder; //Don't AddRef due to ownership issues.
+ else
+ rv = NS_MSG_FOLDER_BUSY;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ReleaseSemaphore(nsISupports *semHolder)
+{
+ if (!mSemaphoreHolder || mSemaphoreHolder == semHolder)
+ mSemaphoreHolder = NULL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::TestSemaphore(nsISupports *semHolder, bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (mSemaphoreHolder == semHolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetLocked(bool *isLocked)
+{
+ *isLocked = mSemaphoreHolder != NULL;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDBFolder::GetRelativePathName(nsACString& pathName)
+{
+ pathName.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSizeOnDisk(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+ *size = kSizeUnknown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetSizeOnDisk(int64_t aSizeOnDisk)
+{
+ NotifyIntPropertyChanged(kFolderSizeAtom, mFolderSize, aSizeOnDisk);
+ mFolderSize = aSizeOnDisk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetUsername(nsACString& userName)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetUsername(userName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHostname(nsACString& hostName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetHostName(hostName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNewMessages(nsIMsgWindow *, nsIUrlListener * /* aListener */)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetBiffState(uint32_t *aBiffState)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetBiffState(aBiffState);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetBiffState(uint32_t aBiffState)
+{
+ uint32_t oldBiffState = nsMsgBiffState_Unknown;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = server->GetBiffState(&oldBiffState);
+
+ if (oldBiffState != aBiffState)
+ {
+ // Get the server and notify it and not inbox.
+ if (!mIsServer)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetRootFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ return folder->SetBiffState(aBiffState);
+ }
+ if (server)
+ server->SetBiffState(aBiffState);
+
+ NotifyIntPropertyChanged(kBiffStateAtom, oldBiffState, aBiffState);
+ }
+ else if (aBiffState == oldBiffState && aBiffState == nsMsgBiffState_NewMail)
+ {
+ // The folder has been updated, so update the MRUTime
+ SetMRUTime();
+ // biff is already set, but notify that there is additional new mail for the folder
+ NotifyIntPropertyChanged(kNewMailReceivedAtom, 0, mNumNewBiffMessages);
+ }
+ else if (aBiffState == nsMsgBiffState_NoMail)
+ {
+ // even if the old biff state equals the new biff state, it is still possible that we've never
+ // cleared the number of new messages for this particular folder. This happens when the new mail state
+ // got cleared by viewing a new message in folder that is different from this one. Biff state is stored per server
+ // the num. of new messages is per folder.
+ SetNumNewMessages(0);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumNewMessages(bool deep, int32_t *aNumNewMessages)
+{
+ NS_ENSURE_ARG_POINTER(aNumNewMessages);
+
+ int32_t numNewMessages = (!deep || ! (mFlags & nsMsgFolderFlags::Virtual))
+ ? mNumNewBiffMessages : 0;
+ if (deep)
+ {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++)
+ {
+ int32_t num;
+ mSubFolders[i]->GetNumNewMessages(deep, &num);
+ if (num > 0) // it's legal for counts to be negative if we don't know
+ numNewMessages += num;
+ }
+ }
+ *aNumNewMessages = numNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetNumNewMessages(int32_t aNumNewMessages)
+{
+ if (aNumNewMessages != mNumNewBiffMessages)
+ {
+ int32_t oldNumMessages = mNumNewBiffMessages;
+ mNumNewBiffMessages = aNumNewMessages;
+
+ nsAutoCString oldNumMessagesStr;
+ oldNumMessagesStr.AppendInt(oldNumMessages);
+ nsAutoCString newNumMessagesStr;
+ newNumMessagesStr.AppendInt(aNumNewMessages);
+ NotifyPropertyChanged(kNumNewBiffMessagesAtom, oldNumMessagesStr, newNumMessagesStr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetRootFolder(nsIMsgFolder * *aRootFolder)
+{
+ NS_ENSURE_ARG_POINTER(aRootFolder);
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetRootMsgFolder(aRootFolder);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetFilePath(nsIFile *aFile)
+{
+ mPath = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetFilePath(nsIFile * *aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+ // make a new nsIFile object in case the caller
+ // alters the underlying file object.
+ nsCOMPtr <nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mPath)
+ parseURI(true);
+ rv = file->InitWithFile(mPath);
+ file.swap(*aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSummaryFile(nsIFile **aSummaryFile)
+{
+ NS_ENSURE_ARG_POINTER(aSummaryFile);
+
+ nsresult rv;
+ nsCOMPtr <nsIFile> newSummaryLocation =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation->InitWithFile(pathFile);
+
+ nsString fileName;
+ rv = newSummaryLocation->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fileName.Append(NS_LITERAL_STRING(SUMMARY_SUFFIX));
+ rv = newSummaryLocation->SetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation.forget(aSummaryFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkMessagesRead(nsIArray *messages, bool markRead)
+{
+ uint32_t count;
+ nsresult rv;
+
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(messages, i, &rv);
+ if (message)
+ rv = message->MarkRead(markRead);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkMessagesFlagged(nsIArray *messages, bool markFlagged)
+{
+ uint32_t count;
+ nsresult rv;
+
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(messages, i, &rv);
+ if (message)
+ rv = message->MarkFlagged(markFlagged);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel)
+{
+ NS_ENSURE_ARG(aMessages);
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) message->GetMessageKey(&msgKey);
+ rv = mDatabase->SetLabel(msgKey, aLabel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& junkScore)
+{
+ NS_ENSURE_ARG(aMessages);
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) message->GetMessageKey(&msgKey);
+ mDatabase->SetStringProperty(msgKey, "junkscore", nsCString(junkScore).get());
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::ApplyRetentionSettings()
+{
+ return ApplyRetentionSettings(true);
+}
+
+nsresult nsMsgDBFolder::ApplyRetentionSettings(bool deleteViaFolder)
+{
+ if (mFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders.
+ return NS_OK;
+ bool weOpenedDB = !mDatabase;
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
+ nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsMsgRetainByPreference retainByPreference =
+ nsIMsgRetentionSettings::nsMsgRetainAll;
+
+ retentionSettings->GetRetainByPreference(&retainByPreference);
+ if (retainByPreference != nsIMsgRetentionSettings::nsMsgRetainAll)
+ {
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mDatabase)
+ rv = mDatabase->ApplyRetentionSettings(retentionSettings, deleteViaFolder);
+ }
+ }
+ // we don't want applying retention settings to keep the db open, because
+ // if we try to purge a bunch of folders, that will leave the dbs all open.
+ // So if we opened the db, close it.
+ if (weOpenedDB)
+ CloseDBIfFolderNotOpen();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow,
+ bool deleteStorage,
+ bool isMove,
+ nsIMsgCopyServiceListener *listener,
+ bool allowUndo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyMessages(nsIMsgFolder* srcFolder,
+ nsIArray *messages,
+ bool isMove,
+ nsIMsgWindow *window,
+ nsIMsgCopyServiceListener* listener,
+ bool isFolder,
+ bool allowUndo)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyFolder(nsIMsgFolder* srcFolder,
+ bool isMoveFolder,
+ nsIMsgWindow *window,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyFileMessage(nsIFile* aFile,
+ nsIMsgDBHdr* messageToReplace,
+ bool isDraftOrTemplate,
+ uint32_t aNewMsgFlags,
+ const nsACString &aNewMsgKeywords,
+ nsIMsgWindow *window,
+ nsIMsgCopyServiceListener* listener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CopyDataToOutputStreamForAppend(nsIInputStream *aInStream,
+ int32_t aLength, nsIOutputStream *aOutputStream)
+{
+ if (!aInStream)
+ return NS_OK;
+
+ uint32_t uiWritten;
+ return aOutputStream->WriteFrom(aInStream, aLength, &uiWritten);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CopyDataDone()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyPropertyChanged(nsIAtom *aProperty,
+ const nsACString& aOldValue,
+ const nsACString& aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemPropertyChanged,
+ (this, aProperty,
+ nsCString(aOldValue).get(),
+ nsCString(aNewValue).get()));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemPropertyChanged(this, aProperty,
+ nsCString(aOldValue).get(),
+ nsCString(aNewValue).get());
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyUnicharPropertyChanged(nsIAtom *aProperty,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemUnicharPropertyChanged,
+ (this, aProperty,
+ nsString(aOldValue).get(),
+ nsString(aNewValue).get()));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemUnicharPropertyChanged(this,
+ aProperty,
+ nsString(aOldValue).get(),
+ nsString(aNewValue).get());
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyIntPropertyChanged(nsIAtom *aProperty, int64_t aOldValue,
+ int64_t aNewValue)
+{
+ // Don't send off count notifications if they are turned off.
+ if (!mNotifyCountChanges &&
+ ((aProperty == kTotalMessagesAtom) ||
+ (aProperty == kTotalUnreadMessagesAtom)))
+ return NS_OK;
+
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemIntPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemIntPropertyChanged(this, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyBoolPropertyChanged(nsIAtom* aProperty,
+ bool aOldValue, bool aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemBoolPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemBoolPropertyChanged(this, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyPropertyFlagChanged(nsIMsgDBHdr *aItem, nsIAtom *aProperty,
+ uint32_t aOldValue, uint32_t aNewValue)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemPropertyFlagChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemPropertyFlagChanged(aItem, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyItemAdded(nsISupports *aItem)
+{
+ static bool notify = true;
+
+ if (!notify)
+ return NS_OK;
+
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemAdded,
+ (this, aItem));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemAdded(this, aItem);
+}
+
+nsresult nsMsgDBFolder::NotifyItemRemoved(nsISupports *aItem)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemRemoved,
+ (this, aItem));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemRemoved(this, aItem);
+}
+
+nsresult nsMsgDBFolder::NotifyFolderEvent(nsIAtom* aEvent)
+{
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(mListeners, nsIFolderListener,
+ OnItemEvent,
+ (this, aEvent));
+
+ //Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnItemEvent(this, aEvent);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetFilterList(nsIMsgFilterList *aFilterList)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetFilterList(aFilterList);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetEditableFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetEditableFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetEditableFilterList(nsIMsgFilterList *aFilterList)
+{
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetEditableFilterList(aFilterList);
+}
+
+/* void enableNotifications (in long notificationType, in boolean enable); */
+NS_IMETHODIMP nsMsgDBFolder::EnableNotifications(int32_t notificationType, bool enable, bool dbBatching)
+{
+ if (notificationType == nsIMsgFolder::allMessageCountNotifications)
+ {
+ mNotifyCountChanges = enable;
+ // start and stop db batching here. This is under the theory
+ // that any time we want to enable and disable notifications,
+ // we're probably doing something that should be batched.
+ nsCOMPtr <nsIMsgDatabase> database;
+
+ if (dbBatching) //only if we do dbBatching we need to get db
+ GetMsgDatabase(getter_AddRefs(database));
+
+ if (enable)
+ {
+ if (database)
+ database->EndBatch();
+ UpdateSummaryTotals(true);
+ }
+ else if (database)
+ return database->StartBatch();
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMessageHeader(nsMsgKey msgKey, nsIMsgDBHdr **aMsgHdr)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsCOMPtr <nsIMsgDatabase> database;
+ nsresult rv = GetMsgDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return (database) ? database->GetMsgHdrForKey(msgKey, aMsgHdr) : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDescendants(nsIArray** aDescendants)
+{
+ NS_ENSURE_ARG_POINTER(aDescendants);
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> allFolders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ListDescendants(allFolders);
+ allFolders.forget(aDescendants);
+ return NS_OK;
+}
+
+// this gets the deep sub-folders too, e.g., the children of the children
+NS_IMETHODIMP nsMsgDBFolder::ListDescendants(nsIMutableArray *aDescendants)
+{
+ NS_ENSURE_ARG_POINTER(aDescendants);
+
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ GetSubFolders(getter_AddRefs(dummy)); // initialize mSubFolders
+ uint32_t count = mSubFolders.Count();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]);
+ aDescendants->AppendElement(child, false);
+ child->ListDescendants(aDescendants); // recurse
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetBaseMessageURI(nsACString& baseMessageURI)
+{
+ if (mBaseMessageURI.IsEmpty())
+ return NS_ERROR_FAILURE;
+ baseMessageURI = mBaseMessageURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetUriForMsg(nsIMsgDBHdr *msgHdr, nsACString& aURI)
+{
+ NS_ENSURE_ARG(msgHdr);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ nsAutoCString uri;
+ uri.Assign(mBaseMessageURI);
+
+ // append a "#" followed by the message key.
+ uri.Append('#');
+ uri.AppendInt(msgKey);
+ aURI = uri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GenerateMessageURI(nsMsgKey msgKey, nsACString& aURI)
+{
+ nsCString uri;
+ nsresult rv = GetBaseMessageURI( uri);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // append a "#" followed by the message key.
+ uri.Append('#');
+ uri.AppendInt(msgKey);
+ aURI = uri;
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::GetBaseStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ bundle.swap(*aBundle);
+ return NS_OK;
+}
+
+nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time
+nsMsgDBFolder::GetStringFromBundle(const char *msgName, nsString& aResult)
+{
+ nsresult rv;
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(msgName).get(), getter_Copies(aResult));
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::ThrowConfirmationPrompt(nsIMsgWindow *msgWindow, const nsAString& confirmString, bool *confirmed)
+{
+ if (msgWindow)
+ {
+ nsCOMPtr <nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog && !confirmString.IsEmpty())
+ dialog->Confirm(nullptr, nsString(confirmString).get(), confirmed);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetStringWithFolderNameFromBundle(const char * msgName, nsAString& aResult)
+{
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ {
+ nsString folderName;
+ GetName(folderName);
+ const char16_t *formatStrings[] =
+ {
+ folderName.get(),
+ kLocalizedBrandShortName
+ };
+
+ nsString resultStr;
+ rv = bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(msgName).get(),
+ formatStrings, 2, getter_Copies(resultStr));
+ if (NS_SUCCEEDED(rv))
+ aResult.Assign(resultStr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ConfirmFolderDeletionForFilter(nsIMsgWindow *msgWindow, bool *confirmed)
+{
+ nsString confirmString;
+ nsresult rv = GetStringWithFolderNameFromBundle("confirmFolderDeletionForFilter", confirmString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ThrowConfirmationPrompt(msgWindow, confirmString, confirmed);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ThrowAlertMsg(const char * msgName, nsIMsgWindow *msgWindow)
+{
+ nsString alertString;
+ nsresult rv = GetStringWithFolderNameFromBundle(msgName, alertString);
+ if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow)
+ {
+ nsCOMPtr<nsIPrompt> dialog;
+ msgWindow->GetPromptDialog(getter_AddRefs(dialog));
+ if (dialog)
+ dialog->Alert(nullptr, alertString.get());
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AlertFilterChanged(nsIMsgWindow *msgWindow)
+{
+ NS_ENSURE_ARG(msgWindow);
+ nsresult rv = NS_OK;
+ bool checkBox=false;
+ GetWarnFilterChanged(&checkBox);
+ if (!checkBox)
+ {
+ nsCOMPtr <nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ nsString alertString;
+ rv = GetStringFromBundle("alertFilterChanged", alertString);
+ nsString alertCheckbox;
+ rv = GetStringFromBundle("alertFilterCheckbox", alertCheckbox);
+ if (!alertString.IsEmpty() && !alertCheckbox.IsEmpty() && docShell)
+ {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog)
+ {
+ dialog->AlertCheck(nullptr, alertString.get(), alertCheckbox.get(), &checkBox);
+ SetWarnFilterChanged(checkBox);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsMsgDBFolder::GetWarnFilterChanged(bool *aVal)
+{
+ NS_ENSURE_ARG(aVal);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = prefBranch->GetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
+ if (NS_FAILED(rv))
+ *aVal = false;
+ return NS_OK;
+}
+
+nsresult
+nsMsgDBFolder::SetWarnFilterChanged(bool aVal)
+{
+ nsresult rv=NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->SetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyCompactCompleted()
+{
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMsgDBFolder::CloseDBIfFolderNotOpen()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool folderOpen;
+ session->IsFolderOpenInWindow(this, &folderOpen);
+ if (!folderOpen && ! (mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ SetMsgDatabase(nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetSortOrder(int32_t order)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSortOrder(int32_t *order)
+{
+ NS_ENSURE_ARG_POINTER(order);
+
+ uint32_t flags;
+ nsresult rv = GetFlags(&flags);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (flags & nsMsgFolderFlags::Inbox)
+ *order = 0;
+ else if (flags & nsMsgFolderFlags::Drafts)
+ *order = 1;
+ else if (flags & nsMsgFolderFlags::Templates)
+ *order = 2;
+ else if (flags & nsMsgFolderFlags::SentMail)
+ *order = 3;
+ else if (flags & nsMsgFolderFlags::Archive)
+ *order = 4;
+ else if (flags & nsMsgFolderFlags::Junk)
+ *order = 5;
+ else if (flags & nsMsgFolderFlags::Trash)
+ *order = 6;
+ else if (flags & nsMsgFolderFlags::Virtual)
+ *order = 7;
+ else if (flags & nsMsgFolderFlags::Queue)
+ *order = 8;
+ else
+ *order = 9;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSortKey(uint32_t *aLength, uint8_t **aKey)
+{
+ NS_ENSURE_ARG(aKey);
+ int32_t order;
+ nsresult rv = GetSortOrder(&order);
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsAutoString orderString;
+ orderString.AppendInt(order);
+ nsString folderName;
+ rv = GetName(folderName);
+ NS_ENSURE_SUCCESS(rv,rv);
+ orderString.Append(folderName);
+ return CreateCollationKey(orderString, aKey, aLength);
+}
+
+nsresult
+nsMsgDBFolder::CreateCollationKey(const nsString &aSource, uint8_t **aKey, uint32_t *aLength)
+{
+ NS_ENSURE_TRUE(gCollationKeyGenerator, NS_ERROR_NULL_POINTER);
+ return gCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, aSource,
+ aKey, aLength);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CompareSortKeys(nsIMsgFolder *aFolder, int32_t *sortOrder)
+{
+ uint8_t *sortKey1=nullptr;
+ uint8_t *sortKey2=nullptr;
+ uint32_t sortKey1Length;
+ uint32_t sortKey2Length;
+ nsresult rv = GetSortKey(&sortKey1Length, &sortKey1);
+ NS_ENSURE_SUCCESS(rv,rv);
+ aFolder->GetSortKey(&sortKey2Length, &sortKey2);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = gCollationKeyGenerator->CompareRawSortKey(sortKey1, sortKey1Length, sortKey2, sortKey2Length, sortOrder);
+ PR_Free(sortKey1);
+ PR_Free(sortKey2);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetInVFEditSearchScope (bool *aInVFEditSearchScope)
+{
+ *aInVFEditSearchScope = mInVFEditSearchScope;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetInVFEditSearchScope (bool aInVFEditSearchScope, bool aSetOnSubFolders)
+{
+ bool oldInVFEditSearchScope = mInVFEditSearchScope;
+ mInVFEditSearchScope = aInVFEditSearchScope;
+ NotifyBoolPropertyChanged(kInVFEditSearchScopeAtom, oldInVFEditSearchScope, mInVFEditSearchScope);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::FetchMsgPreviewText(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults)
+{
+ NS_ENSURE_ARG_POINTER(aKeysToFetch);
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMsgTextFromStream(nsIInputStream *stream, const nsACString &aCharset,
+ uint32_t bytesToRead, uint32_t aMaxOutputLen,
+ bool aCompressQuotes, bool aStripHTMLTags,
+ nsACString &aContentType, nsACString &aMsgText)
+{
+ /*
+ 1. non mime message - the message body starts after the blank line following the headers.
+ 2. mime message, multipart/alternative - we could simply scan for the boundary line,
+ advance past its headers, and treat the next few lines as the text.
+ 3. mime message, text/plain - body follows headers
+ 4. multipart/mixed - scan past boundary, treat next part as body.
+ */
+
+ nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
+ NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString msgText;
+ nsAutoString contentType;
+ nsAutoString encoding;
+ nsAutoCString curLine;
+ nsAutoCString charset(aCharset);
+
+ // might want to use a state var instead of bools.
+ bool msgBodyIsHtml = false;
+ bool more = true;
+ bool reachedEndBody = false;
+ bool isBase64 = false;
+ bool inMsgBody = false;
+ bool justPassedEndBoundary = false;
+
+ uint32_t bytesRead = 0;
+
+ nsresult rv;
+
+ // Both are used to extract data from the headers
+ nsCOMPtr<nsIMimeHeaders> mimeHeaders(do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParam(do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Stack of boundaries, used to figure out where we are
+ nsTArray<nsCString> boundaryStack;
+
+ while (!inMsgBody && bytesRead <= bytesToRead)
+ {
+ nsAutoCString msgHeaders;
+ // We want to NS_ReadLine until we get to a blank line (the end of the headers)
+ while (more)
+ {
+ rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (curLine.IsEmpty())
+ break;
+ msgHeaders.Append(curLine);
+ msgHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+ bytesRead += curLine.Length();
+ if (bytesRead > bytesToRead)
+ break;
+ }
+
+ // There's no point in processing if we can't get the body
+ if (bytesRead > bytesToRead)
+ break;
+
+ // Process the headers, looking for things we need
+ rv = mimeHeaders->Initialize(msgHeaders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contentTypeHdr;
+ mimeHeaders->ExtractHeader("Content-Type", false, contentTypeHdr);
+
+ // Get the content type
+ // If we don't have a content type, then we assign text/plain
+ // this is in violation of the RFC for multipart/digest, though
+ // Also, if we've just passed an end boundary, we're going to ignore this.
+ if (!justPassedEndBoundary && contentTypeHdr.IsEmpty())
+ contentType.Assign(NS_LITERAL_STRING("text/plain"));
+ else
+ mimeHdrParam->GetParameter(contentTypeHdr, nullptr, EmptyCString(), false, nullptr, contentType);
+
+ justPassedEndBoundary = false;
+
+ // If we are multipart, then we need to get the boundary
+ if (StringBeginsWith(contentType, NS_LITERAL_STRING("multipart/"), nsCaseInsensitiveStringComparator()))
+ {
+ nsAutoString boundaryParam;
+ mimeHdrParam->GetParameter(contentTypeHdr, "boundary", EmptyCString(), false, nullptr, boundaryParam);
+ if (!boundaryParam.IsEmpty())
+ {
+ nsAutoCString boundary(NS_LITERAL_CSTRING("--"));
+ boundary.Append(NS_ConvertUTF16toUTF8(boundaryParam));
+ boundaryStack.AppendElement(boundary);
+ }
+ }
+
+ // If we are message/rfc822, then there's another header block coming up
+ else if (contentType.LowerCaseEqualsLiteral("message/rfc822"))
+ continue;
+
+ // If we are a text part, then we want it
+ else if (StringBeginsWith(contentType, NS_LITERAL_STRING("text/"), nsCaseInsensitiveStringComparator()))
+ {
+ inMsgBody = true;
+
+ if (contentType.LowerCaseEqualsLiteral("text/html"))
+ msgBodyIsHtml = true;
+
+ // Also get the charset if required
+ if (charset.IsEmpty())
+ {
+ nsAutoString charsetW;
+ mimeHdrParam->GetParameter(contentTypeHdr, "charset", EmptyCString(), false, nullptr, charsetW);
+ charset.Assign(NS_ConvertUTF16toUTF8(charsetW));
+ }
+
+ // Finally, get the encoding
+ nsAutoCString encodingHdr;
+ mimeHeaders->ExtractHeader("Content-Transfer-Encoding", false, encodingHdr);
+ if (!encodingHdr.IsEmpty())
+ mimeHdrParam->GetParameter(encodingHdr, nullptr, EmptyCString(), false, nullptr, encoding);
+
+ if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64))
+ isBase64 = true;
+ }
+
+ // We need to consume the rest, until the next headers
+ uint32_t count = boundaryStack.Length();
+ nsAutoCString boundary;
+ nsAutoCString endBoundary;
+ if (count)
+ {
+ boundary.Assign(boundaryStack.ElementAt(count - 1));
+ endBoundary.Assign(boundary);
+ endBoundary.Append(NS_LITERAL_CSTRING("--"));
+ }
+ while (more)
+ {
+ rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count)
+ {
+ // If we've reached a MIME final delimiter, pop and break
+ if (StringBeginsWith(curLine, endBoundary))
+ {
+ if (inMsgBody)
+ reachedEndBody = true;
+ boundaryStack.RemoveElementAt(count - 1);
+ justPassedEndBoundary = true;
+ break;
+ }
+ // If we've reached the end of this MIME part, we can break
+ if (StringBeginsWith(curLine, boundary))
+ {
+ if (inMsgBody)
+ reachedEndBody = true;
+ break;
+ }
+ }
+
+ // Only append the text if we're actually in the message body
+ if (inMsgBody)
+ {
+ msgText.Append(curLine);
+ if (!isBase64)
+ msgText.Append(NS_LITERAL_CSTRING("\r\n"));
+ }
+
+ bytesRead += curLine.Length();
+ if (bytesRead > bytesToRead)
+ break;
+ }
+ }
+ lineBuffer = nullptr;
+
+ // if the snippet is encoded, decode it
+ if (!encoding.IsEmpty())
+ decodeMsgSnippet(NS_ConvertUTF16toUTF8(encoding), !reachedEndBody, msgText);
+
+ // In order to turn our snippet into unicode, we need to convert it from the charset we
+ // detected earlier.
+ nsString unicodeMsgBodyStr;
+ ConvertToUnicode(charset.get(), msgText, unicodeMsgBodyStr);
+
+ // now we've got a msg body. If it's html, convert it to plain text.
+ if (msgBodyIsHtml && aStripHTMLTags)
+ ConvertMsgSnippetToPlainText(unicodeMsgBodyStr, unicodeMsgBodyStr);
+
+ // We want to remove any whitespace from the beginning and end of the string
+ unicodeMsgBodyStr.Trim(" \t\r\n", true, true);
+
+ // step 3, optionally remove quoted text from the snippet
+ nsString compressedQuotesMsgStr;
+ if (aCompressQuotes)
+ compressQuotesInMsgSnippet(unicodeMsgBodyStr, compressedQuotesMsgStr);
+
+ // now convert back to utf-8 which is more convenient for storage
+ CopyUTF16toUTF8(aCompressQuotes ? compressedQuotesMsgStr : unicodeMsgBodyStr, aMsgText);
+
+ // finally, truncate the string based on aMaxOutputLen
+ if (aMsgText.Length() > aMaxOutputLen) {
+ if (NS_IsAscii(aMsgText.BeginReading()))
+ aMsgText.SetLength(aMaxOutputLen);
+ else
+ nsMsgI18NShrinkUTF8Str(nsCString(aMsgText),
+ aMaxOutputLen, aMsgText);
+ }
+
+ // Also assign the content type being returned
+ aContentType.Assign(NS_ConvertUTF16toUTF8(contentType));
+ return rv;
+}
+
+/**
+ * decodeMsgSnippet - helper function which applies the appropriate transfer decoding
+ * to the message snippet based on aEncodingType. Currently handles
+ * base64 and quoted-printable. If aEncodingType refers to an encoding we don't
+ * handle, the message data is passed back unmodified.
+ * @param aEncodingType the encoding type (base64, quoted-printable)
+ * @param aIsComplete the snippet is actually the entire message so the decoder
+ * doesn't have to worry about partial data
+ * @param aMsgSnippet in/out argument. The encoded msg snippet and then the decoded snippet
+ */
+void nsMsgDBFolder::decodeMsgSnippet(const nsACString& aEncodingType, bool aIsComplete, nsCString& aMsgSnippet)
+{
+ if (MsgLowerCaseEqualsLiteral(aEncodingType, ENCODING_BASE64))
+ {
+ int32_t base64Len = aMsgSnippet.Length();
+ if (aIsComplete)
+ base64Len -= base64Len % 4;
+ char *decodedBody = PL_Base64Decode(aMsgSnippet.get(), base64Len, nullptr);
+ if (decodedBody)
+ aMsgSnippet.Adopt(decodedBody);
+ }
+ else if (MsgLowerCaseEqualsLiteral(aEncodingType, ENCODING_QUOTED_PRINTABLE))
+ {
+ // giant hack - decode in place, and truncate string.
+ MsgStripQuotedPrintable((unsigned char *) aMsgSnippet.get());
+ aMsgSnippet.SetLength(strlen(aMsgSnippet.get()));
+ }
+}
+
+/**
+ * stripQuotesFromMsgSnippet - Reduces quoted reply text including the citation (Scott wrote:) from
+ * the message snippet to " ... ". Assumes the snippet has been decoded and converted to
+ * plain text.
+ * @param aMsgSnippet in/out argument. The string to strip quotes from.
+ */
+void nsMsgDBFolder::compressQuotesInMsgSnippet(const nsString& aMsgSnippet, nsAString& aCompressedQuotes)
+{
+ int32_t msgBodyStrLen = aMsgSnippet.Length();
+ bool lastLineWasAQuote = false;
+ int32_t offset = 0;
+ int32_t lineFeedPos = 0;
+ while (offset < msgBodyStrLen)
+ {
+ lineFeedPos = aMsgSnippet.FindChar('\n', offset);
+ if (lineFeedPos != -1)
+ {
+ const nsAString& currentLine = Substring(aMsgSnippet, offset, lineFeedPos - offset);
+ // this catches quoted text ("> "), nested quotes of any level (">> ", ">>> ", ...)
+ // it also catches empty line quoted text (">"). It might be over agressive and require
+ // tweaking later.
+ // Try to strip the citation. If the current line ends with a ':' and the next line
+ // looks like a quoted reply (starts with a ">") skip the current line
+ if (StringBeginsWith(currentLine, NS_LITERAL_STRING(">")) ||
+ (lineFeedPos + 1 < msgBodyStrLen && lineFeedPos
+ && aMsgSnippet[lineFeedPos - 1] == char16_t(':')
+ && aMsgSnippet[lineFeedPos + 1] == char16_t('>')))
+ {
+ lastLineWasAQuote = true;
+ }
+ else if (!currentLine.IsEmpty())
+ {
+ if (lastLineWasAQuote)
+ {
+ aCompressedQuotes += NS_LITERAL_STRING(" ... ");
+ lastLineWasAQuote = false;
+ }
+
+ aCompressedQuotes += currentLine;
+ aCompressedQuotes += char16_t(' '); // don't forget to substitute a space for the line feed
+ }
+
+ offset = lineFeedPos + 1;
+ }
+ else
+ {
+ aCompressedQuotes.Append(Substring(aMsgSnippet, offset, msgBodyStrLen - offset));
+ break;
+ }
+ }
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ConvertMsgSnippetToPlainText(
+ const nsAString& aMessageText, nsAString& aOutText)
+{
+ uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak
+ | nsIDocumentEncoder::OutputNoScriptContent
+ | nsIDocumentEncoder::OutputNoFramesContent
+ | nsIDocumentEncoder::OutputBodyOnly;
+ nsCOMPtr<nsIParserUtils> utils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(aMessageText, flags, 80, aOutText);
+}
+
+nsresult nsMsgDBFolder::GetMsgPreviewTextFromStream(nsIMsgDBHdr *msgHdr, nsIInputStream *stream)
+{
+ nsCString msgBody;
+ nsAutoCString charset;
+ msgHdr->GetCharset(getter_Copies(charset));
+ nsAutoCString contentType;
+ nsresult rv = GetMsgTextFromStream(stream, charset, 4096, 255, true, true, contentType, msgBody);
+ // replaces all tabs and line returns with a space,
+ // then trims off leading and trailing white space
+ MsgCompressWhitespace(msgBody);
+ msgHdr->SetStringProperty("preview", msgBody.get());
+ return rv;
+}
+
+void nsMsgDBFolder::UpdateTimestamps(bool allowUndo)
+{
+ if (!(mFlags & (nsMsgFolderFlags::Trash|nsMsgFolderFlags::Junk)))
+ {
+ SetMRUTime();
+ if (allowUndo) // This is a proxy for a user-initiated act.
+ {
+ bool isArchive;
+ IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isArchive);
+ if (!isArchive)
+ {
+ SetMRMTime();
+ nsCOMPtr<nsIAtom> MRMTimeChangedAtom = MsgGetAtom("MRMTimeChanged");
+ NotifyFolderEvent(MRMTimeChangedAtom);
+ }
+ }
+ }
+}
+
+void nsMsgDBFolder::SetMRUTime()
+{
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ SetStringProperty(MRU_TIME_PROPERTY, nowStr);
+}
+
+void nsMsgDBFolder::SetMRMTime()
+{
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ SetStringProperty(MRM_TIME_PROPERTY, nowStr);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ NS_ENSURE_ARG(aMessages);
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString keywords;
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ message->GetStringProperty("keywords", getter_Copies(keywords));
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+ uint32_t addCount = 0;
+ for (uint32_t j = 0; j < keywordArray.Length(); j++)
+ {
+ int32_t start, length;
+ if (!MsgFindKeyword(keywordArray[j], keywords, &start, &length))
+ {
+ if (!keywords.IsEmpty())
+ keywords.Append(' ');
+ keywords.Append(keywordArray[j]);
+ addCount++;
+ }
+ }
+ // avoid using the message key to set the string property, because
+ // in the case of filters running on incoming pop3 mail with quarantining
+ // turned on, the message key is wrong.
+ mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get());
+
+ if (addCount)
+ NotifyPropertyFlagChanged(message, kKeywords, 0, addCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords)
+{
+ NS_ENSURE_ARG(aMessages);
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase)
+ {
+ uint32_t count;
+ nsresult rv = aMessages->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+ nsCString keywords;
+ // If the tag is also a label, we should remove the label too...
+
+ for(uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(aMessages, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = message->GetStringProperty("keywords", getter_Copies(keywords));
+ uint32_t removeCount = 0;
+ for (uint32_t j = 0; j < keywordArray.Length(); j++)
+ {
+ bool keywordIsLabel = (StringBeginsWith(keywordArray[j], NS_LITERAL_CSTRING("$label"))
+ && keywordArray[j].CharAt(6) >= '1' && keywordArray[j].CharAt(6) <= '5');
+ if (keywordIsLabel)
+ {
+ nsMsgLabelValue labelValue;
+ message->GetLabel(&labelValue);
+ // if we're removing the keyword that corresponds to a pre 2.0 label,
+ // we need to clear the old label attribute on the message.
+ if (labelValue == (nsMsgLabelValue) (keywordArray[j].CharAt(6) - '0'))
+ message->SetLabel((nsMsgLabelValue) 0);
+ }
+ int32_t startOffset, length;
+ if (MsgFindKeyword(keywordArray[j], keywords, &startOffset, &length))
+ {
+ // delete any leading space delimiters
+ while (startOffset && keywords.CharAt(startOffset - 1) == ' ')
+ {
+ startOffset--;
+ length++;
+ }
+ // but if the keyword is at the start then delete the following space
+ if (!startOffset && length < static_cast<int32_t>(keywords.Length()) &&
+ keywords.CharAt(length) == ' ')
+ length++;
+ keywords.Cut(startOffset, length);
+ removeCount++;
+ }
+ }
+
+ if (removeCount)
+ {
+ mDatabase->SetStringPropertyByHdr(message, "keywords", keywords.get());
+ NotifyPropertyFlagChanged(message, kKeywords, removeCount, 0);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetCustomIdentity(nsIMsgIdentity **aIdentity)
+{
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ *aIdentity = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetProcessingFlags(nsMsgKey aKey, uint32_t *aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = 0;
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (mProcessingFlag[i].keys && mProcessingFlag[i].keys->IsMember(aKey))
+ *aFlags |= mProcessingFlag[i].bit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OrProcessingFlags(nsMsgKey aKey, uint32_t mask)
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (mProcessingFlag[i].bit & mask && mProcessingFlag[i].keys)
+ mProcessingFlag[i].keys->Add(aKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AndProcessingFlags(nsMsgKey aKey, uint32_t mask)
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (!(mProcessingFlag[i].bit & mask) && mProcessingFlag[i].keys)
+ mProcessingFlag[i].keys->Remove(aKey);
+ return NS_OK;
+}
+
+// Each implementation must provide an override of this, connecting the folder
+// type to the corresponding incoming server type.
+NS_IMETHODIMP nsMsgDBFolder::GetIncomingServerType(nsACString& aIncomingServerType)
+{
+ NS_ASSERTION(false, "subclasses need to override this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsMsgDBFolder::ClearProcessingFlags()
+{
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ {
+ // There is no clear method so we need to delete and re-create.
+ delete mProcessingFlag[i].keys;
+ mProcessingFlag[i].keys = nsMsgKeySetU::Create();
+ }
+}
+
+nsresult nsMsgDBFolder::MessagesInKeyOrder(nsTArray<nsMsgKey> &aKeyArray,
+ nsIMsgFolder *srcFolder, nsIMutableArray* messages)
+{
+ // XXX: the output messages - should really be and nsCOMArray<nsIMsgDBHdr>
+
+ nsresult rv = NS_OK;
+ uint32_t numMessages = aKeyArray.Length();
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ for (uint32_t i = 0; i < numMessages; i++)
+ {
+ rv = db->GetMsgHdrForKey(aKeyArray[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (msgHdr)
+ messages->AppendElement(msgHdr, false);
+ }
+ }
+ return rv;
+}
+
+/* static */ nsMsgKeySetU* nsMsgKeySetU::Create()
+{
+ nsMsgKeySetU* set = new nsMsgKeySetU;
+ if (set)
+ {
+ set->loKeySet = nsMsgKeySet::Create();
+ set->hiKeySet = nsMsgKeySet::Create();
+ if (!(set->loKeySet && set->hiKeySet))
+ {
+ delete set;
+ set = nullptr;
+ }
+ }
+ return set;
+}
+
+nsMsgKeySetU::nsMsgKeySetU()
+{ }
+
+nsMsgKeySetU::~nsMsgKeySetU()
+{
+ delete loKeySet;
+ delete hiKeySet;
+}
+
+const uint32_t kLowerBits = 0x7fffffff;
+
+int nsMsgKeySetU::Add(nsMsgKey aKey)
+{
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0)
+ return loKeySet->Add(intKey);
+ return hiKeySet->Add(intKey & kLowerBits);
+}
+
+int nsMsgKeySetU::Remove(nsMsgKey aKey)
+{
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0)
+ return loKeySet->Remove(intKey);
+ return hiKeySet->Remove(intKey & kLowerBits);
+}
+
+bool nsMsgKeySetU::IsMember(nsMsgKey aKey)
+{
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0)
+ return loKeySet->IsMember(intKey);
+ return hiKeySet->IsMember(intKey & kLowerBits);
+}
+
+nsresult nsMsgKeySetU::ToMsgKeyArray(nsTArray<nsMsgKey> &aArray)
+{
+ nsresult rv = loKeySet->ToMsgKeyArray(aArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hiKeySet->ToMsgKeyArray(aArray);
+}
+