summaryrefslogtreecommitdiffstats
path: root/mailnews/db/msgdb
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2019-11-03 00:17:46 -0400
committerMatt A. Tobin <email@mattatobin.com>2019-11-03 00:17:46 -0400
commit302bf1b523012e11b60425d6eee1221ebc2724eb (patch)
treeb191a895f8716efcbe42f454f37597a545a6f421 /mailnews/db/msgdb
parent21b3f6247403c06f85e1f45d219f87549862198f (diff)
downloadUXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.gz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.lz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.xz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.zip
Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1
Diffstat (limited to 'mailnews/db/msgdb')
-rw-r--r--mailnews/db/msgdb/moz.build9
-rw-r--r--mailnews/db/msgdb/public/moz.build27
-rw-r--r--mailnews/db/msgdb/public/nsDBFolderInfo.h135
-rw-r--r--mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl33
-rw-r--r--mailnews/db/msgdb/public/nsIDBChangeListener.idl115
-rw-r--r--mailnews/db/msgdb/public/nsIDBFolderInfo.idl108
-rw-r--r--mailnews/db/msgdb/public/nsIMsgDatabase.idl570
-rw-r--r--mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl53
-rw-r--r--mailnews/db/msgdb/public/nsINewsDatabase.idl18
-rw-r--r--mailnews/db/msgdb/public/nsImapMailDatabase.h52
-rw-r--r--mailnews/db/msgdb/public/nsMailDatabase.h67
-rw-r--r--mailnews/db/msgdb/public/nsMsgDBCID.h63
-rw-r--r--mailnews/db/msgdb/public/nsMsgDatabase.h462
-rw-r--r--mailnews/db/msgdb/public/nsMsgHdr.h86
-rw-r--r--mailnews/db/msgdb/public/nsMsgThread.h63
-rw-r--r--mailnews/db/msgdb/public/nsNewsDatabase.h57
-rw-r--r--mailnews/db/msgdb/src/moz.build18
-rw-r--r--mailnews/db/msgdb/src/nsDBFolderInfo.cpp977
-rw-r--r--mailnews/db/msgdb/src/nsImapMailDatabase.cpp249
-rw-r--r--mailnews/db/msgdb/src/nsMailDatabase.cpp444
-rw-r--r--mailnews/db/msgdb/src/nsMsgDatabase.cpp5915
-rw-r--r--mailnews/db/msgdb/src/nsMsgHdr.cpp1098
-rw-r--r--mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp378
-rw-r--r--mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h55
-rw-r--r--mailnews/db/msgdb/src/nsMsgThread.cpp1180
-rw-r--r--mailnews/db/msgdb/src/nsNewsDatabase.cpp360
26 files changed, 12592 insertions, 0 deletions
diff --git a/mailnews/db/msgdb/moz.build b/mailnews/db/msgdb/moz.build
new file mode 100644
index 000000000..27dcb8746
--- /dev/null
+++ b/mailnews/db/msgdb/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ 'public',
+ 'src',
+]
diff --git a/mailnews/db/msgdb/public/moz.build b/mailnews/db/msgdb/public/moz.build
new file mode 100644
index 000000000..9bf74e1d6
--- /dev/null
+++ b/mailnews/db/msgdb/public/moz.build
@@ -0,0 +1,27 @@
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ 'nsIDBChangeAnnouncer.idl',
+ 'nsIDBChangeListener.idl',
+ 'nsIDBFolderInfo.idl',
+ 'nsIMsgDatabase.idl',
+ 'nsIMsgOfflineImapOperation.idl',
+ 'nsINewsDatabase.idl',
+]
+
+XPIDL_MODULE = 'msgdb'
+
+EXPORTS += [
+ 'nsDBFolderInfo.h',
+ 'nsImapMailDatabase.h',
+ 'nsMailDatabase.h',
+ 'nsMsgDatabase.h',
+ 'nsMsgDBCID.h',
+ 'nsMsgHdr.h',
+ 'nsMsgThread.h',
+ 'nsNewsDatabase.h',
+]
+
diff --git a/mailnews/db/msgdb/public/nsDBFolderInfo.h b/mailnews/db/msgdb/public/nsDBFolderInfo.h
new file mode 100644
index 000000000..9b3a51348
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsDBFolderInfo.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/* This class encapsulates the global information about a folder stored in the
+ summary file.
+*/
+#ifndef _nsDBFolderInfo_H
+#define _nsDBFolderInfo_H
+
+#include "mozilla/MemoryReporting.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "mdb.h"
+#include "nsTArray.h"
+#include "nsIDBFolderInfo.h"
+#include <time.h>
+
+class nsMsgDatabase;
+
+// again, this could inherit from nsISupports, but I don't see the need as of yet.
+// I'm not sure it needs to be ref-counted (but I think it does).
+
+// I think these getters and setters really need to go through mdb and not rely on the object
+// caching the values. If this somehow turns out to be prohibitively expensive, we can invent
+// some sort of dirty mechanism, but I think it turns out that these values will be cached by
+// the MSG_FolderInfo's anyway.
+class nsDBFolderInfo : public nsIDBFolderInfo
+{
+public:
+ friend class nsMsgDatabase;
+
+ nsDBFolderInfo(nsMsgDatabase *mdb);
+
+ NS_DECL_ISUPPORTS
+ // interface methods.
+ NS_DECL_NSIDBFOLDERINFO
+ // create the appropriate table and row in a new db.
+ nsresult AddToNewMDB();
+ // accessor methods.
+
+ bool TestFlag(int32_t flags);
+ int16_t GetIMAPHierarchySeparator() ;
+ void SetIMAPHierarchySeparator(int16_t hierarchyDelimiter) ;
+ void ChangeImapTotalPendingMessages(int32_t delta);
+ void ChangeImapUnreadPendingMessages(int32_t delta) ;
+
+ nsresult InitFromExistingDB();
+ // get and set arbitrary property, aka row cell value.
+ nsresult SetPropertyWithToken(mdb_token aProperty, const nsAString &propertyStr);
+ nsresult SetUint32PropertyWithToken(mdb_token aProperty, uint32_t propertyValue);
+ nsresult SetInt64PropertyWithToken(mdb_token aProperty, int64_t propertyValue);
+ nsresult SetInt32PropertyWithToken(mdb_token aProperty, int32_t propertyValue);
+ nsresult GetPropertyWithToken(mdb_token aProperty, nsAString &propertyValue);
+ nsresult GetUint32PropertyWithToken(mdb_token aProperty, uint32_t &propertyValue, uint32_t defaultValue = 0);
+ nsresult GetInt32PropertyWithToken(mdb_token aProperty, int32_t &propertyValue, int32_t defaultValue = 0);
+ nsresult GetInt64PropertyWithToken(mdb_token aProperty,
+ int64_t &propertyValue, int64_t defaultValue = 0);
+
+ nsTArray<nsMsgKey> m_lateredKeys; // list of latered messages
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return m_lateredKeys.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+protected:
+ virtual ~nsDBFolderInfo();
+
+ // initialize from appropriate table and row in existing db.
+ nsresult InitMDBInfo();
+ nsresult LoadMemberVariables();
+
+ nsresult AdjustHighWater(nsMsgKey highWater, bool force);
+
+ void ReleaseExternalReferences(); // let go of any references to other objects.
+
+ int64_t m_folderSize;
+ int64_t m_expungedBytes; // sum of size of deleted messages in folder
+ uint32_t m_folderDate;
+ nsMsgKey m_highWaterMessageKey; // largest news article number or imap uid whose header we've seen
+
+ // m_numUnreadMessages and m_numMessages can never be negative. 0 means 'no msgs'.
+ int32_t m_numUnreadMessages;
+ int32_t m_numMessages; // includes expunged and ignored messages
+
+ int32_t m_flags; // folder specific flags. This holds things like re-use thread pane,
+ // configured for off-line use, use default retrieval, purge article/header options
+
+ uint16_t m_version; // for upgrading...
+ int16_t m_IMAPHierarchySeparator; // imap path separator
+
+ // mail only (for now)
+
+ // IMAP only
+ int32_t m_ImapUidValidity;
+ int32_t m_totalPendingMessages;
+ int32_t m_unreadPendingMessages;
+
+ // news only (for now)
+ nsMsgKey m_expiredMark; // Highest invalid article number in group - for expiring
+ // the db folder info will have to know what db and row it belongs to, since it is really
+ // just a wrapper around the singleton folder info row in the mdb.
+ nsMsgDatabase *m_mdb;
+ nsIMdbTable *m_mdbTable; // singleton table in db
+ nsIMdbRow *m_mdbRow; // singleton row in table;
+
+ nsCString m_charSet;
+ bool m_charSetOverride;
+ bool m_mdbTokensInitialized;
+
+ mdb_token m_rowScopeToken;
+ mdb_token m_tableKindToken;
+ // tokens for the pre-set columns - we cache these for speed, which may be silly
+ mdb_token m_mailboxNameColumnToken;
+ mdb_token m_numMessagesColumnToken;
+ mdb_token m_numUnreadMessagesColumnToken;
+ mdb_token m_flagsColumnToken;
+ mdb_token m_folderSizeColumnToken;
+ mdb_token m_expungedBytesColumnToken;
+ mdb_token m_folderDateColumnToken;
+ mdb_token m_highWaterMessageKeyColumnToken;
+
+ mdb_token m_imapUidValidityColumnToken;
+ mdb_token m_totalPendingMessagesColumnToken;
+ mdb_token m_unreadPendingMessagesColumnToken;
+ mdb_token m_expiredMarkColumnToken;
+ mdb_token m_versionColumnToken;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl b/mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl
new file mode 100644
index 000000000..28551ea60
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIDBChangeAnnouncer.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIDBChangeListener;
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(22baf00b-939d-42c3-ac51-21d99dfa1f05)]
+
+interface nsIDBChangeAnnouncer : nsISupports {
+ /* these 2 calls return NS_OK on success, NS_COMFALSE on failure */
+ void AddListener(in nsIDBChangeListener listener);
+ void RemoveListener(in nsIDBChangeListener listener);
+
+ void NotifyHdrChangeAll(in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags, in unsigned long aNewFlags,
+ in nsIDBChangeListener instigator);
+ void NotifyHdrAddedAll(in nsIMsgDBHdr aHdrAdded, in nsMsgKey parentKey, in long flags,
+ in nsIDBChangeListener instigator);
+ void NotifyHdrDeletedAll(in nsIMsgDBHdr aHdrDeleted, in nsMsgKey parentKey, in long flags,
+ in nsIDBChangeListener instigator);
+ void NotifyParentChangedAll(in nsMsgKey keyReparented, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener instigator);
+
+ void NotifyReadChanged(in nsIDBChangeListener instigator);
+
+ void NotifyJunkScoreChanged(in nsIDBChangeListener aInstigator);
+
+ void NotifyAnnouncerGoingAway();
+};
+
diff --git a/mailnews/db/msgdb/public/nsIDBChangeListener.idl b/mailnews/db/msgdb/public/nsIDBChangeListener.idl
new file mode 100644
index 000000000..3acda8ad8
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIDBChangeListener.idl
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIDBChangeAnnouncer;
+interface nsIMsgDBHdr;
+interface nsIMsgDatabase;
+
+/**
+ * These callbacks are provided to allow listeners to the message database
+ * to update their status when changes occur.
+ */
+[scriptable, uuid(21c56d34-71b9-42bb-9606-331a6a5f8210)]
+
+interface nsIDBChangeListener : nsISupports {
+ /**
+ * Callback when message flags are changed.
+ *
+ * @param aHdrChanged The changed header.
+ * @param aOldFlags Message flags prior to change.
+ * @param aNewFlags Message flags after change.
+ * @param aInstigator Object that initiated the change.
+ */
+ void onHdrFlagsChanged(in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags,
+ in unsigned long aNewFlags, in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when message is marked as deleted.
+ *
+ * @param aHdrChanged The message header that is going to be deleted.
+ * @param aParentKey Key of parent.
+ * @param aFlags Flags that message has before delete.
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onHdrDeleted(in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when message is added.
+ *
+ * @param aHdrChanged The message header that is added.
+ * @param aParentKey Parent key of message.
+ * @param aFlags Flags that new message will have.
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onHdrAdded(in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when message parrent is changed. Parent is changed when message is deleted or moved.
+ *
+ * @param aKeyChanged The message key that parent key was changed.
+ * @param oldParent Old parent key.
+ * @param newParent New parent key.
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onParentChanged(in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback when announcer is going away. This is good place to release strong pointers to announcer.
+ *
+ * @param instigator Object that initiated the change. Can be null.
+ */
+ void onAnnouncerGoingAway(in nsIDBChangeAnnouncer instigator);
+
+ /**
+ * Callback when read flag is changed.
+ *
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onReadChanged(in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback used in case when "junkscore" property is changed.
+ *
+ * @param aInstigator Object that initiated the change. Can be null.
+ */
+ void onJunkScoreChanged(in nsIDBChangeListener aInstigator);
+
+ /**
+ * Callback used in the general case where any field may have changed.
+ * OnHdrPropertyChanged is called twice per change. On the first call, aPreChange
+ * is true, and aStatus is undefined. OnHdrPropertyChanged saves any required status in aStatus
+ * (such as a filter match). The calling function stores the value of aStatus, changes the
+ * header aHdrToChange, then calls OnHdrPropertyChanged again with aPreChange false. On this
+ * second call, the stored value of aStatus is provided, so that any changes may be noted.
+ *
+ * @param aHdrToChange the message header that is changing.
+ * @param aPreChange true on first call before change, false on second call after change
+ * @param aStatus storage location provided by calling routine for status
+ * @param aInstigator object that initiated the change
+ */
+ void onHdrPropertyChanged(in nsIMsgDBHdr aHdrToChange, in boolean aPreChange, inout uint32_t aStatus,
+ in nsIDBChangeListener aInstigator);
+
+ /**
+ * Generic notification for extensibility. Common events should be documented
+ * here so we have a hope of keeping the documentation up to date.
+ * Current events are:
+ * "DBOpened" - When a pending listener becomes real. This can happen when
+ * the existing db is force closed and a new one opened. Only
+ * registered pending listeners are notified.
+ *
+ * @param aDB the db for this event.
+ * @param aEvent type of event.
+ *
+ */
+ void onEvent(in nsIMsgDatabase aDB, in string aEvent);
+};
+
diff --git a/mailnews/db/msgdb/public/nsIDBFolderInfo.idl b/mailnews/db/msgdb/public/nsIDBFolderInfo.idl
new file mode 100644
index 000000000..59b294d3b
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIDBFolderInfo.idl
@@ -0,0 +1,108 @@
+/* -*- 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 "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(a72dab4b-b3bd-471e-9a38-1b242b385459)]
+interface nsIDBFolderInfo : nsISupports {
+ attribute long flags;
+
+ /**
+ * Or's aFlags into flags.
+ *
+ * @param - the flags(s) to set
+ *
+ * @return - the resulting flags.
+ */
+ long orFlags(in long aFlags);
+ /**
+ * And's aFlags with flags, set flags to the result
+ *
+ * @param the flags(s) to AND
+ *
+ * @return the resulting flags.
+ */
+ long andFlags(in long aFlags);
+
+ /**
+ * Allows us to keep track of the highwater mark
+ *
+ * @param aNewKey If larger than the current highwater
+ * mark, sets the highwater mark to aNewKey.
+ */
+ void onKeyAdded(in nsMsgKey aNewKey);
+
+ attribute nsMsgKey highWater;
+ attribute nsMsgKey expiredMark;
+ attribute long long folderSize;
+ attribute unsigned long folderDate;
+ void changeNumUnreadMessages(in long aDelta);
+ void changeNumMessages(in long aDelta);
+
+ // numUnreadMessages and numMessages will never return negative numbers. 0 means 'no msgs'.
+ attribute long numUnreadMessages;
+ attribute long numMessages;
+
+ attribute long long expungedBytes;
+ attribute long imapUidValidity;
+ attribute unsigned long version;
+ attribute long imapTotalPendingMessages;
+ attribute long imapUnreadPendingMessages;
+
+ attribute nsMsgViewTypeValue viewType;
+ attribute nsMsgViewFlagsTypeValue viewFlags;
+ attribute nsMsgViewSortTypeValue sortType;
+ attribute nsMsgViewSortOrderValue sortOrder;
+
+ void changeExpungedBytes(in long aDelta);
+
+ /**
+ * Gets a string property from the folder.
+ *
+ * @param propertyName The name of the property for the value to retrieve.
+ */
+ ACString getCharProperty(in string propertyName);
+
+ /**
+ * Sets a string property from the folder.
+ *
+ * @param propertyName The name of the property for which to set a value
+ * @param propertyValue The new value of the property.
+ */
+ void setCharProperty(in string aPropertyName, in ACString aPropertyValue);
+ void setUint32Property(in string propertyName, in unsigned long propertyValue);
+ void setInt64Property(in string propertyName, in long long propertyValue);
+ unsigned long getUint32Property(in string propertyName, in unsigned long defaultValue);
+ long long getInt64Property(in string propertyName, in long long defaultValue);
+ boolean getBooleanProperty(in string propertyName, in boolean defaultValue);
+ void setBooleanProperty(in string propertyName, in boolean aPropertyValue);
+ nsIDBFolderInfo GetTransferInfo();
+ void initFromTransferInfo(in nsIDBFolderInfo transferInfo);
+
+ /**
+ * Gets/Sets the current character set for the folder. If there is no
+ * specific character set for the folder, it will return an empty string.
+ */
+ attribute ACString characterSet;
+
+ /**
+ * Returns the effective character set on the folder. If there is no specific
+ * set defined for the folder, it will return the default character set.
+ */
+ readonly attribute ACString effectiveCharacterSet;
+
+ attribute boolean characterSetOverride;
+
+ attribute AString locale;
+ attribute AString mailboxName;
+
+
+ AString getProperty(in string propertyName);
+ void setProperty(in string propertyName, in AString propertyStr);
+
+ attribute string knownArtsSet;
+ attribute ACString folderName;
+};
diff --git a/mailnews/db/msgdb/public/nsIMsgDatabase.idl b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
new file mode 100644
index 000000000..6b79fc940
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIMsgDatabase.idl
@@ -0,0 +1,570 @@
+/* -*- 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/. */
+
+/**
+ * @defgroup msgdb Mailnews message database
+ * This module is the access point to locally-stored databases.
+ *
+ * These databases are stored in .msf files. Each file contains useful cached
+ * information, like the message id or references, as well as the cc header or
+ * tag information. This cached information is encapsulated in nsIMsgDBHdr.
+ *
+ * Also included is threading information, mostly encapsulated in nsIMsgThread.
+ * The final component is the database folder info, which contains information
+ * on the view and basic information also stored in the folder cache such as the
+ * name or most recent update.
+ *
+ * What this module does not do is access individual messages. Access is
+ * strictly controlled by the nsIMsgFolder objects and their backends.
+ * @{
+ */
+#include "nsISupports.idl"
+#include "nsIDBChangeAnnouncer.idl"
+
+%{C++
+#include "nsTArray.h"
+%}
+
+interface nsIMutableArray;
+interface nsIMsgDatabase;
+interface nsIMsgDBView;
+interface nsIDBChangeListener;
+interface nsIMsgDBHdr;
+interface nsISimpleEnumerator;
+interface nsIMsgThread;
+interface nsIDBFolderInfo;
+interface nsIMsgOfflineImapOperation;
+interface nsIMsgFolder;
+interface nsIMsgKeyArray;
+interface nsIFile;
+interface nsIArray;
+
+typedef unsigned long nsMsgRetainByPreference;
+
+
+[scriptable, uuid(fe8b7cec-eec8-4bcd-82ff-d8bb23cef3da)]
+
+interface nsIMsgRetentionSettings : nsISupports
+{
+ const unsigned long nsMsgRetainAll = 1;
+ const unsigned long nsMsgRetainByAge = 2;
+ const unsigned long nsMsgRetainByNumHeaders = 3;
+
+ attribute boolean useServerDefaults;
+ attribute nsMsgRetainByPreference retainByPreference;
+ attribute unsigned long daysToKeepHdrs;
+ attribute unsigned long numHeadersToKeep;
+
+ // this is for keeping offline bodies.
+ attribute boolean cleanupBodiesByDays;
+ attribute unsigned long daysToKeepBodies;
+
+ /**
+ * Should retention settings be applied to flagged/starred messages?
+ * If false, flagged messages are never automatically deleted.
+ */
+ attribute boolean applyToFlaggedMessages;
+};
+
+[scriptable, uuid(86a9da90-14f1-11d5-a5c0-0060b0fc04b7)]
+interface nsIMsgDownloadSettings : nsISupports
+{
+ attribute boolean useServerDefaults;
+ attribute boolean downloadByDate;
+ attribute boolean downloadUnreadOnly;
+ attribute unsigned long ageLimitOfMsgsToDownload;
+};
+
+typedef long nsMsgDBCommit;
+
+[scriptable, uuid(15431853-e448-45dc-8978-9958bf74d9b7)]
+
+interface nsMsgDBCommitType
+{
+ const long kLargeCommit = 1;
+ const long kSessionCommit = 2;
+ const long kCompressCommit = 3;
+};
+
+[ref] native nsMsgKeyArrayRef(nsTArray<nsMsgKey>);
+[ptr] native nsMsgKeyArrayPtr(nsTArray<nsMsgKey>);
+
+/**
+ * A service to open mail databases and manipulate listeners automatically.
+ *
+ * The contract ID for this component is
+ * <tt>\@mozilla.org/msgDatabase/msgDBService;1</tt>.
+ */
+[scriptable, uuid(4cbbf024-3760-402d-89f3-6ababafeb07d)]
+interface nsIMsgDBService : nsISupports
+{
+ /**
+ * Opens a database for a given folder.
+ *
+ * This method is preferred over nsIMsgDBService::openMailDBFromFile if the
+ * caller has an actual nsIMsgFolder around. If the database detects that it
+ * is unreadable or out of date (using nsIMsgDatabase::outOfDate) it will
+ * destroy itself and prepare to be rebuilt, unless aLeaveInvalidDB is true.
+ *
+ * If one gets a NS_MSG_ERROR_FOLDER_SUMMARY_MISSING message, then one
+ * should call nsIMsgDBService::createNewDB to create the new database.
+ *
+ * @param aFolder The folder whose database should be returned.
+ * @param aLeaveInvalidDB Whether or not the database should be deleted if it
+ * is invalid.
+ * @return A new nsIMsgDatabase object representing the folder
+ * database that was opened.
+ * @exception NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
+ * The file could not be created.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE
+ * The database is present (and was opened), but the
+ * summary file is out of date.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_MISSING
+ * The database is present, but the summary file is
+ * missing.
+ * @see nsIMsgDatabase::Open
+ * @see nsIMsgDBService::createNewDB
+ */
+ nsIMsgDatabase openFolderDB(in nsIMsgFolder aFolder,
+ in boolean aLeaveInvalidDB);
+
+ /**
+ * This is the same as a synchronous open in terms of params and errors.
+ * But to finish opening the db, the caller must call
+ * nsIMsgDBService::OpenMore repeatedly until the open is finished.
+ * @see nsIMsgDBService::openFolderDB
+ * @see nsIMsgDBService::openMore
+ */
+ nsIMsgDatabase asyncOpenFolderDB(in nsIMsgFolder aFolder,
+ in boolean aLeaveInvalidDB);
+
+ /**
+ * Continues the open process for a db opened with
+ * nsIMsgDBService::asyncOpenFolderDB. Returns true if the db is ready
+ * to use, false if openMore needs to be called again.
+ * This will throw the same kinds of exceptions as openFolderDB.
+ * @param aTimeHint approximate number of milliseconds to spend
+ * before returning. This is more of a floor than
+ a ceiling, since we can't guarantee that there
+ won't be one big chunk that we can't interrupt.
+ * @return true if db is ready to use, false if openMore needs to
+ * be called again.
+ * @see nsIMsgDBService::openFolderDB
+ */
+ boolean openMore(in nsIMsgDatabase aDB, in unsigned long aTimeHint);
+
+ /**
+ * Creates a new database for the given folder.
+ *
+ * If the database already exists, it will return the database, emit a
+ * warning, but not fully initialize it. For this reason, it should only be
+ * used when it is known that the database does not exist, such as when
+ * nsIMsgDBService::openFolderDB throws an error.
+ *
+ * @see nsIMsgDBService::openFolderDB
+ */
+ nsIMsgDatabase createNewDB(in nsIMsgFolder aFolder);
+
+ /**
+ * Opens or creates a database for a given file.
+ *
+ * This method should only be used if the caller does not have a folder
+ * instance, because the resulting db and message headers retrieved from the
+ * database would not know their owning folder, which limits their usefulness.
+ * For this reason, one should use nsIMsgDBService::openFolderDB instead
+ * except under special circumstances.
+ *
+ * Unlike nsIMsgDBService::openFolderDB, there is no corresponding method to
+ * create a new database if opening the database failed. However, this method
+ * will never throw NS_MSG_ERROR_FOLDER_SUMMARY_MISSING, so no corresponding
+ * method is needed.
+ *
+ * @param aFile The file for which the database should be returned.
+ * @param aFolder Folder the db corresponds to (may be null)
+ * @param aCreate Whether or not the file should be created.
+ * @param aLeaveInvalidDB Whether or not the database should be deleted if it
+ * is invalid.
+ * @return A new nsIMsgDatabase object encapsulating the file
+ * passed in.
+ * @exception NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
+ * The file could not be created.
+ * @see nsIMsgDBService::openFolderDB
+ * @see nsIMsgDatabase::Open
+ */
+ nsIMsgDatabase openMailDBFromFile(in nsIFile aFile,
+ in nsIMsgFolder aFolder,
+ in boolean aCreate,
+ in boolean aLeaveInvalidDB);
+ /**
+ * Adds the given listener to the listener set for the folder.
+ *
+ * Since the message database will likely be opened and closed many times, by
+ * registering using this method, one will be guaranteed to see all subsequent
+ * modifications. This will also add the listener to the database if it is
+ * already opened.
+ *
+ * @param aFolder The folder to add a listener to.
+ * @param aListener The listener to add the folder to.
+ */
+ void registerPendingListener(in nsIMsgFolder aFolder,
+ in nsIDBChangeListener aListener);
+ /**
+ * Removes the listener from all folder listener sets.
+ *
+ * @param aListener The listener to remove.
+ * @exception NS_ERROR_FAILURE
+ * The listener is not registered.
+ */
+ void unregisterPendingListener(in nsIDBChangeListener aListener);
+
+ /**
+ * Get the db for a folder, if already open.
+ *
+ * @param aFolder The folder to get the cached (open) db for.
+ *
+ * @returns null if the db isn't open, otherwise the db.
+ */
+ nsIMsgDatabase cachedDBForFolder(in nsIMsgFolder aFolder);
+
+ /**
+ * Close the db for a folder, if already open.
+ *
+ * @param aFolder The folder to close the cached (open) db for.
+ */
+ void forceFolderDBClosed(in nsIMsgFolder aFolder);
+
+ /// an enumerator to iterate over the open dbs.
+ readonly attribute nsIArray openDBs;
+};
+
+[scriptable, uuid(b64e66f8-4717-423a-be42-482658fb2199)]
+interface nsIMsgDatabase : nsIDBChangeAnnouncer {
+ void Close(in boolean aForceCommit);
+
+ void Commit(in nsMsgDBCommit commitType);
+ // Force closed is evil, and we should see if we can do without it.
+ // In 4.x, it was mainly used to remove corrupted databases.
+ void ForceClosed();
+ void clearCachedHdrs();
+ void resetHdrCacheSize(in unsigned long size);
+
+ readonly attribute nsIDBFolderInfo dBFolderInfo;
+
+ /// Size of the database file in bytes.
+ readonly attribute long long databaseSize;
+
+ /// Folder this db was opened on.
+ readonly attribute nsIMsgFolder folder;
+
+ /**
+ * This is used when deciding which db's to close to free up memory
+ * and other resources in an LRU manner. It doesn't track every operation
+ * on every object from the db, but high level things like open, commit,
+ * and perhaps some of the list methods. Commit should be a proxy for all
+ * the mutation methods.
+ *
+ * I'm allowing clients to set the last use time as well, so that
+ * nsIMsgFolder.msgDatabase can set the last use time.
+ */
+ attribute PRTime lastUseTime;
+
+ // get a message header for the given key. Caller must release()!
+
+ nsIMsgDBHdr GetMsgHdrForKey(in nsMsgKey key);
+ nsIMsgDBHdr getMsgHdrForMessageID(in string messageID);
+
+ /**
+ * get a message header for a Gmail message with the given X-GM-MSGID.
+ */
+ nsIMsgDBHdr GetMsgHdrForGMMsgID(in string aGmailMessageID);
+ //Returns whether or not this database contains the given key
+ boolean ContainsKey(in nsMsgKey key);
+
+/**
+ * Must call AddNewHdrToDB after creating. The idea is that you create
+ * a new header, fill in its properties, and then call AddNewHdrToDB.
+ * AddNewHdrToDB will send notifications to any listeners.
+ *
+ * @param aKey msgKey for the new header. If aKey is nsMsgKey_None,
+ * we will auto-assign a new key.
+ */
+ nsIMsgDBHdr CreateNewHdr(in nsMsgKey aKey);
+
+ void AddNewHdrToDB(in nsIMsgDBHdr newHdr, in boolean notify);
+
+ nsIMsgDBHdr CopyHdrFromExistingHdr(in nsMsgKey key, in nsIMsgDBHdr existingHdr, in boolean addHdrToDB);
+
+ /**
+ * Returns all message keys stored in the database.
+ * Keys are returned in the order as stored in the database.
+ * The caller should sort them if it needs to.
+ */
+ void ListAllKeys(in nsIMsgKeyArray array);
+
+ nsISimpleEnumerator EnumerateMessages();
+ nsISimpleEnumerator ReverseEnumerateMessages();
+ nsISimpleEnumerator EnumerateThreads();
+
+ /**
+ * Get an enumerator for use with nextMatchingHdrs. The enumerator
+ * will only return messages that match the passed-in search terms.
+ *
+ * @param searchTerms array of search terms to evaluate.
+ * @param reverse start at the end, defaults to false.
+ *
+ * @returns an enumerator for passing into nextMatchingHdrs
+ */
+ nsISimpleEnumerator getFilterEnumerator(in nsIArray searchTerms,
+ [optional] in boolean reverse);
+
+ /**
+ * Get the next N matching headers using a filter enumerator
+ * obtained by calling getFilterEnumerator.
+ *
+ * @param enumerator - This *must* be a filter enumerator
+ * @param numHdrsToLookAt if non 0, the number of hdrs to advance the
+ * enumerator before returning.
+ * @param maxResults if non 0, the max results to return.
+ * @param matchingHdrs if non null, array of matching hdrs.
+ * @param numMatches if non null, the number of matching hdrs.
+ *
+ * @returns false, if done, true if more hdrs to look at.
+ */
+ boolean nextMatchingHdrs(in nsISimpleEnumerator enumerator,
+ in long numHdrsToLookAt,
+ in long maxResults,
+ in nsIMutableArray matchingHdrs,
+ out long numMatches);
+
+
+ // count the total and unread msgs, and adjust global count if needed
+ void syncCounts();
+
+ nsIMsgThread GetThreadContainingMsgHdr(in nsIMsgDBHdr msgHdr) ;
+
+ // helpers for user command functions like delete, mark read, etc.
+
+ void MarkHdrRead(in nsIMsgDBHdr msgHdr, in boolean bRead,
+ in nsIDBChangeListener instigator);
+
+ void MarkHdrReplied(in nsIMsgDBHdr msgHdr, in boolean bReplied,
+ in nsIDBChangeListener instigator);
+
+ void MarkHdrMarked(in nsIMsgDBHdr msgHdr, in boolean mark,
+ in nsIDBChangeListener instigator);
+ /**
+ * Remove the new status from a message.
+ *
+ * @param aMsgHdr The database reference header for the message
+ * @param aInstigator Reference to original calling object
+ */
+ void MarkHdrNotNew(in nsIMsgDBHdr aMsgHdr,
+ in nsIDBChangeListener aInstigator);
+
+ // MDN support
+ void MarkMDNNeeded(in nsMsgKey key, in boolean bNeeded,
+ in nsIDBChangeListener instigator);
+
+ // MarkMDNneeded only used when mail server is a POP3 server
+ // or when the IMAP server does not support user defined
+ // PERMANENTFLAGS
+ boolean IsMDNNeeded(in nsMsgKey key);
+
+ void MarkMDNSent(in nsMsgKey key, in boolean bNeeded,
+ in nsIDBChangeListener instigator);
+ boolean IsMDNSent(in nsMsgKey key);
+
+// methods to get and set docsets for ids.
+ void MarkRead(in nsMsgKey key, in boolean bRead,
+ in nsIDBChangeListener instigator);
+
+ void MarkReplied(in nsMsgKey key, in boolean bReplied,
+ in nsIDBChangeListener instigator);
+
+ void MarkForwarded(in nsMsgKey key, in boolean bForwarded,
+ in nsIDBChangeListener instigator);
+
+ void MarkHasAttachments(in nsMsgKey key, in boolean bHasAttachments,
+ in nsIDBChangeListener instigator);
+
+ void MarkThreadRead(in nsIMsgThread thread, in nsIDBChangeListener instigator,
+ out unsigned long aCount,
+ [array, size_is(aCount)] out nsMsgKey aKeys);
+
+ /// Mark the specified thread ignored.
+ void MarkThreadIgnored(in nsIMsgThread thread, in nsMsgKey threadKey,
+ in boolean bIgnored,
+ in nsIDBChangeListener instigator);
+
+ /// Mark the specified thread watched.
+ void MarkThreadWatched(in nsIMsgThread thread, in nsMsgKey threadKey,
+ in boolean bWatched,
+ in nsIDBChangeListener instigator);
+
+ /// Mark the specified subthread ignored.
+ void MarkHeaderKilled(in nsIMsgDBHdr msg, in boolean bIgnored,
+ in nsIDBChangeListener instigator);
+
+ /// Is the message read.
+ boolean IsRead(in nsMsgKey key);
+ /// Is the message part of an ignored thread.
+ boolean IsIgnored(in nsMsgKey key);
+ /// Is the message part of a watched thread.
+ boolean IsWatched(in nsMsgKey key);
+ /// Is the message flagged/starred.
+ boolean IsMarked(in nsMsgKey key);
+ /// Does the message have attachments.
+ boolean HasAttachments(in nsMsgKey key);
+
+ void MarkAllRead(out unsigned long aCount,
+ [array, size_is(aCount)] out nsMsgKey aKeys);
+
+ void deleteMessages(in unsigned long aNumKeys,
+ [array, size_is(aNumKeys)] in nsMsgKey nsMsgKeys,
+ in nsIDBChangeListener instigator);
+ void DeleteMessage(in nsMsgKey key,
+ in nsIDBChangeListener instigator,
+ in boolean commit);
+ void DeleteHeader(in nsIMsgDBHdr msgHdr, in nsIDBChangeListener instigator,
+ in boolean commit, in boolean notify);
+
+ // lower level routine that doesn't remove hdr from thread or adjust counts
+ void RemoveHeaderMdbRow(in nsIMsgDBHdr msgHdr);
+
+ void UndoDelete(in nsIMsgDBHdr msgHdr);
+
+ void MarkMarked(in nsMsgKey key, in boolean mark,
+ in nsIDBChangeListener instigator);
+ void MarkOffline(in nsMsgKey key, in boolean offline,
+ in nsIDBChangeListener instigator);
+ void SetLabel(in nsMsgKey key, in nsMsgLabelValue label);
+ void setStringProperty(in nsMsgKey aKey, in string aProperty, in string aValue);
+ /**
+ * Set the value of a string property in a message header
+ *
+ * @param msgHdr Header of the message whose property will be changed
+ * @param aProperty the property to change
+ * @param aValue new value for the property
+ */
+ void setStringPropertyByHdr(in nsIMsgDBHdr msgHdr, in string aProperty, in string aValue);
+
+ /**
+ * Set the value of a uint32 property in a message header.
+ *
+ * @param aMsgHdr header of the message whose property will be changed
+ * @param aProperty the property to change
+ * @param aValue new value for the property
+ */
+ void setUint32PropertyByHdr(in nsIMsgDBHdr aMsgHdr,
+ in string aProperty, in unsigned long aValue);
+
+ void MarkImapDeleted(in nsMsgKey key, in boolean deleted,
+ in nsIDBChangeListener instigator);
+
+ readonly attribute nsMsgKey FirstNew;
+
+ attribute nsIMsgRetentionSettings msgRetentionSettings;
+ // purge unwanted message headers and/or bodies. If deleteViaFolder is
+ // true, we'll call nsIMsgFolder::DeleteMessages to delete the messages.
+ // Otherwise, we'll just delete them from the db.
+ void applyRetentionSettings(in nsIMsgRetentionSettings aMsgRetentionSettings,
+ in boolean aDeleteViaFolder);
+
+ attribute nsIMsgDownloadSettings msgDownloadSettings;
+
+ boolean HasNew();
+ void ClearNewList(in boolean notify);
+ void AddToNewList(in nsMsgKey key);
+
+ // used mainly to force the timestamp of a local mail folder db to
+ // match the time stamp of the corresponding berkeley mail folder,
+ // but also useful to tell the summary to mark itself invalid
+ // Also, if a local folder is being reparsed, summary will be invalid
+ // until the reparsing is done.
+ attribute boolean summaryValid;
+
+ // batching - can be used to cache file stream for local mail,
+ // and perhaps to use the mdb batching mechanism as well.
+ void StartBatch();
+ void EndBatch();
+ // offline operations - we could move these into an offline operation interface
+ // but it would have to be in nsMailDatabase, since local folders can be move destinations
+ nsIMsgOfflineImapOperation GetOfflineOpForKey(in nsMsgKey messageKey, in boolean create);
+ void RemoveOfflineOp(in nsIMsgOfflineImapOperation op);
+ nsISimpleEnumerator EnumerateOfflineOps();
+ [noscript] void ListAllOfflineOpIds(in nsMsgKeyArrayPtr offlineOpIds);
+ [noscript] void ListAllOfflineDeletes(in nsMsgKeyArrayPtr offlineDeletes);
+ void ListAllOfflineMsgs(in nsIMsgKeyArray aKeys);
+
+ void setAttributeOnPendingHdr(in nsIMsgDBHdr pendingHdr, in string property,
+ in string propertyVal);
+
+ void setUint32AttributeOnPendingHdr(in nsIMsgDBHdr pendingHdr, in string property,
+ in unsigned long propertyVal);
+
+ /**
+ * Sets a pending 64 bit attribute, which tells the DB that when a message
+ * which looks like the pendingHdr (e.g., same message-id) is added to the
+ * db, set the passed in property and value on the new header. This is
+ * usually because we've copied an imap message to a different folder, and
+ * want to carry forward attributes from the original message to the copy,
+ * but don't have the message hdr for the copy yet so we can't set
+ * attributes directly.
+ *
+ * @param aPendingHdr usually the source of the copy.
+ * @param aProperty name of property to set.
+ * @param aPropertyVal 64 bit value of property to set.
+ */
+ void setUint64AttributeOnPendingHdr(in nsIMsgDBHdr aPendingHdr,
+ in string aProperty,
+ in unsigned long long aPropertyVal);
+
+ /**
+ * Given a message header with its message-id set, update any pending
+ * attributes on the header.
+ *
+ * @param aNewHdr a new header that may have pending attributes.
+ */
+ void updatePendingAttributes(in nsIMsgDBHdr aNewHdr);
+
+ readonly attribute nsMsgKey lowWaterArticleNum;
+ readonly attribute nsMsgKey highWaterArticleNum;
+ attribute nsMsgKey nextPseudoMsgKey; //for undo-redo of move pop->imap
+ readonly attribute nsMsgKey nextFakeOfflineMsgKey; // for saving "fake" offline msg hdrs
+ // for sorting
+ void createCollationKey(in AString sourceString, out unsigned long aCount,
+ [array, size_is(aCount)] out octet aKey);
+ long compareCollationKeys(in unsigned long aLen1,
+ [array, size_is(aLen1)] in octet key1,
+ in unsigned long aLen2,
+ [array, size_is(aLen2)] in octet key2);
+
+ // when creating a view, the default sort order and view flags
+ // use these for the default. (this allows news to override, so that
+ // news can be threaded by default)
+ readonly attribute nsMsgViewFlagsTypeValue defaultViewFlags;
+ readonly attribute nsMsgViewSortTypeValue defaultSortType;
+ readonly attribute nsMsgViewSortOrderValue defaultSortOrder;
+
+ // for msg hdr hash table allocation. controllable by caller to improve folder loading preformance.
+ attribute unsigned long msgHdrCacheSize;
+
+ /**
+ * The list of messages currently in the NEW state.
+ *
+ * If there are no such messages, a null pointer may be returned.
+ * the caller should free when done using free.
+ */
+ void getNewList(out unsigned long count, [array, size_is(count)] out nsMsgKey newKeys);
+
+ // These are used for caching search hits in a db, to speed up saved search folders.
+ nsISimpleEnumerator getCachedHits(in string aSearchFolderUri);
+ void refreshCache(in string aSearchFolderUri, in unsigned long aNumKeys, [array, size_is (aNumKeys)] in nsMsgKey aNewHits,
+ out unsigned long aNumBadHits, [array, size_is(aNumBadHits)] out nsMsgKey aStaleHits);
+ void updateHdrInCache(in string aSearchFolderUri, in nsIMsgDBHdr aHdr, in boolean aAdd);
+ boolean hdrIsInCache(in string aSearchFolderUri, in nsIMsgDBHdr aHdr);
+
+};
+/** @} */
diff --git a/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl b/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl
new file mode 100644
index 000000000..b07beef63
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsIMsgOfflineImapOperation.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+// #include "nsIImapUrl.idl" // for imapMessageFlagsType
+
+typedef unsigned short imapMessageFlagsType;
+
+typedef long nsOfflineImapOperationType;
+
+[scriptable, uuid(b5229a55-22bb-444b-be92-13d719353828)]
+
+interface nsIMsgOfflineImapOperation : nsISupports
+{
+// type of stored imap operations
+ const long kFlagsChanged = 0x1;
+ const long kMsgMoved = 0x2;
+ const long kMsgCopy = 0x4;
+ const long kMoveResult = 0x8;
+ const long kAppendDraft = 0x10;
+ const long kAddedHeader = 0x20;
+ const long kDeletedMsg = 0x40;
+ const long kMsgMarkedDeleted = 0x80;
+ const long kAppendTemplate = 0x100;
+ const long kDeleteAllMsgs = 0x200;
+ const long kAddKeywords = 0x400;
+ const long kRemoveKeywords = 0x800;
+
+ attribute nsOfflineImapOperationType operation;
+ void clearOperation(in nsOfflineImapOperationType operation);
+ attribute nsMsgKey messageKey;
+
+ // for move/copy operations, the msg key of the source msg.
+ attribute nsMsgKey srcMessageKey;
+
+ attribute imapMessageFlagsType flagOperation;
+ attribute imapMessageFlagsType newFlags; // for kFlagsChanged
+ attribute string destinationFolderURI; // for move or copy
+ attribute string sourceFolderURI;
+ void addKeywordToAdd(in string aKeyword);
+ void addKeywordToRemove(in string aKeyword);
+ readonly attribute string keywordsToAdd;
+ readonly attribute string keywordsToRemove;
+ readonly attribute long numberOfCopies;
+ void addMessageCopyOperation(in string destinationBox);
+ string getCopyDestination(in long copyIndex);
+ attribute unsigned long msgSize;
+ attribute boolean playingBack;
+};
+
diff --git a/mailnews/db/msgdb/public/nsINewsDatabase.idl b/mailnews/db/msgdb/public/nsINewsDatabase.idl
new file mode 100644
index 000000000..151a42f01
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsINewsDatabase.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+%{C++
+#include "nsMsgKeySet.h"
+%}
+
+[ptr] native nsMsgKeySetPtr(nsMsgKeySet);
+
+[scriptable, uuid(f700208a-1dd1-11b2-b947-e4e1e4fdf278)]
+
+interface nsINewsDatabase : nsISupports {
+ [noscript] attribute nsMsgKeySetPtr readSet;
+};
diff --git a/mailnews/db/msgdb/public/nsImapMailDatabase.h b/mailnews/db/msgdb/public/nsImapMailDatabase.h
new file mode 100644
index 000000000..b6092c284
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsImapMailDatabase.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+#ifndef _nsImapMailDatabase_H_
+#define _nsImapMailDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMailDatabase.h"
+
+class nsImapMailDatabase : public nsMailDatabase
+{
+public:
+ // OK, it's dumb that this should require a fileSpec, since there is no file
+ // for the folder. This is mainly because we're deriving from nsMailDatabase;
+ // Perhaps we shouldn't...
+ nsImapMailDatabase();
+ virtual ~nsImapMailDatabase();
+
+ NS_IMETHOD StartBatch() override;
+ NS_IMETHOD EndBatch() override;
+ NS_IMETHOD GetSummaryValid(bool *aResult) override;
+ NS_IMETHOD SetSummaryValid(bool valid = true) override;
+ virtual nsresult AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr) override;
+
+ NS_IMETHOD ForceClosed() override;
+ NS_IMETHOD AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify) override;
+ NS_IMETHOD SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ const char *propertyVal) override;
+ NS_IMETHOD SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ uint32_t propertyVal) override;
+ NS_IMETHOD SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr,
+ const char *aProperty,
+ uint64_t aPropertyVal) override;
+ NS_IMETHOD DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys,
+ nsIDBChangeListener *instigator) override;
+ NS_IMETHOD UpdatePendingAttributes(nsIMsgDBHdr* aNewHdr) override;
+
+protected:
+ // IMAP does not set local file flags, override does nothing
+ virtual void UpdateFolderFlag(nsIMsgDBHdr *msgHdr, bool bSet,
+ nsMsgMessageFlagType flag, nsIOutputStream **ppFileStream);
+
+ nsresult GetRowForPendingHdr(nsIMsgDBHdr *pendingHdr, nsIMdbRow **row);
+ nsresult GetAllPendingHdrsTable();
+ mdb_token m_pendingHdrsRowScopeToken;
+ mdb_token m_pendingHdrsTableKindToken;
+ nsCOMPtr<nsIMdbTable> m_mdbAllPendingHdrsTable;
+};
+
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMailDatabase.h b/mailnews/db/msgdb/public/nsMailDatabase.h
new file mode 100644
index 000000000..6a5c6b5c4
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMailDatabase.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#ifndef _nsMailDatabase_H_
+#define _nsMailDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDatabase.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+
+// This is the subclass of nsMsgDatabase that handles local mail messages.
+class nsIOFileStream;
+class nsIFile;
+class nsOfflineImapOperation;
+
+class nsMailDatabase : public nsMsgDatabase
+{
+public:
+ nsMailDatabase();
+ virtual ~nsMailDatabase();
+ NS_IMETHOD ForceClosed() override;
+ NS_IMETHOD DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys,
+ nsIDBChangeListener *instigator) override;
+
+ NS_IMETHOD StartBatch() override;
+ NS_IMETHOD EndBatch() override;
+
+ nsresult Open(nsMsgDBService* aDBService, nsIFile *aSummaryFile, bool create, bool upgrading) override;
+ virtual nsMailDatabase *GetMailDB() {return this;}
+
+ virtual uint32_t GetCurVersion() override {return kMsgDBVersion;}
+
+ NS_IMETHOD GetOfflineOpForKey(nsMsgKey opKey, bool create,
+ nsIMsgOfflineImapOperation **op) override;
+ NS_IMETHOD RemoveOfflineOp(nsIMsgOfflineImapOperation *op) override;
+
+ NS_IMETHOD SetSummaryValid(bool valid) override;
+ NS_IMETHOD GetSummaryValid(bool *valid) override;
+
+ NS_IMETHOD EnumerateOfflineOps(nsISimpleEnumerator **enumerator) override;
+ NS_IMETHOD ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds) override;
+ NS_IMETHOD ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes) override;
+
+ friend class nsMsgOfflineOpEnumerator;
+protected:
+
+ nsresult GetAllOfflineOpsTable(); // get this on demand
+
+ // get the time and date of the mailbox file
+ void GetMailboxModProperties(int64_t *aSize, uint32_t *aDate);
+
+ nsCOMPtr <nsIMdbTable> m_mdbAllOfflineOpsTable;
+ mdb_token m_offlineOpsRowScopeToken;
+ mdb_token m_offlineOpsTableKindToken;
+
+ virtual void SetReparse(bool reparse);
+
+protected:
+
+ bool m_reparse;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMsgDBCID.h b/mailnews/db/msgdb/public/nsMsgDBCID.h
new file mode 100644
index 000000000..557cc24c0
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgDBCID.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+#ifndef nsMsgDBCID_h__
+#define nsMsgDBCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+// 03223c50-1e88-45e8-ba1a-7ce792dc3fc3
+#define NS_MSGDB_SERVICE_CID \
+{ 0x03223c50, 0x1e88, 0x45e8, \
+ { 0xba, 0x1a, 0x7c, 0xe7, 0x92, 0xdc, 0x3f, 0xc3 } }
+
+#define NS_MSGDB_SERVICE_CONTRACTID \
+ "@mozilla.org/msgDatabase/msgDBService;1"
+
+#define NS_MSGDB_CONTRACTID \
+ "@mozilla.org/nsMsgDatabase/msgDB-"
+
+#define NS_MAILBOXDB_CONTRACTID \
+ NS_MSGDB_CONTRACTID"mailbox"
+
+// a86c86ae-e97f-11d2-a506-0060b0fc04b7
+#define NS_MAILDB_CID \
+{ 0xa86c86ae, 0xe97f, 0x11d2, \
+ { 0xa5, 0x06, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_NEWSDB_CONTRACTID \
+ NS_MSGDB_CONTRACTID"news"
+
+// 36414aa0-e980-11d2-a506-0060b0fc04b7
+#define NS_NEWSDB_CID \
+{ 0x36414aa0, 0xe980, 0x11d2, \
+ { 0xa5, 0x06, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_IMAPDB_CONTRACTID \
+ NS_MSGDB_CONTRACTID"imap"
+
+// 9e4b07ee-e980-11d2-a506-0060b0fc04b7
+#define NS_IMAPDB_CID \
+{ 0x9e4b07ee, 0xe980, 0x11d2, \
+ { 0xa5, 0x06, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 } }
+
+#define NS_MSG_RETENTIONSETTINGS_CID \
+{ 0x1bd976d6, 0xdf44, 0x11d4, \
+ {0xa5, 0xb6, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7} }
+
+#define NS_MSG_RETENTIONSETTINGS_CONTRACTID \
+ "@mozilla.org/msgDatabase/retentionSettings;1"
+
+// 4e3dae5a-157a-11d5-a5c0-0060b0fc04b7
+#define NS_MSG_DOWNLOADSETTINGS_CID \
+{ 0x4e3dae5a, 0x157a, 0x11d5, \
+ {0xa5, 0xc0, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7} }
+
+#define NS_MSG_DOWNLOADSETTINGS_CONTRACTID \
+ "@mozilla.org/msgDatabase/downloadSettings;1"
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMsgDatabase.h b/mailnews/db/msgdb/public/nsMsgDatabase.h
new file mode 100644
index 000000000..bb011e85c
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgDatabase.h
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#ifndef _nsMsgDatabase_H_
+#define _nsMsgDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgHdr.h"
+#include "nsStringGlue.h"
+#include "nsAutoPtr.h"
+#include "nsIDBChangeListener.h"
+#include "nsIDBChangeAnnouncer.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMutableArray.h"
+#include "nsDBFolderInfo.h"
+#include "nsICollation.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMimeConverter.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "PLDHashTable.h"
+#include "nsTArray.h"
+#include "nsTObserverArray.h"
+class ListContext;
+class nsMsgKeySet;
+class nsMsgThread;
+class nsMsgDatabase;
+class nsIMsgThread;
+class nsIDBFolderInfo;
+
+const int32_t kMsgDBVersion = 1;
+
+// Hopefully we're not opening up lots of databases at the same time, however
+// this will give us a buffer before we need to start reallocating the cache
+// array.
+const uint32_t kInitialMsgDBCacheSize = 20;
+
+class nsMsgDBService final : public nsIMsgDBService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDBSERVICE
+
+ nsMsgDBService();
+
+ void AddToCache(nsMsgDatabase* pMessageDB);
+ void DumpCache();
+ void EnsureCached(nsMsgDatabase* pMessageDB)
+ {
+ if (!m_dbCache.Contains(pMessageDB))
+ m_dbCache.AppendElement(pMessageDB);
+ }
+ void RemoveFromCache(nsMsgDatabase* pMessageDB)
+ {
+ m_dbCache.RemoveElement(pMessageDB);
+ }
+
+protected:
+ ~nsMsgDBService();
+ void HookupPendingListeners(nsIMsgDatabase *db, nsIMsgFolder *folder);
+ void FinishDBOpen(nsIMsgFolder *aFolder, nsMsgDatabase *aMsgDB);
+ nsMsgDatabase* FindInCache(nsIFile *dbName);
+
+ nsCOMArray <nsIMsgFolder> m_foldersPendingListeners;
+ nsCOMArray <nsIDBChangeListener> m_pendingListeners;
+ AutoTArray<nsMsgDatabase*, kInitialMsgDBCacheSize> m_dbCache;
+};
+
+class nsMsgDBEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgDBEnumerator methods:
+ typedef nsresult (*nsMsgDBEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
+
+ nsMsgDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
+ nsMsgDBEnumeratorFilter filter, void* closure,
+ bool iterateForwards = true);
+ void Clear();
+
+ nsresult GetRowCursor();
+ virtual nsresult PrefetchNext();
+ RefPtr<nsMsgDatabase> mDB;
+ nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
+ mdb_pos mRowPos;
+ nsCOMPtr<nsIMsgDBHdr> mResultHdr;
+ bool mDone;
+ bool mNextPrefetched;
+ bool mIterateForwards;
+ nsMsgDBEnumeratorFilter mFilter;
+ nsCOMPtr <nsIMdbTable> mTable;
+ void* mClosure;
+ // This is used when the caller wants to limit how many headers the
+ // enumerator looks at in any given time slice.
+ mdb_pos mStopPos;
+
+protected:
+ virtual ~nsMsgDBEnumerator();
+};
+
+class nsMsgFilteredDBEnumerator : public nsMsgDBEnumerator
+{
+public:
+ nsMsgFilteredDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
+ bool reverse, nsIArray *searchTerms);
+ virtual ~nsMsgFilteredDBEnumerator();
+ nsresult InitSearchSession(nsIArray *searchTerms, nsIMsgFolder *folder);
+
+protected:
+ virtual nsresult PrefetchNext() override;
+
+ nsCOMPtr <nsIMsgSearchSession> m_searchSession;
+
+};
+
+namespace mozilla {
+namespace mailnews {
+class MsgDBReporter;
+}
+}
+
+class nsMsgDatabase : public nsIMsgDatabase
+{
+public:
+ friend class nsMsgDBService;
+ friend class nsMsgPropertyEnumerator; // accesses m_mdbEnv and m_mdbStore
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDBCHANGEANNOUNCER
+ NS_DECL_NSIMSGDATABASE
+
+ /**
+ * Opens a database folder.
+ *
+ * @param aFolderName The name of the folder to create.
+ * @param aCreate Whether or not the file should be created.
+ * @param aLeaveInvalidDB Set to true if you do not want the database to be
+ * deleted if it is invalid.
+ * @exception NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
+ * The file could not be created.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE
+ * The database is present (and was opened), but the
+ * summary file is out of date.
+ * @exception NS_MSG_ERROR_FOLDER_SUMMARY_MISSING
+ * The database is present (and was opened), but the
+ * summary file is missing.
+ */
+ virtual nsresult Open(nsMsgDBService *aDBService, nsIFile *aFolderName,
+ bool aCreate, bool aLeaveInvalidDB);
+ virtual nsresult IsHeaderRead(nsIMsgDBHdr *hdr, bool *pRead);
+ virtual nsresult MarkHdrReadInDB(nsIMsgDBHdr *msgHdr, bool bRead,
+ nsIDBChangeListener *instigator);
+ nsresult OpenInternal(nsMsgDBService *aDBService, nsIFile *aFolderName,
+ bool aCreate, bool aLeaveInvalidDB, bool sync);
+ nsresult CheckForErrors(nsresult err, bool sync, nsMsgDBService *aDBService, nsIFile *summaryFile);
+ virtual nsresult OpenMDB(const char *dbName, bool create, bool sync);
+ virtual nsresult CloseMDB(bool commit);
+ virtual nsresult CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key, nsIMsgDBHdr **result);
+ virtual nsresult GetThreadForMsgKey(nsMsgKey msgKey, nsIMsgThread **result);
+ virtual nsresult EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, uint32_t *pFlag);
+ nsresult GetSearchResultsTable(const char *searchFolderUri, bool createIfMissing, nsIMdbTable **table);
+
+ // this might just be for debugging - we'll see.
+ nsresult ListAllThreads(nsTArray<nsMsgKey> *threadIds);
+ //////////////////////////////////////////////////////////////////////////////
+ // nsMsgDatabase methods:
+ nsMsgDatabase();
+
+ void GetMDBFactory(nsIMdbFactory ** aMdbFactory);
+ nsIMdbEnv *GetEnv() {return m_mdbEnv;}
+ nsIMdbStore *GetStore() {return m_mdbStore;}
+ virtual uint32_t GetCurVersion();
+ nsresult GetCollationKeyGenerator();
+ nsIMimeConverter * GetMimeConverter();
+
+ nsresult GetTableCreateIfMissing(const char *scope, const char *kind, nsIMdbTable **table,
+ mdb_token &scopeToken, mdb_token &kindToken);
+
+ //helper function to fill in nsStrings from hdr row cell contents.
+ nsresult RowCellColumnTonsString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr);
+ nsresult RowCellColumnToUInt32(nsIMdbRow *row, mdb_token columnToken, uint32_t *uint32Result, uint32_t defaultValue = 0);
+ nsresult RowCellColumnToUInt32(nsIMdbRow *row, mdb_token columnToken, uint32_t &uint32Result, uint32_t defaultValue = 0);
+ nsresult RowCellColumnToUInt64(nsIMdbRow *row, mdb_token columnToken, uint64_t *uint64Result, uint64_t defaultValue = 0);
+ nsresult RowCellColumnToMime2DecodedString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr);
+ nsresult RowCellColumnToCollationKey(nsIMdbRow *row, mdb_token columnToken, uint8_t **result, uint32_t *len);
+ nsresult RowCellColumnToConstCharPtr(nsIMdbRow *row, mdb_token columnToken, const char **ptr);
+ nsresult RowCellColumnToAddressCollationKey(nsIMdbRow *row, mdb_token colToken, uint8_t **result, uint32_t *len);
+
+ nsresult GetEffectiveCharset(nsIMdbRow *row, nsACString &resultCharset);
+
+ // these methods take the property name as a string, not a token.
+ // they should be used when the properties aren't accessed a lot
+ nsresult GetProperty(nsIMdbRow *row, const char *propertyName, char **result);
+ nsresult SetProperty(nsIMdbRow *row, const char *propertyName, const char *propertyVal);
+ nsresult GetPropertyAsNSString(nsIMdbRow *row, const char *propertyName, nsAString &result);
+ nsresult SetPropertyFromNSString(nsIMdbRow *row, const char *propertyName, const nsAString &propertyVal);
+ nsresult GetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t *result, uint32_t defaultValue = 0);
+ nsresult GetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t *result, uint64_t defaultValue = 0);
+ nsresult SetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t propertyVal);
+ nsresult SetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t propertyVal);
+ nsresult GetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool *result, bool defaultValue = false);
+ nsresult SetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool propertyVal);
+ // helper function for once we have the token.
+ nsresult SetNSStringPropertyWithToken(nsIMdbRow *row, mdb_token aProperty, const nsAString &propertyStr);
+
+ // helper functions to put values in cells for the passed-in row
+ nsresult UInt32ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint32_t value);
+ nsresult CharPtrToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, const char *charPtr);
+ nsresult RowCellColumnToCharPtr(nsIMdbRow *row, mdb_token columnToken, char **result);
+ nsresult UInt64ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint64_t value);
+
+ // helper functions to copy an nsString to a yarn, int32 to yarn, and vice versa.
+ static struct mdbYarn *nsStringToYarn(struct mdbYarn *yarn, const nsAString &str);
+ static struct mdbYarn *UInt32ToYarn(struct mdbYarn *yarn, uint32_t i);
+ static struct mdbYarn *UInt64ToYarn(struct mdbYarn *yarn, uint64_t i);
+ static void YarnTonsString(struct mdbYarn *yarn, nsAString &str);
+ static void YarnTonsCString(struct mdbYarn *yarn, nsACString &str);
+ static void YarnToUInt32(struct mdbYarn *yarn, uint32_t *i);
+ static void YarnToUInt64(struct mdbYarn *yarn, uint64_t *i);
+
+#ifdef DEBUG
+ virtual nsresult DumpContents();
+ nsresult DumpThread(nsMsgKey threadId);
+ nsresult DumpMsgChildren(nsIMsgDBHdr *msgHdr);
+#endif
+
+ friend class nsMsgHdr; // use this to get access to cached tokens for hdr fields
+ friend class nsMsgThread; // use this to get access to cached tokens for hdr fields
+ friend class nsMsgDBEnumerator;
+ friend class nsMsgDBThreadEnumerator;
+protected:
+ virtual ~nsMsgDatabase();
+
+ // prefs stuff - in future, we might want to cache the prefs interface
+ nsresult GetBoolPref(const char *prefName, bool *result);
+ nsresult GetIntPref(const char *prefName, int32_t *result);
+ virtual void GetGlobalPrefs();
+ // retrieval methods
+ nsIMsgThread * GetThreadForReference(nsCString &msgID, nsIMsgDBHdr **pMsgHdr);
+ nsIMsgThread * GetThreadForSubject(nsCString &subject);
+ nsIMsgThread * GetThreadForMessageId(nsCString &msgId);
+ nsIMsgThread * GetThreadForThreadId(nsMsgKey threadId);
+ nsMsgHdr * GetMsgHdrForReference(nsCString &reference);
+ nsIMsgDBHdr * GetMsgHdrForSubject(nsCString &subject);
+ // threading interfaces
+ virtual nsresult CreateNewThread(nsMsgKey key, const char *subject, nsMsgThread **newThread);
+ virtual bool ThreadBySubjectWithoutRe();
+ virtual bool UseStrictThreading();
+ virtual bool UseCorrectThreading();
+ virtual nsresult ThreadNewHdr(nsMsgHdr* hdr, bool &newThread);
+ virtual nsresult AddNewThread(nsMsgHdr *msgHdr);
+ virtual nsresult AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *pMsgHdr, bool threadInThread);
+
+ static PRTime gLastUseTime; // global last use time
+ PRTime m_lastUseTime; // last use time for this db
+ // inline to make instrumentation as cheap as possible
+ inline void RememberLastUseTime() {gLastUseTime = m_lastUseTime = PR_Now();}
+
+ bool MatchDbName(nsIFile *dbName); // returns TRUE if they match
+
+ // Flag handling routines
+ virtual nsresult SetKeyFlag(nsMsgKey key, bool set, uint32_t flag,
+ nsIDBChangeListener *instigator = NULL);
+ virtual nsresult SetMsgHdrFlag(nsIMsgDBHdr *msgHdr, bool set, uint32_t flag,
+ nsIDBChangeListener *instigator);
+
+ virtual bool SetHdrFlag(nsIMsgDBHdr *, bool bSet, nsMsgMessageFlagType flag);
+ virtual bool SetHdrReadFlag(nsIMsgDBHdr *, bool pRead);
+ virtual uint32_t GetStatusFlags(nsIMsgDBHdr *msgHdr, uint32_t origFlags);
+ // helper function which doesn't involve thread object
+
+ virtual nsresult RemoveHeaderFromDB(nsMsgHdr *msgHdr);
+ virtual nsresult RemoveHeaderFromThread(nsMsgHdr *msgHdr);
+ virtual nsresult AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr);
+
+ nsCOMPtr <nsICollation> m_collationKeyGenerator;
+ nsCOMPtr <nsIMimeConverter> m_mimeConverter;
+ nsCOMPtr <nsIMsgRetentionSettings> m_retentionSettings;
+ nsCOMPtr <nsIMsgDownloadSettings> m_downloadSettings;
+
+ nsresult PurgeMessagesOlderThan(uint32_t daysToKeepHdrs,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete);
+ nsresult PurgeExcessMessages(uint32_t numHeadersToKeep,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete);
+
+ // mdb bookkeeping stuff
+ virtual nsresult InitExistingDB();
+ virtual nsresult InitNewDB();
+ virtual nsresult InitMDBInfo();
+
+ nsCOMPtr <nsIMsgFolder> m_folder;
+ nsDBFolderInfo *m_dbFolderInfo;
+ nsMsgKey m_nextPseudoMsgKey;
+ nsIMdbEnv *m_mdbEnv; // to be used in all the db calls.
+ nsIMdbStore *m_mdbStore;
+ nsIMdbTable *m_mdbAllMsgHeadersTable;
+ nsIMdbTable *m_mdbAllThreadsTable;
+
+ // Used for asynchronous db opens. If non-null, we're still opening
+ // the underlying mork database. If null, the db has been completely opened.
+ nsCOMPtr<nsIMdbThumb> m_thumb;
+ // used to remember the args to Open for async open.
+ bool m_create;
+ bool m_leaveInvalidDB;
+
+ nsCString m_dbName;
+ nsTArray<nsMsgKey> m_newSet; // new messages since last open.
+ bool m_mdbTokensInitialized;
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> > m_ChangeListeners;
+ mdb_token m_hdrRowScopeToken;
+ mdb_token m_threadRowScopeToken;
+ mdb_token m_hdrTableKindToken;
+ mdb_token m_threadTableKindToken;
+ mdb_token m_allThreadsTableKindToken;
+ mdb_token m_subjectColumnToken;
+ mdb_token m_senderColumnToken;
+ mdb_token m_messageIdColumnToken;
+ mdb_token m_referencesColumnToken;
+ mdb_token m_recipientsColumnToken;
+ mdb_token m_dateColumnToken;
+ mdb_token m_messageSizeColumnToken;
+ mdb_token m_flagsColumnToken;
+ mdb_token m_priorityColumnToken;
+ mdb_token m_labelColumnToken;
+ mdb_token m_statusOffsetColumnToken;
+ mdb_token m_numLinesColumnToken;
+ mdb_token m_ccListColumnToken;
+ mdb_token m_bccListColumnToken;
+ mdb_token m_threadFlagsColumnToken;
+ mdb_token m_threadIdColumnToken;
+ mdb_token m_threadChildrenColumnToken;
+ mdb_token m_threadUnreadChildrenColumnToken;
+ mdb_token m_messageThreadIdColumnToken;
+ mdb_token m_threadSubjectColumnToken;
+ mdb_token m_messageCharSetColumnToken;
+ mdb_token m_threadParentColumnToken;
+ mdb_token m_threadRootKeyColumnToken;
+ mdb_token m_threadNewestMsgDateColumnToken;
+ mdb_token m_offlineMsgOffsetColumnToken;
+ mdb_token m_offlineMessageSizeColumnToken;
+
+ // header caching stuff - MRU headers, keeps them around in memory
+ nsresult AddHdrToCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+ nsresult ClearHdrCache(bool reInit);
+ nsresult RemoveHdrFromCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+ // all headers currently instantiated, doesn't hold refs
+ // these get added when msg hdrs get constructed, and removed when they get destroyed.
+ nsresult GetHdrFromUseCache(nsMsgKey key, nsIMsgDBHdr* *result);
+ nsresult AddHdrToUseCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+ nsresult ClearUseHdrCache();
+ nsresult RemoveHdrFromUseCache(nsIMsgDBHdr *hdr, nsMsgKey key);
+
+ // not-reference holding array of threads we've handed out.
+ // If a db goes away, it will clean up the outstanding threads.
+ // We use an nsTArray because we don't expect to ever have very many
+ // of these, rarely more than 5.
+ nsTArray<nsMsgThread *> m_threads;
+ // Clear outstanding thread objects
+ void ClearThreads();
+ nsMsgThread *FindExistingThread(nsMsgKey threadId);
+
+ mdb_pos FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert);
+
+ void ClearCachedObjects(bool dbGoingAway);
+ void ClearEnumerators();
+ // all instantiated headers, but doesn't hold refs.
+ PLDHashTable *m_headersInUse;
+ static PLDHashNumber HashKey(const void* aKey);
+ static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey);
+ static void MoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, PLDHashEntryHdr* aTo);
+ static void ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry);
+ static PLDHashTableOps gMsgDBHashTableOps;
+ struct MsgHdrHashElement : public PLDHashEntryHdr {
+ nsMsgKey mKey;
+ nsIMsgDBHdr *mHdr;
+ };
+ PLDHashTable *m_cachedHeaders;
+ bool m_bCacheHeaders;
+ nsMsgKey m_cachedThreadId;
+ nsCOMPtr <nsIMsgThread> m_cachedThread;
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+
+ // Message reference hash table
+ static PLDHashTableOps gRefHashTableOps;
+ struct RefHashElement : public PLDHashEntryHdr {
+ const char *mRef; // Hash entry key, must come first
+ nsMsgKey mThreadId;
+ uint32_t mCount;
+ };
+ PLDHashTable *m_msgReferences;
+ nsresult GetRefFromHash(nsCString &reference, nsMsgKey *threadId);
+ nsresult AddRefToHash(nsCString &reference, nsMsgKey threadId);
+ nsresult AddMsgRefsToHash(nsIMsgDBHdr *msgHdr);
+ nsresult RemoveRefFromHash(nsCString &reference);
+ nsresult RemoveMsgRefsFromHash(nsIMsgDBHdr *msgHdr);
+ nsresult InitRefHash();
+
+ // not-reference holding array of enumerators we've handed out.
+ // If a db goes away, it will clean up the outstanding enumerators.
+ nsTArray<nsMsgDBEnumerator *> m_enumerators;
+
+ // Memory reporter details
+public:
+ static size_t HeaderHashSizeOf(PLDHashEntryHdr *hdr,
+ mozilla::MallocSizeOf aMallocSizeOf,
+ void *arg);
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+private:
+ uint32_t m_cacheSize;
+ RefPtr<mozilla::mailnews::MsgDBReporter> mMemReporter;
+};
+
+class nsMsgRetentionSettings : public nsIMsgRetentionSettings
+{
+public:
+ nsMsgRetentionSettings();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGRETENTIONSETTINGS
+protected:
+ virtual ~nsMsgRetentionSettings();
+ nsMsgRetainByPreference m_retainByPreference;
+ uint32_t m_daysToKeepHdrs;
+ uint32_t m_numHeadersToKeep;
+ bool m_useServerDefaults;
+ bool m_cleanupBodiesByDays;
+ uint32_t m_daysToKeepBodies;
+ bool m_applyToFlaggedMessages;
+};
+
+class nsMsgDownloadSettings : public nsIMsgDownloadSettings
+{
+public:
+ nsMsgDownloadSettings();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDOWNLOADSETTINGS
+protected:
+ virtual ~nsMsgDownloadSettings();
+ bool m_useServerDefaults;
+ bool m_downloadUnreadOnly;
+ bool m_downloadByDate;
+ int32_t m_ageLimitOfMsgsToDownload;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/public/nsMsgHdr.h b/mailnews/db/msgdb/public/nsMsgHdr.h
new file mode 100644
index 000000000..6d23e7b49
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgHdr.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef _nsMsgHdr_H
+#define _nsMsgHdr_H
+
+#include "mozilla/MemoryReporting.h"
+#include "nsIMsgHdr.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "mdb.h"
+#include "nsTArray.h"
+
+class nsMsgDatabase;
+class nsCString;
+class nsIMsgThread;
+
+class nsMsgHdr : public nsIMsgDBHdr {
+public:
+ NS_DECL_NSIMSGDBHDR
+ friend class nsMsgDatabase;
+ friend class nsMsgPropertyEnumerator; // accesses m_mdb
+ ////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////
+ // nsMsgHdr methods:
+ nsMsgHdr(nsMsgDatabase *db, nsIMdbRow *dbRow);
+
+ virtual nsresult GetRawFlags(uint32_t *result);
+ void Init();
+ virtual nsresult InitCachedValues();
+ virtual nsresult InitFlags();
+ void ClearCachedValues() {m_initedValues = 0;}
+
+ NS_DECL_ISUPPORTS
+
+ nsIMdbRow *GetMDBRow() {return m_mdbRow;}
+ bool IsParentOf(nsIMsgDBHdr *possibleChild);
+ bool IsAncestorOf(nsIMsgDBHdr *possibleChild);
+ bool IsAncestorKilled(uint32_t ancestorsToCheck);
+ void ReparentInThread(nsIMsgThread *thread);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOfFun) const
+ {
+ return m_references.ShallowSizeOfExcludingThis(aMallocSizeOfFun);
+ }
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfFun) const
+ {
+ return aMallocSizeOfFun(this) + SizeOfExcludingThis(aMallocSizeOfFun);
+ }
+
+protected:
+ virtual ~nsMsgHdr();
+ nsresult SetStringColumn(const char *str, mdb_token token);
+ nsresult SetUInt32Column(uint32_t value, mdb_token token);
+ nsresult GetUInt32Column(mdb_token token, uint32_t *pvalue, uint32_t defaultValue = 0);
+ nsresult SetUInt64Column(uint64_t value, mdb_token token);
+ nsresult GetUInt64Column(mdb_token token, uint64_t *pvalue, uint64_t defaultValue = 0);
+
+ // reference and threading stuff.
+ nsresult ParseReferences(const char *references);
+ const char* GetNextReference(const char *startNextRef, nsCString &reference,
+ bool acceptNonDelimitedReferences);
+
+ nsMsgKey m_threadId;
+ nsMsgKey m_messageKey; //news: article number, mail mbox offset, imap uid...
+ nsMsgKey m_threadParent; // message this is a reply to, in thread.
+ PRTime m_date;
+ uint32_t m_messageSize; // lines for news articles, bytes for mail messages
+ uint32_t m_statusOffset; // offset in a local mail message of the mozilla status hdr
+ uint32_t m_flags;
+ // avoid parsing references every time we want one
+ nsTArray<nsCString> m_references;
+ nsMsgPriorityValue m_priority;
+
+ // nsMsgHdrs will have to know what db and row they belong to, since they are really
+ // just a wrapper around the msg row in the mdb. This could cause problems,
+ // though I hope not.
+ nsMsgDatabase *m_mdb;
+ nsIMdbRow *m_mdbRow;
+ uint32_t m_initedValues;
+};
+
+#endif
+
diff --git a/mailnews/db/msgdb/public/nsMsgThread.h b/mailnews/db/msgdb/public/nsMsgThread.h
new file mode 100644
index 000000000..62d303bc8
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsMsgThread.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+#ifndef _nsMsgThread_H
+#define _nsMsgThread_H
+
+#include "nsAutoPtr.h"
+#include "nsIMsgThread.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "mdb.h"
+
+class nsIMdbTable;
+class nsIMsgDBHdr;
+class nsMsgDatabase;
+
+class nsMsgThread : public nsIMsgThread {
+public:
+ nsMsgThread();
+ nsMsgThread(nsMsgDatabase *db, nsIMdbTable *table);
+
+ friend class nsMsgThreadEnumerator;
+ friend class nsMsgDatabase;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTHREAD
+
+ RefPtr<nsMsgDatabase> m_mdbDB;
+
+protected:
+ virtual ~nsMsgThread();
+
+ void Init();
+ void Clear();
+ virtual nsresult InitCachedValues();
+ nsresult ChangeChildCount(int32_t delta);
+ nsresult ChangeUnreadChildCount(int32_t delta);
+ nsresult RemoveChild(nsMsgKey msgKey);
+ nsresult SetThreadRootKey(nsMsgKey threadRootKey);
+ nsresult GetChildHdrForKey(nsMsgKey desiredKey,
+ nsIMsgDBHdr **result, int32_t *resultIndex);
+ nsresult RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer);
+ nsresult ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer);
+
+ nsresult ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer *announcer);
+ nsresult ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey);
+
+ nsMsgKey m_threadKey;
+ uint32_t m_numChildren;
+ uint32_t m_numUnreadChildren;
+ uint32_t m_flags;
+ nsCOMPtr<nsIMdbTable> m_mdbTable;
+ nsCOMPtr<nsIMdbRow> m_metaRow;
+ bool m_cachedValuesInitialized;
+ nsMsgKey m_threadRootKey;
+ uint32_t m_newestMsgDate;
+};
+
+#endif
+
diff --git a/mailnews/db/msgdb/public/nsNewsDatabase.h b/mailnews/db/msgdb/public/nsNewsDatabase.h
new file mode 100644
index 000000000..33e225913
--- /dev/null
+++ b/mailnews/db/msgdb/public/nsNewsDatabase.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+#ifndef _nsNewsDatabase_H_
+#define _nsNewsDatabase_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDatabase.h"
+#include "nsINewsDatabase.h"
+#include "nsTArray.h"
+
+class nsIDBChangeListener;
+class MSG_RetrieveArtInfo;
+class MSG_PurgeInfo;
+// news group database
+
+class nsNewsDatabase : public nsMsgDatabase , public nsINewsDatabase
+{
+public:
+ nsNewsDatabase();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSINEWSDATABASE
+
+ NS_IMETHOD Close(bool forceCommit) override;
+ NS_IMETHOD ForceClosed() override;
+ NS_IMETHOD Commit(nsMsgDBCommit commitType) override;
+ virtual uint32_t GetCurVersion() override;
+
+ // methods to get and set docsets for ids.
+ NS_IMETHOD IsRead(nsMsgKey key, bool *pRead) override;
+ virtual nsresult IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead) override;
+
+ NS_IMETHOD GetHighWaterArticleNum(nsMsgKey *key) override;
+ NS_IMETHOD GetLowWaterArticleNum(nsMsgKey *key) override;
+ NS_IMETHOD MarkAllRead(uint32_t *aNumMarked, nsMsgKey **thoseMarked) override;
+
+ virtual nsresult ExpireUpTo(nsMsgKey expireKey);
+ virtual nsresult ExpireRange(nsMsgKey startRange, nsMsgKey endRange);
+
+ virtual bool SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead) override;
+
+ virtual nsresult AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr) override;
+ nsresult SyncWithReadSet();
+
+ NS_IMETHOD GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags) override;
+ NS_IMETHOD GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType) override;
+ NS_IMETHOD GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder) override;
+
+protected:
+ virtual ~nsNewsDatabase();
+ // this is owned by the nsNewsFolder, which lives longer than the db.
+ nsMsgKeySet *m_readSet;
+};
+
+#endif
diff --git a/mailnews/db/msgdb/src/moz.build b/mailnews/db/msgdb/src/moz.build
new file mode 100644
index 000000000..07f537462
--- /dev/null
+++ b/mailnews/db/msgdb/src/moz.build
@@ -0,0 +1,18 @@
+# vim: set filetype=python:
+# 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/.
+
+SOURCES += [
+ 'nsDBFolderInfo.cpp',
+ 'nsImapMailDatabase.cpp',
+ 'nsMailDatabase.cpp',
+ 'nsMsgDatabase.cpp',
+ 'nsMsgHdr.cpp',
+ 'nsMsgOfflineImapOperation.cpp',
+ 'nsMsgThread.cpp',
+ 'nsNewsDatabase.cpp',
+]
+
+FINAL_LIBRARY = 'mail'
+
diff --git a/mailnews/db/msgdb/src/nsDBFolderInfo.cpp b/mailnews/db/msgdb/src/nsDBFolderInfo.cpp
new file mode 100644
index 000000000..0260738d0
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsDBFolderInfo.cpp
@@ -0,0 +1,977 @@
+/* -*- 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 "nsDBFolderInfo.h"
+#include "nsMsgDatabase.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIMsgDBView.h"
+#include "nsServiceManagerUtils.h"
+#include "nsImapCore.h"
+#include "mozilla/Services.h"
+
+static const char *kDBFolderInfoScope = "ns:msg:db:row:scope:dbfolderinfo:all";
+static const char *kDBFolderInfoTableKind = "ns:msg:db:table:kind:dbfolderinfo";
+
+struct mdbOid gDBFolderInfoOID;
+
+static const char * kNumMessagesColumnName ="numMsgs";
+// have to leave this as numNewMsgs even though it's numUnread Msgs
+static const char * kNumUnreadMessagesColumnName = "numNewMsgs";
+static const char * kFlagsColumnName = "flags";
+static const char * kFolderSizeColumnName = "folderSize";
+static const char * kExpungedBytesColumnName = "expungedBytes";
+static const char * kFolderDateColumnName = "folderDate";
+static const char * kHighWaterMessageKeyColumnName = "highWaterKey";
+
+static const char * kImapUidValidityColumnName = "UIDValidity";
+static const char * kTotalPendingMessagesColumnName = "totPendingMsgs";
+static const char * kUnreadPendingMessagesColumnName = "unreadPendingMsgs";
+static const char * kMailboxNameColumnName = "mailboxName";
+static const char * kKnownArtsSetColumnName = "knownArts";
+static const char * kExpiredMarkColumnName = "expiredMark";
+static const char * kVersionColumnName = "version";
+static const char * kCharacterSetColumnName = "charSet";
+static const char * kCharacterSetOverrideColumnName = "charSetOverride";
+static const char * kLocaleColumnName = "locale";
+
+
+#define kMAILNEWS_VIEW_DEFAULT_CHARSET "mailnews.view_default_charset"
+#define kMAILNEWS_DEFAULT_CHARSET_OVERRIDE "mailnews.force_charset_override"
+static nsCString* gDefaultCharacterSet = nullptr;
+static bool gDefaultCharacterOverride;
+static nsIObserver *gFolderCharsetObserver = nullptr;
+
+// observer for charset related preference notification
+class nsFolderCharsetObserver : public nsIObserver {
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsFolderCharsetObserver() { }
+private:
+ virtual ~nsFolderCharsetObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(nsFolderCharsetObserver, nsIObserver)
+
+NS_IMETHODIMP nsFolderCharsetObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID))
+ {
+ nsDependentString prefName(someData);
+
+ if (prefName.EqualsLiteral(kMAILNEWS_VIEW_DEFAULT_CHARSET))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ rv = prefBranch->GetComplexValue(kMAILNEWS_VIEW_DEFAULT_CHARSET,
+ NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString ucsval;
+ pls->ToString(getter_Copies(ucsval));
+ if (!ucsval.IsEmpty())
+ {
+ if (gDefaultCharacterSet)
+ CopyUTF16toUTF8(ucsval, *gDefaultCharacterSet);
+ }
+ }
+ }
+ else if (prefName.EqualsLiteral(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE))
+ {
+ rv = prefBranch->GetBoolPref(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, &gDefaultCharacterOverride);
+ }
+ }
+ else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ {
+ rv = prefBranch->RemoveObserver(kMAILNEWS_VIEW_DEFAULT_CHARSET, this);
+ rv = prefBranch->RemoveObserver(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, this);
+ NS_IF_RELEASE(gFolderCharsetObserver);
+ delete gDefaultCharacterSet;
+ gDefaultCharacterSet = nullptr;
+ }
+ return rv;
+}
+
+
+NS_IMPL_ADDREF(nsDBFolderInfo)
+NS_IMPL_RELEASE(nsDBFolderInfo)
+
+NS_IMETHODIMP
+nsDBFolderInfo::QueryInterface(REFNSIID iid, void** result)
+{
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ *result = nullptr;
+ if(iid.Equals(NS_GET_IID(nsIDBFolderInfo)) ||
+ iid.Equals(NS_GET_IID(nsISupports)))
+ {
+ *result = static_cast<nsIDBFolderInfo*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+
+nsDBFolderInfo::nsDBFolderInfo(nsMsgDatabase *mdb)
+ : m_flags(0),
+ m_expiredMark(0),
+ m_expiredMarkColumnToken(0)
+{
+ m_mdbTable = NULL;
+ m_mdbRow = NULL;
+ m_version = 1; // for upgrading...
+ m_IMAPHierarchySeparator = 0; // imap path separator
+ // mail only (for now)
+ m_folderSize = 0;
+ m_folderDate = 0;
+ m_expungedBytes = 0; // sum of size of deleted messages in folder
+ m_highWaterMessageKey = 0;
+
+ m_numUnreadMessages = 0;
+ m_numMessages = 0;
+ // IMAP only
+ m_ImapUidValidity = kUidUnknown;
+ m_totalPendingMessages =0;
+ m_unreadPendingMessages = 0;
+
+ m_mdbTokensInitialized = false;
+ m_charSetOverride = false;
+
+ if (!gFolderCharsetObserver)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ rv = prefBranch->GetComplexValue(kMAILNEWS_VIEW_DEFAULT_CHARSET,
+ NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString ucsval;
+ pls->ToString(getter_Copies(ucsval));
+ if (!ucsval.IsEmpty())
+ {
+ if (!gDefaultCharacterSet)
+ gDefaultCharacterSet = new nsCString;
+
+ if (gDefaultCharacterSet)
+ CopyUTF16toUTF8(ucsval, *gDefaultCharacterSet);
+ }
+ }
+ rv = prefBranch->GetBoolPref(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, &gDefaultCharacterOverride);
+
+ gFolderCharsetObserver = new nsFolderCharsetObserver();
+ NS_ASSERTION(gFolderCharsetObserver, "failed to create observer");
+
+ // register prefs callbacks
+ if (gFolderCharsetObserver)
+ {
+ NS_ADDREF(gFolderCharsetObserver);
+ rv = prefBranch->AddObserver(kMAILNEWS_VIEW_DEFAULT_CHARSET, gFolderCharsetObserver, false);
+ rv = prefBranch->AddObserver(kMAILNEWS_DEFAULT_CHARSET_OVERRIDE, gFolderCharsetObserver, false);
+
+ // also register for shutdown
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ {
+ rv = observerService->AddObserver(gFolderCharsetObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+ }
+ }
+ }
+
+ m_mdb = mdb;
+ if (mdb)
+ {
+ nsresult err;
+
+ // mdb->AddRef();
+ err = m_mdb->GetStore()->StringToToken(mdb->GetEnv(), kDBFolderInfoScope, &m_rowScopeToken);
+ if (NS_SUCCEEDED(err))
+ {
+ err = m_mdb->GetStore()->StringToToken(mdb->GetEnv(), kDBFolderInfoTableKind, &m_tableKindToken);
+ if (NS_SUCCEEDED(err))
+ {
+ gDBFolderInfoOID.mOid_Scope = m_rowScopeToken;
+ gDBFolderInfoOID.mOid_Id = 1;
+ }
+ }
+ InitMDBInfo();
+ }
+}
+
+nsDBFolderInfo::~nsDBFolderInfo()
+{
+ // nsMsgDatabase strictly owns nsDBFolderInfo, so don't ref-count db.
+ ReleaseExternalReferences();
+}
+
+// Release any objects we're holding onto. This needs to be safe
+// to call multiple times.
+void nsDBFolderInfo::ReleaseExternalReferences()
+{
+ if (m_mdb)
+ {
+ if (m_mdbTable)
+ {
+ NS_RELEASE(m_mdbTable);
+ m_mdbTable = nullptr;
+ }
+ if (m_mdbRow)
+ {
+ NS_RELEASE(m_mdbRow);
+ m_mdbRow = nullptr;
+ }
+ m_mdb = nullptr;
+ }
+}
+
+// this routine sets up a new db to know about the dbFolderInfo stuff...
+nsresult nsDBFolderInfo::AddToNewMDB()
+{
+ nsresult ret = NS_OK;
+ if (m_mdb && m_mdb->GetStore())
+ {
+ nsIMdbStore *store = m_mdb->GetStore();
+ // create the unique table for the dbFolderInfo.
+ nsresult err = store->NewTable(m_mdb->GetEnv(), m_rowScopeToken,
+ m_tableKindToken, true, nullptr, &m_mdbTable);
+
+ // create the singleton row for the dbFolderInfo.
+ err = store->NewRowWithOid(m_mdb->GetEnv(),
+ &gDBFolderInfoOID, &m_mdbRow);
+
+ // add the row to the singleton table.
+ if (m_mdbRow && NS_SUCCEEDED(err))
+ err = m_mdbTable->AddRow(m_mdb->GetEnv(), m_mdbRow);
+
+ ret = err; // what are we going to do about nsresult's?
+ }
+ return ret;
+}
+
+nsresult nsDBFolderInfo::InitFromExistingDB()
+{
+ nsresult ret = NS_OK;
+ if (m_mdb && m_mdb->GetStore())
+ {
+ nsIMdbStore *store = m_mdb->GetStore();
+ if (store)
+ {
+ mdb_pos rowPos;
+ mdb_count outTableCount; // current number of such tables
+ mdb_bool mustBeUnique; // whether port can hold only one of these
+ mdb_bool hasOid;
+ ret = store->GetTableKind(m_mdb->GetEnv(), m_rowScopeToken, m_tableKindToken, &outTableCount,
+ &mustBeUnique, &m_mdbTable);
+ // NS_ASSERTION(mustBeUnique && outTableCount == 1, "only one global db info allowed");
+
+ if (m_mdbTable)
+ {
+ // find singleton row for global info.
+ ret = m_mdbTable->HasOid(m_mdb->GetEnv(), &gDBFolderInfoOID, &hasOid);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbTableRowCursor *rowCursor;
+ rowPos = -1;
+ ret= m_mdbTable->GetTableRowCursor(m_mdb->GetEnv(), rowPos, &rowCursor);
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = rowCursor->NextRow(m_mdb->GetEnv(), &m_mdbRow, &rowPos);
+ NS_RELEASE(rowCursor);
+ if (!m_mdbRow)
+ ret = NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(ret))
+ LoadMemberVariables();
+ }
+ }
+ }
+ else
+ ret = NS_ERROR_FAILURE;
+ }
+ }
+ return ret;
+}
+
+nsresult nsDBFolderInfo::InitMDBInfo()
+{
+ nsresult ret = NS_OK;
+ if (!m_mdbTokensInitialized && m_mdb && m_mdb->GetStore())
+ {
+ nsIMdbStore *store = m_mdb->GetStore();
+ nsIMdbEnv *env = m_mdb->GetEnv();
+
+ store->StringToToken(env, kNumMessagesColumnName, &m_numMessagesColumnToken);
+ store->StringToToken(env, kNumUnreadMessagesColumnName, &m_numUnreadMessagesColumnToken);
+ store->StringToToken(env, kFlagsColumnName, &m_flagsColumnToken);
+ store->StringToToken(env, kFolderSizeColumnName, &m_folderSizeColumnToken);
+ store->StringToToken(env, kExpungedBytesColumnName, &m_expungedBytesColumnToken);
+ store->StringToToken(env, kFolderDateColumnName, &m_folderDateColumnToken);
+
+ store->StringToToken(env, kHighWaterMessageKeyColumnName, &m_highWaterMessageKeyColumnToken);
+ store->StringToToken(env, kMailboxNameColumnName, &m_mailboxNameColumnToken);
+
+ store->StringToToken(env, kImapUidValidityColumnName, &m_imapUidValidityColumnToken);
+ store->StringToToken(env, kTotalPendingMessagesColumnName, &m_totalPendingMessagesColumnToken);
+ store->StringToToken(env, kUnreadPendingMessagesColumnName, &m_unreadPendingMessagesColumnToken);
+ store->StringToToken(env, kExpiredMarkColumnName, &m_expiredMarkColumnToken);
+ store->StringToToken(env, kVersionColumnName, &m_versionColumnToken);
+ m_mdbTokensInitialized = true;
+ }
+
+ return ret;
+}
+
+nsresult nsDBFolderInfo::LoadMemberVariables()
+{
+ // it's really not an error for these properties to not exist...
+ GetInt32PropertyWithToken(m_numMessagesColumnToken, m_numMessages);
+ GetInt32PropertyWithToken(m_numUnreadMessagesColumnToken, m_numUnreadMessages);
+ GetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+ GetInt64PropertyWithToken(m_folderSizeColumnToken, m_folderSize);
+ GetUint32PropertyWithToken(m_folderDateColumnToken, m_folderDate);
+ GetInt32PropertyWithToken(m_imapUidValidityColumnToken, m_ImapUidValidity, kUidUnknown);
+ GetUint32PropertyWithToken(m_expiredMarkColumnToken, m_expiredMark);
+ GetInt64PropertyWithToken(m_expungedBytesColumnToken, m_expungedBytes);
+ GetUint32PropertyWithToken(m_highWaterMessageKeyColumnToken, m_highWaterMessageKey);
+ int32_t version;
+
+ GetInt32PropertyWithToken(m_versionColumnToken, version);
+ m_version = (uint16_t) version;
+ m_charSetOverride = gDefaultCharacterOverride;
+ uint32_t propertyValue;
+ nsresult rv = GetUint32Property(kCharacterSetOverrideColumnName, gDefaultCharacterOverride, &propertyValue);
+ if (NS_SUCCEEDED(rv))
+ m_charSetOverride = propertyValue;
+
+ m_mdb->GetProperty(m_mdbRow, kCharacterSetColumnName, getter_Copies(m_charSet));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetVersion(uint32_t version)
+{
+ m_version = version;
+ return SetUint32PropertyWithToken(m_versionColumnToken, (uint32_t) m_version);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetVersion(uint32_t *version)
+{
+ *version = m_version;
+ return NS_OK;
+}
+
+
+nsresult nsDBFolderInfo::AdjustHighWater(nsMsgKey highWater, bool force)
+{
+ if (force || m_highWaterMessageKey < highWater)
+ {
+ m_highWaterMessageKey = highWater;
+ SetUint32PropertyWithToken(m_highWaterMessageKeyColumnToken, highWater);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetHighWater(nsMsgKey highWater)
+{
+ return AdjustHighWater(highWater, true);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::OnKeyAdded(nsMsgKey aNewKey)
+{
+ return AdjustHighWater(aNewKey, false);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetFolderSize(int64_t *size)
+{
+ NS_ENSURE_ARG_POINTER(size);
+ *size = m_folderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFolderSize(int64_t size)
+{
+ m_folderSize = size;
+ return SetInt64Property(kFolderSizeColumnName, m_folderSize);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetFolderDate(uint32_t *folderDate)
+{
+ NS_ENSURE_ARG_POINTER(folderDate);
+ *folderDate = m_folderDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFolderDate(uint32_t folderDate)
+{
+ m_folderDate = folderDate;
+ return SetUint32PropertyWithToken(m_folderDateColumnToken, folderDate);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetHighWater(nsMsgKey *result)
+{
+ // Sanity check highwater - if it gets too big, other code
+ // can fail. Look through last 100 messages to recalculate
+ // the highwater mark.
+ *result = m_highWaterMessageKey;
+ if (m_highWaterMessageKey > 0xFFFFFF00 && m_mdb)
+ {
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = m_mdb->ReverseEnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+ nsCOMPtr<nsIMsgDBHdr> pHeader;
+ nsMsgKey recalculatedHighWater = 1;
+ int32_t i = 0;
+ while(i++ < 100 && NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore))
+ && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ (void) hdrs->GetNext(getter_AddRefs(supports));
+ pHeader = do_QueryInterface(supports);
+ if (pHeader)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ if (msgKey > recalculatedHighWater)
+ recalculatedHighWater = msgKey;
+ }
+ }
+ NS_ASSERTION(m_highWaterMessageKey >= recalculatedHighWater,
+ "highwater incorrect");
+ m_highWaterMessageKey = recalculatedHighWater;
+ }
+ *result = m_highWaterMessageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetExpiredMark(nsMsgKey expiredKey)
+{
+ m_expiredMark = expiredKey;
+ return SetUint32PropertyWithToken(m_expiredMarkColumnToken, expiredKey);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetExpiredMark(nsMsgKey *result)
+{
+ *result = m_expiredMark;
+ return NS_OK;
+}
+
+// The size of the argument depends on the maximum size of a single message
+NS_IMETHODIMP nsDBFolderInfo::ChangeExpungedBytes(int32_t delta)
+{
+ return SetExpungedBytes(m_expungedBytes + delta);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetMailboxName(const nsAString &newBoxName)
+{
+ return SetPropertyWithToken(m_mailboxNameColumnToken, newBoxName);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetMailboxName(nsAString &boxName)
+{
+ return GetPropertyWithToken(m_mailboxNameColumnToken, boxName);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::ChangeNumUnreadMessages(int32_t delta)
+{
+ m_numUnreadMessages += delta;
+ // m_numUnreadMessages can never be set to negative.
+ if (m_numUnreadMessages < 0)
+ {
+#ifdef DEBUG_bienvenu1
+ NS_ASSERTION(false, "Hardcoded assertion");
+#endif
+ m_numUnreadMessages = 0;
+ }
+ return SetUint32PropertyWithToken(m_numUnreadMessagesColumnToken, m_numUnreadMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::ChangeNumMessages(int32_t delta)
+{
+ m_numMessages += delta;
+ // m_numMessages can never be set to negative.
+ if (m_numMessages < 0)
+ {
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(false, "num messages can't be < 0");
+#endif
+ m_numMessages = 0;
+ }
+ return SetUint32PropertyWithToken(m_numMessagesColumnToken, m_numMessages);
+}
+
+
+NS_IMETHODIMP nsDBFolderInfo::GetNumUnreadMessages(int32_t *result)
+{
+ *result = m_numUnreadMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetNumUnreadMessages(int32_t numUnreadMessages)
+{
+ m_numUnreadMessages = numUnreadMessages;
+ return SetUint32PropertyWithToken(m_numUnreadMessagesColumnToken, m_numUnreadMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetNumMessages(int32_t *result)
+{
+ *result = m_numMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetNumMessages(int32_t numMessages)
+{
+ m_numMessages = numMessages;
+ return SetUint32PropertyWithToken(m_numMessagesColumnToken, m_numMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetExpungedBytes(int64_t *result)
+{
+ *result = m_expungedBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetExpungedBytes(int64_t expungedBytes)
+{
+ m_expungedBytes = expungedBytes;
+ return SetInt64PropertyWithToken(m_expungedBytesColumnToken, m_expungedBytes);
+}
+
+
+NS_IMETHODIMP nsDBFolderInfo::GetFlags(int32_t *result)
+{
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFlags(int32_t flags)
+{
+ nsresult ret = NS_OK;
+
+ if (m_flags != flags)
+ {
+ NS_ASSERTION((m_flags & nsMsgFolderFlags::Inbox) == 0 || (flags & nsMsgFolderFlags::Inbox) != 0, "lost inbox flag");
+ m_flags = flags;
+ ret = SetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::OrFlags(int32_t flags, int32_t *result)
+{
+ m_flags |= flags;
+ *result = m_flags;
+ return SetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::AndFlags(int32_t flags, int32_t *result)
+{
+ m_flags &= flags;
+ *result = m_flags;
+ return SetInt32PropertyWithToken(m_flagsColumnToken, m_flags);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetImapUidValidity(int32_t *result)
+{
+ *result = m_ImapUidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetImapUidValidity(int32_t uidValidity)
+{
+ m_ImapUidValidity = uidValidity;
+ return SetUint32PropertyWithToken(m_imapUidValidityColumnToken, m_ImapUidValidity);
+}
+
+bool nsDBFolderInfo::TestFlag(int32_t flags)
+{
+ return (m_flags & flags) != 0;
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetCharacterSet(nsACString &result)
+{
+ if (!m_charSet.IsEmpty())
+ result.Assign(m_charSet);
+ else if (gDefaultCharacterSet)
+ result.Assign(*gDefaultCharacterSet);
+ else
+ result.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetEffectiveCharacterSet(nsACString &result)
+{
+ result.Truncate();
+ if (NS_FAILED(GetCharProperty(kCharacterSetColumnName, result)) ||
+ (result.IsEmpty() && gDefaultCharacterSet))
+ result = *gDefaultCharacterSet;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetCharacterSet(const nsACString &charSet)
+{
+ m_charSet.Assign(charSet);
+ return SetCharProperty(kCharacterSetColumnName, charSet);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetCharacterSetOverride(bool *characterSetOverride)
+{
+ NS_ENSURE_ARG_POINTER(characterSetOverride);
+ *characterSetOverride = m_charSetOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetCharacterSetOverride(bool characterSetOverride)
+{
+ m_charSetOverride = characterSetOverride;
+ return SetUint32Property(kCharacterSetOverrideColumnName, characterSetOverride);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetLocale(nsAString &result)
+{
+ GetProperty(kLocaleColumnName, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetLocale(const nsAString &locale)
+{
+ return SetProperty(kLocaleColumnName, locale);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetImapTotalPendingMessages(int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_totalPendingMessages;
+ return NS_OK;
+}
+
+void nsDBFolderInfo::ChangeImapTotalPendingMessages(int32_t delta)
+{
+ m_totalPendingMessages+=delta;
+ SetInt32PropertyWithToken(m_totalPendingMessagesColumnToken, m_totalPendingMessages);
+}
+
+NS_IMETHODIMP
+nsDBFolderInfo::GetImapUnreadPendingMessages(int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_unreadPendingMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetImapUnreadPendingMessages(int32_t numUnreadPendingMessages)
+{
+ m_unreadPendingMessages = numUnreadPendingMessages;
+ return SetUint32PropertyWithToken(m_unreadPendingMessagesColumnToken, m_unreadPendingMessages);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetImapTotalPendingMessages(int32_t numTotalPendingMessages)
+{
+ m_totalPendingMessages = numTotalPendingMessages;
+ return SetUint32PropertyWithToken(m_totalPendingMessagesColumnToken, m_totalPendingMessages);
+}
+
+void nsDBFolderInfo::ChangeImapUnreadPendingMessages(int32_t delta)
+{
+ m_unreadPendingMessages+=delta;
+ SetInt32PropertyWithToken(m_unreadPendingMessagesColumnToken, m_unreadPendingMessages);
+}
+
+/* attribute nsMsgViewTypeValue viewType; */
+NS_IMETHODIMP nsDBFolderInfo::GetViewType(nsMsgViewTypeValue *aViewType)
+{
+ uint32_t viewTypeValue;
+ nsresult rv = GetUint32Property("viewType", nsMsgViewType::eShowAllThreads, &viewTypeValue);
+ *aViewType = viewTypeValue;
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetViewType(nsMsgViewTypeValue aViewType)
+{
+ return SetUint32Property("viewType", aViewType);
+}
+
+/* attribute nsMsgViewFlagsTypeValue viewFlags; */
+NS_IMETHODIMP nsDBFolderInfo::GetViewFlags(nsMsgViewFlagsTypeValue *aViewFlags)
+{
+ nsMsgViewFlagsTypeValue defaultViewFlags;
+ nsresult rv = m_mdb->GetDefaultViewFlags(&defaultViewFlags);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t viewFlagsValue;
+ rv = GetUint32Property("viewFlags", defaultViewFlags, &viewFlagsValue);
+ *aViewFlags = viewFlagsValue;
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags)
+{
+ return SetUint32Property("viewFlags", aViewFlags);
+}
+
+/* attribute nsMsgViewSortTypeValue sortType; */
+NS_IMETHODIMP nsDBFolderInfo::GetSortType(nsMsgViewSortTypeValue *aSortType)
+{
+ nsMsgViewSortTypeValue defaultSortType;
+ nsresult rv = m_mdb->GetDefaultSortType(&defaultSortType);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t sortTypeValue;
+ rv = GetUint32Property("sortType", defaultSortType, &sortTypeValue);
+ *aSortType = sortTypeValue;
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetSortType(nsMsgViewSortTypeValue aSortType)
+{
+ return SetUint32Property("sortType", aSortType);
+}
+
+/* attribute nsMsgViewSortOrderValue sortOrder; */
+NS_IMETHODIMP nsDBFolderInfo::GetSortOrder(nsMsgViewSortOrderValue *aSortOrder)
+{
+ nsMsgViewSortOrderValue defaultSortOrder;
+ nsresult rv = m_mdb->GetDefaultSortOrder(&defaultSortOrder);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t sortOrderValue;
+ rv = GetUint32Property("sortOrder", defaultSortOrder, &sortOrderValue);
+ *aSortOrder = sortOrderValue;
+ return rv;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetSortOrder(nsMsgViewSortOrderValue aSortOrder)
+{
+ return SetUint32Property("sortOrder", aSortOrder);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetKnownArtsSet(const char *newsArtSet)
+{
+ return m_mdb->SetProperty(m_mdbRow, kKnownArtsSetColumnName, newsArtSet);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetKnownArtsSet(char **newsArtSet)
+{
+ return m_mdb->GetProperty(m_mdbRow, kKnownArtsSetColumnName, newsArtSet);
+}
+
+// get arbitrary property, aka row cell value.
+NS_IMETHODIMP nsDBFolderInfo::GetProperty(const char *propertyName, nsAString &resultProperty)
+{
+ return m_mdb->GetPropertyAsNSString(m_mdbRow, propertyName, resultProperty);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetCharProperty(const char *aPropertyName,
+ const nsACString &aPropertyValue)
+{
+ return m_mdb->SetProperty(m_mdbRow, aPropertyName,
+ nsCString(aPropertyValue).get());
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetCharProperty(const char *propertyName,
+ nsACString &resultProperty)
+{
+ nsCString result;
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, propertyName, getter_Copies(result));
+ if (NS_SUCCEEDED(rv))
+ resultProperty.Assign(result);
+ return rv;
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetUint32Property(const char *propertyName, uint32_t propertyValue)
+{
+ return m_mdb->SetUint32Property(m_mdbRow, propertyName, propertyValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetInt64Property(const char *propertyName, int64_t propertyValue)
+{
+ return m_mdb->SetUint64Property(m_mdbRow, propertyName, (uint64_t) propertyValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetProperty(const char *propertyName, const nsAString &propertyStr)
+{
+ return m_mdb->SetPropertyFromNSString(m_mdbRow, propertyName, propertyStr);
+}
+
+nsresult nsDBFolderInfo::SetPropertyWithToken(mdb_token aProperty, const nsAString &propertyStr)
+{
+ return m_mdb->SetNSStringPropertyWithToken(m_mdbRow, aProperty, propertyStr);
+}
+
+nsresult nsDBFolderInfo::SetUint32PropertyWithToken(mdb_token aProperty, uint32_t propertyValue)
+{
+ return m_mdb->UInt32ToRowCellColumn(m_mdbRow, aProperty, propertyValue);
+}
+
+nsresult nsDBFolderInfo::SetInt64PropertyWithToken(mdb_token aProperty, int64_t propertyValue)
+{
+ return m_mdb->UInt64ToRowCellColumn(m_mdbRow, aProperty, (uint64_t) propertyValue);
+}
+
+nsresult nsDBFolderInfo::SetInt32PropertyWithToken(mdb_token aProperty, int32_t propertyValue)
+{
+ nsAutoString propertyStr;
+ propertyStr.AppendInt(propertyValue, 16);
+ return SetPropertyWithToken(aProperty, propertyStr);
+}
+
+nsresult nsDBFolderInfo::GetPropertyWithToken(mdb_token aProperty, nsAString &resultProperty)
+{
+ return m_mdb->RowCellColumnTonsString(m_mdbRow, aProperty, resultProperty);
+}
+
+nsresult nsDBFolderInfo::GetUint32PropertyWithToken(mdb_token aProperty, uint32_t &propertyValue, uint32_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt32(m_mdbRow, aProperty, propertyValue, defaultValue);
+}
+
+nsresult nsDBFolderInfo::GetInt32PropertyWithToken(mdb_token aProperty, int32_t &propertyValue, int32_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt32(m_mdbRow, aProperty, (uint32_t &) propertyValue, defaultValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetUint32Property(const char *propertyName, uint32_t defaultValue, uint32_t *propertyValue)
+{
+ return m_mdb->GetUint32Property(m_mdbRow, propertyName, propertyValue, defaultValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetInt64Property(const char *propertyName, int64_t defaultValue, int64_t *propertyValue)
+{
+ return m_mdb->GetUint64Property(m_mdbRow, propertyName, (uint64_t *) &propertyValue, defaultValue);
+}
+
+nsresult nsDBFolderInfo::GetInt64PropertyWithToken(mdb_token aProperty,
+ int64_t &propertyValue,
+ int64_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt64(m_mdbRow, aProperty, (uint64_t *) &propertyValue, defaultValue);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetBooleanProperty(const char *propertyName, bool defaultValue, bool *propertyValue)
+{
+ uint32_t defaultUint32Value = (defaultValue) ? 1 : 0;
+ uint32_t returnValue;
+ nsresult rv = m_mdb->GetUint32Property(m_mdbRow, propertyName, &returnValue, defaultUint32Value);
+ *propertyValue = (returnValue != 0);
+ return rv;
+}
+NS_IMETHODIMP nsDBFolderInfo::SetBooleanProperty(const char *propertyName, bool propertyValue)
+{
+ return m_mdb->SetUint32Property(m_mdbRow, propertyName, propertyValue ? 1 : 0);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::GetFolderName(nsACString &folderName)
+{
+ return GetCharProperty("folderName", folderName);
+}
+
+NS_IMETHODIMP nsDBFolderInfo::SetFolderName(const nsACString &folderName)
+{
+ return SetCharProperty("folderName", folderName);
+}
+
+class nsTransferDBFolderInfo : public nsDBFolderInfo
+{
+public:
+ nsTransferDBFolderInfo();
+ virtual ~nsTransferDBFolderInfo();
+ // parallel arrays of properties and values
+ nsTArray<nsCString> m_properties;
+ nsTArray<nsCString> m_values;
+};
+
+nsTransferDBFolderInfo::nsTransferDBFolderInfo() : nsDBFolderInfo(nullptr)
+{
+}
+
+nsTransferDBFolderInfo::~nsTransferDBFolderInfo()
+{
+}
+
+/* void GetTransferInfo (out nsIDBFolderInfo transferInfo); */
+NS_IMETHODIMP nsDBFolderInfo::GetTransferInfo(nsIDBFolderInfo **transferInfo)
+{
+ NS_ENSURE_ARG_POINTER(transferInfo);
+
+ nsTransferDBFolderInfo *newInfo = new nsTransferDBFolderInfo;
+ *transferInfo = newInfo;
+ NS_ADDREF(newInfo);
+
+ mdb_count numCells;
+ mdbYarn cellYarn;
+ mdb_column cellColumn;
+ char columnName[100];
+ mdbYarn cellName = { columnName, 0, sizeof(columnName), 0, 0, nullptr };
+
+ NS_ASSERTION(m_mdbRow, "null row in getTransferInfo");
+ m_mdbRow->GetCount(m_mdb->GetEnv(), &numCells);
+ // iterate over the cells in the dbfolderinfo remembering attribute names and values.
+ for (mdb_count cellIndex = 0; cellIndex < numCells; cellIndex++)
+ {
+ nsresult err = m_mdbRow->SeekCellYarn(m_mdb->GetEnv(), cellIndex, &cellColumn, nullptr);
+ if (NS_SUCCEEDED(err))
+ {
+ err = m_mdbRow->AliasCellYarn(m_mdb->GetEnv(), cellColumn, &cellYarn);
+ if (NS_SUCCEEDED(err))
+ {
+ m_mdb->GetStore()->TokenToString(m_mdb->GetEnv(), cellColumn, &cellName);
+ newInfo->m_values.AppendElement(Substring((const char *)cellYarn.mYarn_Buf,
+ (const char *) cellYarn.mYarn_Buf + cellYarn.mYarn_Fill));
+ newInfo->m_properties.AppendElement(Substring((const char *) cellName.mYarn_Buf,
+ (const char *) cellName.mYarn_Buf + cellName.mYarn_Fill));
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+/* void InitFromTransferInfo (in nsIDBFolderInfo transferInfo); */
+NS_IMETHODIMP nsDBFolderInfo::InitFromTransferInfo(nsIDBFolderInfo *aTransferInfo)
+{
+ NS_ENSURE_ARG(aTransferInfo);
+
+ nsTransferDBFolderInfo *transferInfo = static_cast<nsTransferDBFolderInfo *>(aTransferInfo);
+
+ for (uint32_t i = 0; i < transferInfo->m_values.Length(); i++)
+ SetCharProperty(transferInfo->m_properties[i].get(), transferInfo->m_values[i]);
+
+ LoadMemberVariables();
+ return NS_OK;
+}
+
diff --git a/mailnews/db/msgdb/src/nsImapMailDatabase.cpp b/mailnews/db/msgdb/src/nsImapMailDatabase.cpp
new file mode 100644
index 000000000..84865eb80
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsImapMailDatabase.cpp
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 4; 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 <sys/stat.h>
+
+#include "msgCore.h"
+#include "nsImapMailDatabase.h"
+#include "nsDBFolderInfo.h"
+
+const char *kPendingHdrsScope = "ns:msg:db:row:scope:pending:all"; // scope for all offine ops table
+const char *kPendingHdrsTableKind = "ns:msg:db:table:kind:pending";
+struct mdbOid gAllPendingHdrsTableOID;
+
+nsImapMailDatabase::nsImapMailDatabase()
+{
+ m_mdbAllPendingHdrsTable = nullptr;
+}
+
+nsImapMailDatabase::~nsImapMailDatabase()
+{
+}
+
+NS_IMETHODIMP nsImapMailDatabase::GetSummaryValid(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (m_dbFolderInfo)
+ {
+ uint32_t version;
+ m_dbFolderInfo->GetVersion(&version);
+ *aResult = (GetCurVersion() == version);
+ }
+ else
+ *aResult = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::SetSummaryValid(bool valid)
+{
+ if (m_dbFolderInfo)
+ {
+ m_dbFolderInfo->SetVersion(valid ? GetCurVersion() : 0);
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return NS_OK;
+}
+
+// IMAP does not set local file flags, override does nothing
+void nsImapMailDatabase::UpdateFolderFlag(nsIMsgDBHdr * /* msgHdr */, bool /* bSet */,
+ nsMsgMessageFlagType /* flag */, nsIOutputStream ** /* ppFileStream */)
+{
+}
+
+// We override this to avoid our parent class (nsMailDatabase)'s
+// grabbing of the folder semaphore, and bailing on failure.
+NS_IMETHODIMP nsImapMailDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator)
+{
+ return nsMsgDatabase::DeleteMessages(aNumKeys, nsMsgKeys, instigator);
+}
+
+// override so nsMailDatabase methods that deal with m_folderStream are *not* called
+NS_IMETHODIMP nsImapMailDatabase::StartBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::EndBatch()
+{
+ return NS_OK;
+}
+
+nsresult nsImapMailDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline && m_dbFolderInfo)
+ {
+ uint32_t size = 0;
+ (void)msgHdr->GetOfflineMessageSize(&size);
+ return m_dbFolderInfo->ChangeExpungedBytes (size);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::ForceClosed()
+{
+ m_mdbAllPendingHdrsTable = nullptr;
+ return nsMailDatabase::ForceClosed();
+}
+
+nsresult nsImapMailDatabase::GetAllPendingHdrsTable()
+{
+ nsresult rv = NS_OK;
+ if (!m_mdbAllPendingHdrsTable)
+ rv = GetTableCreateIfMissing(kPendingHdrsScope, kPendingHdrsTableKind, getter_AddRefs(m_mdbAllPendingHdrsTable),
+ m_pendingHdrsRowScopeToken, m_pendingHdrsTableKindToken) ;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify)
+{
+ nsresult rv = nsMsgDatabase::AddNewHdrToDB(newHdr, notify);
+ if (NS_SUCCEEDED(rv))
+ rv = UpdatePendingAttributes(newHdr);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::UpdatePendingAttributes(nsIMsgDBHdr* aNewHdr)
+{
+ nsresult rv = GetAllPendingHdrsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ mdb_count numPendingHdrs = 0;
+ m_mdbAllPendingHdrsTable->GetCount(GetEnv(), &numPendingHdrs);
+ if (numPendingHdrs > 0)
+ {
+ mdbYarn messageIdYarn;
+ nsCOMPtr <nsIMdbRow> pendingRow;
+ mdbOid outRowId;
+
+ nsCString messageId;
+ aNewHdr->GetMessageId(getter_Copies(messageId));
+ messageIdYarn.mYarn_Buf = (void*)messageId.get();
+ messageIdYarn.mYarn_Fill = messageId.Length();
+ messageIdYarn.mYarn_Form = 0;
+ messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
+
+ m_mdbStore->FindRow(GetEnv(), m_pendingHdrsRowScopeToken,
+ m_messageIdColumnToken, &messageIdYarn, &outRowId, getter_AddRefs(pendingRow));
+ if (pendingRow)
+ {
+ mdb_count numCells;
+ mdbYarn cellYarn;
+ mdb_column cellColumn;
+ uint32_t existingFlags;
+
+ pendingRow->GetCount(GetEnv(), &numCells);
+ aNewHdr->GetFlags(&existingFlags);
+ // iterate over the cells in the pending hdr setting properties on the aNewHdr.
+ // we skip cell 0, which is the messageId;
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aNewHdr); // closed system, cast ok
+ nsIMdbRow *row = msgHdr->GetMDBRow();
+ for (mdb_count cellIndex = 1; cellIndex < numCells; cellIndex++)
+ {
+ nsresult err = pendingRow->SeekCellYarn(GetEnv(), cellIndex, &cellColumn, nullptr);
+ if (NS_SUCCEEDED(err))
+ {
+ err = pendingRow->AliasCellYarn(GetEnv(), cellColumn, &cellYarn);
+ if (NS_SUCCEEDED(err))
+ {
+ if (row)
+ row->AddColumn(GetEnv(), cellColumn, &cellYarn);
+ }
+ }
+ }
+ // We might have changed some cached values, so force a refresh.
+ msgHdr->ClearCachedValues();
+ uint32_t resultFlags;
+ msgHdr->OrFlags(existingFlags, &resultFlags);
+ m_mdbAllPendingHdrsTable->CutRow(GetEnv(), pendingRow);
+ pendingRow->CutAllColumns(GetEnv());
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapMailDatabase::GetRowForPendingHdr(nsIMsgDBHdr *pendingHdr,
+ nsIMdbRow **row)
+{
+ nsresult rv = GetAllPendingHdrsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mdbYarn messageIdYarn;
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ mdbOid outRowId;
+ nsCString messageId;
+ pendingHdr->GetMessageId(getter_Copies(messageId));
+ messageIdYarn.mYarn_Buf = (void*)messageId.get();
+ messageIdYarn.mYarn_Fill = messageId.Length();
+ messageIdYarn.mYarn_Form = 0;
+ messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
+
+ rv = m_mdbStore->FindRow(GetEnv(), m_pendingHdrsRowScopeToken,
+ m_messageIdColumnToken, &messageIdYarn, &outRowId, getter_AddRefs(pendingRow));
+
+ if (!pendingRow)
+ rv = m_mdbStore->NewRow(GetEnv(), m_pendingHdrsRowScopeToken, getter_AddRefs(pendingRow));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (pendingRow)
+ {
+ // now we need to add cells to the row to remember the messageid, property and property value, and flags.
+ // Then, when hdrs are added to the db, we'll check if they have a matching message-id, and if so,
+ // set the property and flags
+ // XXX we already fetched messageId from the pending hdr, could it have changed by the time we get here?
+ nsCString messageId;
+ pendingHdr->GetMessageId(getter_Copies(messageId));
+ // we're just going to ignore messages without a message-id. They should be rare. If SPAM messages often
+ // didn't have message-id's, they'd be filtered on the server, most likely, and spammers would then
+ // start putting in message-id's.
+ if (!messageId.IsEmpty())
+ {
+ extern const char *kMessageIdColumnName;
+ m_mdbAllPendingHdrsTable->AddRow(GetEnv(), pendingRow);
+ // make sure this is the first cell so that when we ignore the first
+ // cell in nsImapMailDatabase::AddNewHdrToDB, we're ignoring the right one
+ (void) SetProperty(pendingRow, kMessageIdColumnName, messageId.get());
+ pendingRow.forget(row);
+ }
+ else
+ return NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailDatabase::SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ const char *propertyVal)
+{
+ NS_ENSURE_ARG_POINTER(pendingHdr);
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ nsresult rv = GetRowForPendingHdr(pendingHdr, getter_AddRefs(pendingRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetProperty(pendingRow, property, propertyVal);
+}
+
+NS_IMETHODIMP
+nsImapMailDatabase::SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr,
+ const char *property,
+ uint32_t propertyVal)
+{
+ NS_ENSURE_ARG_POINTER(pendingHdr);
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ nsresult rv = GetRowForPendingHdr(pendingHdr, getter_AddRefs(pendingRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetUint32Property(pendingRow, property, propertyVal);
+}
+
+NS_IMETHODIMP
+nsImapMailDatabase::SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr,
+ const char *aProperty,
+ uint64_t aPropertyVal)
+{
+ NS_ENSURE_ARG_POINTER(aPendingHdr);
+ nsCOMPtr<nsIMdbRow> pendingRow;
+ nsresult rv = GetRowForPendingHdr(aPendingHdr, getter_AddRefs(pendingRow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetUint64Property(pendingRow, aProperty, aPropertyVal);
+}
diff --git a/mailnews/db/msgdb/src/nsMailDatabase.cpp b/mailnews/db/msgdb/src/nsMailDatabase.cpp
new file mode 100644
index 000000000..13a53485f
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMailDatabase.cpp
@@ -0,0 +1,444 @@
+/* -*- 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 "nsMailDatabase.h"
+#include "nsDBFolderInfo.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "nsMsgOfflineImapOperation.h"
+#include "nsMsgFolderFlags.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgPluggableStore.h"
+
+extern PRLogModuleInfo *IMAPOffline;
+
+using namespace mozilla;
+
+// scope for all offine ops table
+const char *kOfflineOpsScope = "ns:msg:db:row:scope:ops:all";
+const char *kOfflineOpsTableKind = "ns:msg:db:table:kind:ops";
+struct mdbOid gAllOfflineOpsTableOID;
+
+nsMailDatabase::nsMailDatabase() : m_reparse(false)
+{
+ m_mdbAllOfflineOpsTable = nullptr;
+ m_offlineOpsRowScopeToken = 0;
+ m_offlineOpsTableKindToken = 0;
+}
+
+nsMailDatabase::~nsMailDatabase()
+{
+}
+
+// caller passes in upgrading==true if they want back a db even if the db is out of date.
+// If so, they'll extract out the interesting info from the db, close it, delete it, and
+// then try to open the db again, prior to reparsing.
+nsresult nsMailDatabase::Open(nsMsgDBService* aDBService, nsIFile *aSummaryFile,
+ bool aCreate, bool aUpgrading)
+{
+#ifdef DEBUG
+ nsString leafName;
+ aSummaryFile->GetLeafName(leafName);
+ if (!StringEndsWith(leafName, NS_LITERAL_STRING(".msf"),
+ nsCaseInsensitiveStringComparator()))
+ NS_ERROR("non summary file passed into open\n");
+#endif
+ return nsMsgDatabase::Open(aDBService, aSummaryFile, aCreate, aUpgrading);
+}
+
+NS_IMETHODIMP nsMailDatabase::ForceClosed()
+{
+ m_mdbAllOfflineOpsTable = nullptr;
+ return nsMsgDatabase::ForceClosed();
+}
+
+// get this on demand so that only db's that have offline ops will
+// create the table.
+nsresult nsMailDatabase::GetAllOfflineOpsTable()
+{
+ nsresult rv = NS_OK;
+ if (!m_mdbAllOfflineOpsTable)
+ rv = GetTableCreateIfMissing(kOfflineOpsScope, kOfflineOpsTableKind, getter_AddRefs(m_mdbAllOfflineOpsTable),
+ m_offlineOpsRowScopeToken, m_offlineOpsTableKindToken) ;
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::StartBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailDatabase::EndBatch()
+{
+ SetSummaryValid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ if (m_folder)
+ {
+ bool isLocked;
+ m_folder->GetLocked(&isLocked);
+ if (isLocked)
+ {
+ NS_ASSERTION(false, "Some other operation is in progress");
+ return NS_MSG_FOLDER_BUSY;
+ }
+ }
+
+ rv = nsMsgDatabase::DeleteMessages(aNumKeys, nsMsgKeys, instigator);
+ SetSummaryValid(true);
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::GetSummaryValid(bool *aResult)
+{
+ uint32_t version;
+ m_dbFolderInfo->GetVersion(&version);
+ if (GetCurVersion() != version)
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ if (!m_folder) {
+ // If the folder is not set, we just return without checking the validity
+ // of the summary file. For now, this is an expected condition when the
+ // message database is being opened from a URL in
+ // nsMailboxUrl::GetMsgHdrForKey() which calls
+ // nsMsgDBService::OpenMailDBFromFile() without a folder.
+ // Returning an error here would lead to the deletion of the MSF in the
+ // caller nsMsgDatabase::CheckForErrors().
+ *aResult = true;
+ return NS_OK;
+ }
+ nsresult rv = m_folder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->IsSummaryFileValid(m_folder, this, aResult);
+}
+
+NS_IMETHODIMP nsMailDatabase::SetSummaryValid(bool aValid)
+{
+ nsMsgDatabase::SetSummaryValid(aValid);
+
+ if (!m_folder)
+ return NS_ERROR_NULL_POINTER;
+
+ // If this is a virtual folder, there is no storage.
+ bool flag;
+ m_folder->GetFlag(nsMsgFolderFlags::Virtual, &flag);
+ if (flag)
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = m_folder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->SetSummaryFileValid(m_folder, this, aValid);
+}
+
+NS_IMETHODIMP nsMailDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation *op)
+{
+
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!op || !m_mdbAllOfflineOpsTable)
+ return NS_ERROR_NULL_POINTER;
+ nsMsgOfflineImapOperation* offlineOp = static_cast<nsMsgOfflineImapOperation*>(op); // closed system, so this is ok
+ nsIMdbRow* row = offlineOp->GetMDBRow();
+ rv = m_mdbAllOfflineOpsTable->CutRow(GetEnv(), row);
+ row->CutAllColumns(GetEnv());
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::GetOfflineOpForKey(nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation **offlineOp)
+{
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+ nsresult err;
+
+ if (!IMAPOffline)
+ IMAPOffline = PR_NewLogModule("IMAPOFFLINE");
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!offlineOp || !m_mdbAllOfflineOpsTable)
+ return NS_ERROR_NULL_POINTER;
+
+ *offlineOp = NULL;
+
+ rowObjectId.mOid_Id = msgKey;
+ rowObjectId.mOid_Scope = m_offlineOpsRowScopeToken;
+ err = m_mdbAllOfflineOpsTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ if (NS_SUCCEEDED(err) && m_mdbStore && (hasOid || create))
+ {
+ nsCOMPtr <nsIMdbRow> offlineOpRow;
+ err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(offlineOpRow));
+
+ if (create)
+ {
+ if (!offlineOpRow)
+ {
+ err = m_mdbStore->NewRowWithOid(GetEnv(), &rowObjectId, getter_AddRefs(offlineOpRow));
+ NS_ENSURE_SUCCESS(err, err);
+ }
+ if (offlineOpRow && !hasOid)
+ m_mdbAllOfflineOpsTable->AddRow(GetEnv(), offlineOpRow);
+ }
+
+ if (NS_SUCCEEDED(err) && offlineOpRow)
+ {
+ *offlineOp = new nsMsgOfflineImapOperation(this, offlineOpRow);
+ if (*offlineOp)
+ (*offlineOp)->SetMessageKey(msgKey);
+ NS_IF_ADDREF(*offlineOp);
+ }
+ if (!hasOid && m_dbFolderInfo)
+ {
+ // set initial value for flags so we don't lose them.
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ (*offlineOp)->SetNewFlags(flags);
+ }
+ int32_t newFlags;
+ m_dbFolderInfo->OrFlags(nsMsgFolderFlags::OfflineEvents, &newFlags);
+ }
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsMailDatabase::EnumerateOfflineOps(nsISimpleEnumerator **enumerator)
+{
+ NS_ASSERTION(false, "not impl yet");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsMailDatabase::ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds)
+{
+ NS_ENSURE_ARG(offlineOpIds);
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIMdbTableRowCursor *rowCursor;
+ if (!IMAPOffline)
+ IMAPOffline = PR_NewLogModule("IMAPOFFLINE");
+
+ if (m_mdbAllOfflineOpsTable)
+ {
+ nsresult err = m_mdbAllOfflineOpsTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
+ while (NS_SUCCEEDED(err) && rowCursor)
+ {
+ mdbOid outOid;
+ mdb_pos outPos;
+
+ err = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos);
+ // is this right? Mork is returning a 0 id, but that should valid.
+ if (outPos < 0 || outOid.mOid_Id == (mdb_id) -1)
+ break;
+ if (NS_SUCCEEDED(err))
+ {
+ offlineOpIds->AppendElement(outOid.mOid_Id);
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> offlineOp;
+ GetOfflineOpForKey(outOid.mOid_Id, false, getter_AddRefs(offlineOp));
+ if (offlineOp)
+ {
+ nsMsgOfflineImapOperation *logOp = static_cast<nsMsgOfflineImapOperation *>(static_cast<nsIMsgOfflineImapOperation *>(offlineOp.get()));
+ if (logOp)
+ logOp->Log(IMAPOffline);
+
+ }
+ }
+ }
+ }
+ // TODO: would it cause a problem to replace this with "rv = err;" ?
+ rv = (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+ rowCursor->Release();
+ }
+
+ offlineOpIds->Sort();
+ return rv;
+}
+
+NS_IMETHODIMP nsMailDatabase::ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes)
+{
+ NS_ENSURE_ARG_POINTER(offlineDeletes);
+
+ nsresult rv = GetAllOfflineOpsTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIMdbTableRowCursor *rowCursor;
+ if (m_mdbAllOfflineOpsTable)
+ {
+ nsresult err = m_mdbAllOfflineOpsTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
+ while (NS_SUCCEEDED(err) && rowCursor)
+ {
+ mdbOid outOid;
+ mdb_pos outPos;
+ nsIMdbRow* offlineOpRow;
+
+ err = rowCursor->NextRow(GetEnv(), &offlineOpRow, &outPos);
+ // is this right? Mork is returning a 0 id, but that should valid.
+ if (outPos < 0 || offlineOpRow == nullptr)
+ break;
+ if (NS_SUCCEEDED(err))
+ {
+ offlineOpRow->GetOid(GetEnv(), &outOid);
+ nsIMsgOfflineImapOperation *offlineOp = new nsMsgOfflineImapOperation(this, offlineOpRow);
+ if (offlineOp)
+ {
+ NS_ADDREF(offlineOp);
+ imapMessageFlagsType newFlags;
+ nsOfflineImapOperationType opType;
+
+ offlineOp->GetOperation(&opType);
+ offlineOp->GetNewFlags(&newFlags);
+ if (opType & nsIMsgOfflineImapOperation::kMsgMoved ||
+ ((opType & nsIMsgOfflineImapOperation::kFlagsChanged)
+ && (newFlags & nsIMsgOfflineImapOperation::kMsgMarkedDeleted)))
+ offlineDeletes->AppendElement(outOid.mOid_Id);
+ NS_RELEASE(offlineOp);
+ }
+ offlineOpRow->Release();
+ }
+ }
+ // TODO: would it cause a problem to replace this with "rv = err;" ?
+ rv = (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
+ rowCursor->Release();
+ }
+ return rv;
+}
+
+// This is used to remember that the db is out of sync with the mail folder
+// and needs to be regenerated.
+void nsMailDatabase::SetReparse(bool reparse)
+{
+ m_reparse = reparse;
+}
+
+class nsMsgOfflineOpEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ nsMsgOfflineOpEnumerator(nsMailDatabase* db);
+
+protected:
+ virtual ~nsMsgOfflineOpEnumerator();
+ nsresult GetRowCursor();
+ nsresult PrefetchNext();
+ nsMailDatabase* mDB;
+ nsIMdbTableRowCursor* mRowCursor;
+ nsCOMPtr <nsIMsgOfflineImapOperation> mResultOp;
+ bool mDone;
+ bool mNextPrefetched;
+};
+
+nsMsgOfflineOpEnumerator::nsMsgOfflineOpEnumerator(nsMailDatabase* db)
+ : mDB(db), mRowCursor(nullptr), mDone(false)
+{
+ NS_ADDREF(mDB);
+ mNextPrefetched = false;
+}
+
+nsMsgOfflineOpEnumerator::~nsMsgOfflineOpEnumerator()
+{
+ NS_IF_RELEASE(mRowCursor);
+ NS_RELEASE(mDB);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgOfflineOpEnumerator, nsISimpleEnumerator)
+
+nsresult nsMsgOfflineOpEnumerator::GetRowCursor()
+{
+ nsresult rv = NS_OK;
+ mDone = false;
+
+ if (!mDB || !mDB->m_mdbAllOfflineOpsTable)
+ return NS_ERROR_NULL_POINTER;
+
+ rv = mDB->m_mdbAllOfflineOpsTable->GetTableRowCursor(mDB->GetEnv(), -1, &mRowCursor);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineOpEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv = NS_OK;
+ if (!mNextPrefetched)
+ rv = PrefetchNext();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mResultOp)
+ {
+ *aItem = mResultOp;
+ NS_ADDREF(*aItem);
+ mNextPrefetched = false;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgOfflineOpEnumerator::PrefetchNext()
+{
+ nsresult rv = NS_OK;
+ nsIMdbRow* offlineOpRow;
+ mdb_pos rowPos;
+
+ if (!mRowCursor)
+ {
+ rv = GetRowCursor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ rv = mRowCursor->NextRow(mDB->GetEnv(), &offlineOpRow, &rowPos);
+ if (!offlineOpRow)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+
+ nsIMsgOfflineImapOperation *op = new nsMsgOfflineImapOperation(mDB, offlineOpRow);
+ mResultOp = op;
+ if (!op)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (mResultOp)
+ {
+ mNextPrefetched = true;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgOfflineOpEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!mNextPrefetched)
+ PrefetchNext();
+ *aResult = !mDone;
+ return NS_OK;
+}
diff --git a/mailnews/db/msgdb/src/nsMsgDatabase.cpp b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
new file mode 100644
index 000000000..8b366ab5c
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgDatabase.cpp
@@ -0,0 +1,5915 @@
+/* -*- 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/. */
+
+// this file implements the nsMsgDatabase interface using the MDB Interface.
+
+#include "nscore.h"
+#include "msgCore.h"
+#include "nsMailDatabase.h"
+#include "nsDBFolderInfo.h"
+#include "nsMsgKeySet.h"
+#include "nsMsgThread.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsMsgBaseCID.h"
+#include "nsMorkCID.h"
+#include "nsIMdbFactoryFactory.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsMsgDBCID.h"
+#include "nsILocale.h"
+#include "nsMsgMimeCID.h"
+#include "nsILocaleService.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgDBView.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "MailNewsTypes2.h"
+#include "nsMsgUtils.h"
+#include "nsMsgKeyArray.h"
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsICollation.h"
+#include "nsCollationCID.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsAlgorithm.h"
+#include "nsArrayEnumerator.h"
+#include "nsIMemoryReporter.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/mailnews/Services.h"
+#include <algorithm>
+
+using namespace mozilla::mailnews;
+using namespace mozilla;
+
+#if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
+#define DEBUG_MSGKEYSET 1
+#endif
+
+#define MSG_HASH_SIZE 512
+
+// This will be used on discovery, since we don't know total.
+const int32_t kMaxHdrsInCache = 512;
+
+// special keys
+static const nsMsgKey kAllMsgHdrsTableKey = 1;
+static const nsMsgKey kTableKeyForThreadOne = 0xfffffffe;
+static const nsMsgKey kAllThreadsTableKey = 0xfffffffd;
+static const nsMsgKey kFirstPseudoKey = 0xfffffff0;
+static const nsMsgKey kIdStartOfFake = 0xffffff80;
+static const nsMsgKey kForceReparseKey = 0xfffffff0;
+
+static PRLogModuleInfo* DBLog;
+
+PRTime nsMsgDatabase::gLastUseTime;
+
+NS_IMPL_ISUPPORTS(nsMsgDBService, nsIMsgDBService)
+
+nsMsgDBService::nsMsgDBService()
+{
+ DBLog = PR_NewLogModule("MSGDB");
+}
+
+
+nsMsgDBService::~nsMsgDBService()
+{
+#ifdef DEBUG
+ // If you hit this warning, it means that some code is holding onto
+ // a db at shutdown.
+ NS_WARNING_ASSERTION(!m_dbCache.Length(), "some msg dbs left open");
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ {
+ nsMsgDatabase* pMessageDB = m_dbCache.ElementAt(i);
+ if (pMessageDB)
+ printf("db left open %s\n", (const char *) pMessageDB->m_dbName.get());
+ }
+#endif
+}
+
+NS_IMETHODIMP nsMsgDBService::OpenFolderDB(nsIMsgFolder *aFolder,
+ bool aLeaveInvalidDB,
+ nsIMsgDatabase **_retval)
+{
+ NS_ENSURE_ARG(aFolder);
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFilePath;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *cacheDB = FindInCache(summaryFilePath);
+ if (cacheDB)
+ {
+ // this db could have ended up in the folder cache w/o an m_folder pointer via
+ // OpenMailDBFromFile. If so, take this chance to fix the folder.
+ if (!cacheDB->m_folder)
+ cacheDB->m_folder = aFolder;
+ cacheDB->RememberLastUseTime();
+ *_retval = cacheDB; // FindInCache already addRefed.
+ // if m_thumb is set, someone is asynchronously opening the db. But our
+ // caller wants to synchronously open it, so just do it.
+ if (cacheDB->m_thumb)
+ return cacheDB->Open(this, summaryFilePath, false, aLeaveInvalidDB);
+ return NS_OK;
+ }
+
+ nsCString localDatabaseType;
+ incomingServer->GetLocalDatabaseType(localDatabaseType);
+ nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
+ dbContractID.Append(localDatabaseType.get());
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't try to create the database yet--let the createNewDB call do that.
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get());
+ msgDatabase->m_folder = aFolder;
+ rv = msgDatabase->Open(this, summaryFilePath, false, aLeaveInvalidDB);
+ if (NS_FAILED(rv) && rv != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ return rv;
+
+ NS_ADDREF(*_retval = msgDB);
+
+ if (NS_FAILED(rv))
+ {
+#ifdef DEBUG
+ // Doing these checks for debug only as we don't want to report certain
+ // errors in debug mode, but in release mode we wouldn't report them either
+
+ // These errors are expected.
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ return rv;
+
+ // If it isn't one of the expected errors, throw a warning.
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ return rv;
+ }
+
+ FinishDBOpen(aFolder, msgDatabase);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::AsyncOpenFolderDB(nsIMsgFolder *aFolder,
+ bool aLeaveInvalidDB,
+ nsIMsgDatabase **_retval)
+{
+ NS_ENSURE_ARG(aFolder);
+
+ nsCOMPtr <nsIFile> summaryFilePath;
+ nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *cacheDB = FindInCache(summaryFilePath);
+ if (cacheDB)
+ {
+ // this db could have ended up in the folder cache w/o an m_folder pointer via
+ // OpenMailDBFromFile. If so, take this chance to fix the folder.
+ if (!cacheDB->m_folder)
+ cacheDB->m_folder = aFolder;
+ *_retval = cacheDB; // FindInCache already addRefed.
+ // We don't care if an other consumer is thumbing the store. In that
+ // case, they'll both thumb the store.
+ return NS_OK;
+ }
+
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ rv = aFolder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString localDatabaseType;
+ incomingServer->GetLocalDatabaseType(localDatabaseType);
+ nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
+ dbContractID.Append(localDatabaseType.get());
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get());
+ rv = msgDatabase->OpenInternal(this, summaryFilePath, false, aLeaveInvalidDB,
+ false /* open asynchronously */);
+
+ NS_ADDREF(*_retval = msgDB);
+ msgDatabase->m_folder = aFolder;
+
+ if (NS_FAILED(rv))
+ {
+#ifdef DEBUG
+ // Doing these checks for debug only as we don't want to report certain
+ // errors in debug mode, but in release mode we wouldn't report them either
+
+ // These errors are expected.
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ return rv;
+
+ // If it isn't one of the expected errors, throw a warning.
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ return rv;
+ }
+
+ FinishDBOpen(aFolder, msgDatabase);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::OpenMore(nsIMsgDatabase *aDB,
+ uint32_t aTimeHint,
+ bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(aDB);
+ NS_ENSURE_TRUE(msgDatabase, NS_ERROR_INVALID_ARG);
+ // Check if this db has been opened.
+ if (!msgDatabase->m_thumb)
+ {
+ *_retval = true;
+ return NS_OK;
+ }
+ nsresult rv;
+ *_retval = false;
+ PRIntervalTime startTime = PR_IntervalNow();
+ do
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ rv = msgDatabase->m_thumb->DoMore(msgDatabase->m_mdbEnv,
+ &outTotal, &outCurrent, &outDone,
+ &outBroken);
+ if (NS_FAILED(rv))
+ break;
+ if (outDone)
+ {
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ msgDatabase->GetMDBFactory(getter_AddRefs(mdbFactory));
+ NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE);
+ rv = mdbFactory->ThumbToOpenStore(msgDatabase->m_mdbEnv, msgDatabase->m_thumb, &msgDatabase->m_mdbStore);
+ msgDatabase->m_thumb = nullptr;
+ nsCOMPtr<nsIFile> folderPath;
+ (void) msgDatabase->m_folder->GetFilePath(getter_AddRefs(folderPath));
+ nsCOMPtr <nsIFile> summaryFile;
+ (void) GetSummaryFileLocation(folderPath, getter_AddRefs(summaryFile));
+
+ if (NS_SUCCEEDED(rv))
+ rv = (msgDatabase->m_mdbStore) ? msgDatabase->InitExistingDB() : NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(rv))
+ rv = msgDatabase->CheckForErrors(rv, false, this, summaryFile);
+
+ FinishDBOpen(msgDatabase->m_folder, msgDatabase);
+ break;
+ }
+ }
+ while (PR_IntervalToMilliseconds(PR_IntervalNow() - startTime) <= aTimeHint);
+ *_retval = !msgDatabase->m_thumb;
+ return rv;
+}
+
+/**
+ * When a db is opened, we need to hook up any pending listeners for
+ * that db, and notify them.
+ */
+void nsMsgDBService::HookupPendingListeners(nsIMsgDatabase *db,
+ nsIMsgFolder *folder)
+{
+ for (int32_t listenerIndex = 0;
+ listenerIndex < m_foldersPendingListeners.Count(); listenerIndex++)
+ {
+ // check if we have a pending listener on this db, and if so, add it.
+ if (m_foldersPendingListeners[listenerIndex] == folder)
+ {
+ db->AddListener(m_pendingListeners.ObjectAt(listenerIndex));
+ m_pendingListeners.ObjectAt(listenerIndex)->OnEvent(db, "DBOpened");
+ }
+ }
+}
+
+void nsMsgDBService::FinishDBOpen(nsIMsgFolder *aFolder, nsMsgDatabase *aMsgDB)
+{
+ uint32_t folderFlags;
+ aFolder->GetFlags(&folderFlags);
+
+ if (! (folderFlags & nsMsgFolderFlags::Virtual) &&
+ aMsgDB->m_mdbAllMsgHeadersTable)
+ {
+ mdb_count numHdrsInTable = 0;
+ int32_t numMessages;
+ aMsgDB->m_mdbAllMsgHeadersTable->GetCount(aMsgDB->GetEnv(),
+ &numHdrsInTable);
+ aMsgDB->m_dbFolderInfo->GetNumMessages(&numMessages);
+ if (numMessages != (int32_t) numHdrsInTable)
+ aMsgDB->SyncCounts();
+ }
+ HookupPendingListeners(aMsgDB, aFolder);
+ aMsgDB->RememberLastUseTime();
+}
+
+//----------------------------------------------------------------------
+// FindInCache - this addrefs the db it finds.
+//----------------------------------------------------------------------
+nsMsgDatabase* nsMsgDBService::FindInCache(nsIFile *dbName)
+{
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ {
+ nsMsgDatabase* pMessageDB = m_dbCache[i];
+ if (pMessageDB->MatchDbName(dbName))
+ {
+ if (pMessageDB->m_mdbStore) // don't return db without store
+ {
+ NS_ADDREF(pMessageDB);
+ return pMessageDB;
+ }
+ }
+ }
+ return nullptr;
+}
+
+// This method is called when the caller is trying to create a db without
+// having a corresponding nsIMsgFolder object. This happens in a few
+// situations, including imap folder discovery, compacting local folders,
+// and copying local folders.
+NS_IMETHODIMP nsMsgDBService::OpenMailDBFromFile(nsIFile *aFolderName,
+ nsIMsgFolder *aFolder,
+ bool aCreate,
+ bool aLeaveInvalidDB,
+ nsIMsgDatabase** pMessageDB)
+{
+ if (!aFolderName)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr <nsIFile> dbPath;
+ nsresult rv = GetSummaryFileLocation(aFolderName, getter_AddRefs(dbPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *pMessageDB = FindInCache(dbPath);
+ if (*pMessageDB)
+ return NS_OK;
+
+ RefPtr<nsMailDatabase> msgDB = new nsMailDatabase;
+ NS_ENSURE_TRUE(msgDB, NS_ERROR_OUT_OF_MEMORY);
+ rv = msgDB->Open(this, dbPath, aCreate, aLeaveInvalidDB);
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return rv;
+ NS_IF_ADDREF(*pMessageDB = msgDB);
+ if (aCreate && msgDB && rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ rv = NS_OK;
+ if (NS_SUCCEEDED(rv))
+ msgDB->m_folder = aFolder;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::CreateNewDB(nsIMsgFolder *aFolder,
+ nsIMsgDatabase **_retval)
+{
+ NS_ENSURE_ARG(aFolder);
+
+ nsCOMPtr <nsIMsgIncomingServer> incomingServer;
+ nsresult rv = aFolder->GetServer(getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFilePath;
+ rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString localDatabaseType;
+ incomingServer->GetLocalDatabaseType(localDatabaseType);
+ nsAutoCString dbContractID(NS_MSGDB_CONTRACTID);
+ dbContractID.Append(localDatabaseType.get());
+
+ nsCOMPtr <nsIMsgDatabase> msgDB = do_CreateInstance(dbContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgDatabase *msgDatabase = static_cast<nsMsgDatabase *>(msgDB.get());
+
+ msgDatabase->m_folder = aFolder;
+ rv = msgDatabase->Open(this, summaryFilePath, true, true);
+
+ // We are trying to create a new database, but that implies that it did not
+ // already exist. Open returns NS_MSG_ERROR_FOLDER_SUMMARY_MISSING for the
+ // successful creation of a new database. But if it existed for some
+ // reason, then we would get rv = NS_OK instead. That is a "failure"
+ // from our perspective, so we want to return a failure since we are not
+ // returning a valid database object.
+ NS_ENSURE_TRUE(rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING,
+ NS_SUCCEEDED(rv) ? NS_ERROR_FILE_ALREADY_EXISTS : rv);
+
+ NS_ADDREF(*_retval = msgDB);
+
+ HookupPendingListeners(msgDB, aFolder);
+
+ msgDatabase->RememberLastUseTime();
+
+ return NS_OK;
+}
+
+/* void registerPendingListener (in nsIMsgFolder aFolder, in nsIDBChangeListener aListener); */
+NS_IMETHODIMP nsMsgDBService::RegisterPendingListener(nsIMsgFolder *aFolder, nsIDBChangeListener *aListener)
+{
+ // need to make sure we don't hold onto these forever. Maybe a shutdown listener?
+ // if there is a db open on this folder already, we should register the listener.
+ m_foldersPendingListeners.AppendObject(aFolder);
+ m_pendingListeners.AppendObject(aListener);
+ nsCOMPtr <nsIMsgDatabase> openDB;
+ CachedDBForFolder(aFolder, getter_AddRefs(openDB));
+ if (openDB)
+ openDB->AddListener(aListener);
+ return NS_OK;
+}
+
+/* void unregisterPendingListener (in nsIDBChangeListener aListener); */
+NS_IMETHODIMP nsMsgDBService::UnregisterPendingListener(nsIDBChangeListener *aListener)
+{
+ int32_t listenerIndex = m_pendingListeners.IndexOfObject(aListener);
+ if (listenerIndex != -1)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ CachedDBForFolder(m_foldersPendingListeners[listenerIndex], getter_AddRefs(msgDB));
+ if (msgDB)
+ msgDB->RemoveListener(aListener);
+ m_foldersPendingListeners.RemoveObjectAt(listenerIndex);
+ m_pendingListeners.RemoveObjectAt(listenerIndex);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBService::CachedDBForFolder(nsIMsgFolder *aFolder, nsIMsgDatabase **aRetDB)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aRetDB);
+
+ nsCOMPtr<nsIFile> summaryFilePath;
+ nsresult rv = aFolder->GetSummaryFile(getter_AddRefs(summaryFilePath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aRetDB = FindInCache(summaryFilePath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBService::ForceFolderDBClosed(nsIMsgFolder *aFolder)
+{
+ nsCOMPtr<nsIMsgDatabase> mailDB;
+ nsresult rv = CachedDBForFolder(aFolder, getter_AddRefs(mailDB));
+ if (mailDB)
+ {
+ mailDB->ForceClosed();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBService::GetOpenDBs(nsIArray **aOpenDBs)
+{
+ NS_ENSURE_ARG_POINTER(aOpenDBs);
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> openDBs(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ openDBs->AppendElement(m_dbCache[i], false);
+
+ openDBs.forget(aOpenDBs);
+ return NS_OK;
+}
+
+static bool gGotGlobalPrefs = false;
+static bool gThreadWithoutRe = true;
+static bool gStrictThreading = false;
+static bool gCorrectThreading = false;
+
+void nsMsgDatabase::GetGlobalPrefs()
+{
+ if (!gGotGlobalPrefs)
+ {
+ GetBoolPref("mail.thread_without_re", &gThreadWithoutRe);
+ GetBoolPref("mail.strict_threading", &gStrictThreading);
+ GetBoolPref("mail.correct_threading", &gCorrectThreading);
+ gGotGlobalPrefs = true;
+ }
+}
+
+nsresult nsMsgDatabase::AddHdrToCache(nsIMsgDBHdr *hdr, nsMsgKey key) // do we want key? We could get it from hdr
+{
+ if (m_bCacheHeaders)
+ {
+ if (!m_cachedHeaders)
+ m_cachedHeaders = new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), m_cacheSize);
+ if (m_cachedHeaders)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+ if (m_cachedHeaders->EntryCount() > m_cacheSize)
+ ClearHdrCache(true);
+ PLDHashEntryHdr *entry = m_cachedHeaders->Add((void *)(uintptr_t) key, mozilla::fallible);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory
+
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
+ element->mHdr = hdr;
+ element->mKey = key;
+ NS_ADDREF(hdr); // make the cache hold onto the header
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::SetMsgHdrCacheSize(uint32_t aSize)
+{
+ m_cacheSize = aSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrCacheSize(uint32_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+ *aSize = m_cacheSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetLastUseTime(PRTime *aTime)
+{
+ NS_ENSURE_ARG_POINTER(aTime);
+ *aTime = m_lastUseTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetLastUseTime(PRTime aTime)
+{
+ gLastUseTime = m_lastUseTime = aTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDatabaseSize(int64_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> summaryFilePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = summaryFilePath->InitWithNativePath(m_dbName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = summaryFilePath->Exists(&exists);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (exists)
+ rv = summaryFilePath->GetFileSize(_retval);
+ else
+ *_retval = 0;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ClearCachedHdrs()
+{
+ ClearCachedObjects(false);
+#ifdef DEBUG_bienvenu1
+ if (mRefCnt > 1)
+ {
+ NS_ASSERTION(false, "");
+ printf("someone's holding onto db - refs = %ld\n", mRefCnt);
+ }
+#endif
+ return NS_OK;
+}
+
+void nsMsgDatabase::ClearEnumerators()
+{
+ // clear out existing enumerators
+ nsTArray<nsMsgDBEnumerator *> copyEnumerators;
+ copyEnumerators.SwapElements(m_enumerators);
+
+ uint32_t numEnums = copyEnumerators.Length();
+ for (uint32_t i = 0; i < numEnums; i++)
+ copyEnumerators[i]->Clear();
+}
+
+nsMsgThread *nsMsgDatabase::FindExistingThread(nsMsgKey threadId)
+{
+ uint32_t numThreads = m_threads.Length();
+ for (uint32_t i = 0; i < numThreads; i++)
+ if (m_threads[i]->m_threadKey == threadId)
+ return m_threads[i];
+
+ return nullptr;
+}
+
+void nsMsgDatabase::ClearThreads()
+{
+ // clear out existing threads
+ nsTArray<nsMsgThread *> copyThreads;
+ copyThreads.SwapElements(m_threads);
+
+ uint32_t numThreads = copyThreads.Length();
+ for (uint32_t i = 0; i < numThreads; i++)
+ copyThreads[i]->Clear();
+}
+
+void nsMsgDatabase::ClearCachedObjects(bool dbGoingAway)
+{
+ ClearHdrCache(false);
+#ifdef DEBUG_DavidBienvenu
+ if (m_headersInUse && m_headersInUse->EntryCount() > 0)
+ {
+ NS_ASSERTION(false, "leaking headers");
+ printf("leaking %d headers in %s\n", m_headersInUse->EntryCount(), (const char *) m_dbName);
+ }
+#endif
+ m_cachedThread = nullptr;
+ m_cachedThreadId = nsMsgKey_None;
+ // We should only clear the use hdr cache when the db is going away, or we could
+ // end up with multiple copies of the same logical msg hdr, which will lead to
+ // ref-counting problems.
+ if (dbGoingAway)
+ {
+ ClearUseHdrCache();
+ ClearThreads();
+ }
+ m_thumb = nullptr;
+}
+
+nsresult nsMsgDatabase::ClearHdrCache(bool reInit)
+{
+ if (m_cachedHeaders)
+ {
+ // save this away in case we renter this code.
+ PLDHashTable *saveCachedHeaders = m_cachedHeaders;
+ m_cachedHeaders = nullptr;
+ for (auto iter = saveCachedHeaders->Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<MsgHdrHashElement*>(iter.Get());
+ if (element)
+ NS_IF_RELEASE(element->mHdr);
+ }
+
+ if (reInit)
+ {
+ saveCachedHeaders->ClearAndPrepareForLength(m_cacheSize);
+ m_cachedHeaders = saveCachedHeaders;
+ }
+ else
+ {
+ delete saveCachedHeaders;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::RemoveHdrFromCache(nsIMsgDBHdr *hdr, nsMsgKey key)
+{
+ if (m_cachedHeaders)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+
+ PLDHashEntryHdr *entry =
+ m_cachedHeaders->Search((const void *)(uintptr_t) key);
+ if (entry)
+ {
+ m_cachedHeaders->Remove((void *)(uintptr_t) key);
+ NS_RELEASE(hdr); // get rid of extra ref the cache was holding.
+ }
+
+ }
+ return NS_OK;
+}
+
+
+nsresult nsMsgDatabase::GetHdrFromUseCache(nsMsgKey key, nsIMsgDBHdr* *result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ *result = nullptr;
+
+ if (m_headersInUse)
+ {
+ PLDHashEntryHdr *entry =
+ m_headersInUse->Search((const void *)(uintptr_t) key);
+ if (entry)
+ {
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
+ *result = element->mHdr;
+ }
+ if (*result)
+ {
+ NS_ADDREF(*result);
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+PLDHashTableOps nsMsgDatabase::gMsgDBHashTableOps =
+{
+ HashKey,
+ MatchEntry,
+ MoveEntry,
+ ClearEntry,
+ nullptr
+};
+
+// HashKey is supposed to maximize entropy in the low order bits, and the key
+// as is, should do that.
+PLDHashNumber
+nsMsgDatabase::HashKey(const void* aKey)
+{
+ return PLDHashNumber(NS_PTR_TO_INT32(aKey));
+}
+
+bool
+nsMsgDatabase::MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey)
+{
+ const MsgHdrHashElement* hdr = static_cast<const MsgHdrHashElement*>(aEntry);
+ return aKey == (const void *)(uintptr_t) hdr->mKey; // ### or get the key from the hdr...
+}
+
+void
+nsMsgDatabase::MoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, PLDHashEntryHdr* aTo)
+{
+ const MsgHdrHashElement* from = static_cast<const MsgHdrHashElement*>(aFrom);
+ MsgHdrHashElement* to = static_cast<MsgHdrHashElement*>(aTo);
+ // ### eh? Why is this needed? I don't think we have a copy operator?
+ *to = *from;
+}
+
+void
+nsMsgDatabase::ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(aEntry);
+ element->mHdr = nullptr; // eh? Need to release this or not?
+ element->mKey = nsMsgKey_None; // eh?
+}
+
+
+nsresult nsMsgDatabase::AddHdrToUseCache(nsIMsgDBHdr *hdr, nsMsgKey key)
+{
+ if (!m_headersInUse)
+ {
+ mdb_count numHdrs = MSG_HASH_SIZE;
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
+ m_headersInUse = new PLDHashTable(&gMsgDBHashTableOps, sizeof(struct MsgHdrHashElement), std::max((mdb_count)MSG_HASH_SIZE, numHdrs));
+ }
+ if (m_headersInUse)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+ PLDHashEntryHdr *entry = m_headersInUse->Add((void *)(uintptr_t) key, mozilla::fallible);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory
+
+ MsgHdrHashElement* element = static_cast<MsgHdrHashElement*>(entry);
+ element->mHdr = hdr;
+ element->mKey = key;
+ // the hash table won't add ref, we'll do it ourselves
+ // stand for the addref that CreateMsgHdr normally does.
+ NS_ADDREF(hdr);
+ return NS_OK;
+ }
+
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult nsMsgDatabase::ClearUseHdrCache()
+{
+ if (m_headersInUse)
+ {
+ // clear mdb row pointers of any headers still in use, because the
+ // underlying db is going away.
+ for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) {
+ auto element = static_cast<const MsgHdrHashElement*>(iter.Get());
+ if (element && element->mHdr) {
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(element->mHdr); // closed system, so this is ok
+ // clear out m_mdbRow member variable - the db is going away, which means that this member
+ // variable might very well point to a mork db that is gone.
+ NS_IF_RELEASE(msgHdr->m_mdbRow);
+ // NS_IF_RELEASE(msgHdr->m_mdb);
+ }
+ }
+ delete m_headersInUse;
+ m_headersInUse = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::RemoveHdrFromUseCache(nsIMsgDBHdr *hdr, nsMsgKey key)
+{
+ if (m_headersInUse)
+ {
+ if (key == nsMsgKey_None)
+ hdr->GetMessageKey(&key);
+
+ m_headersInUse->Remove((void *)(uintptr_t) key);
+ }
+ return NS_OK;
+}
+
+
+nsresult
+nsMsgDatabase::CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key, nsIMsgDBHdr* *result)
+{
+ NS_ENSURE_ARG_POINTER(hdrRow);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv = GetHdrFromUseCache(key, result);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ hdrRow->Release();
+ return rv;
+ }
+
+ nsMsgHdr *msgHdr = new nsMsgHdr(this, hdrRow);
+ if(!msgHdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ msgHdr->SetMessageKey(key);
+ // don't need to addref here; GetHdrFromUseCache addrefs.
+ *result = msgHdr;
+
+ AddHdrToCache(msgHdr, key);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::AddListener(nsIDBChangeListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ m_ChangeListeners.AppendElementUnlessExists(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RemoveListener(nsIDBChangeListener *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ m_ChangeListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+// XXX should we return rv for listener->propertyfunc_?
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator iter(m_ChangeListeners); \
+ nsCOMPtr<nsIDBChangeListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+// change announcer methods - just broadcast to all listeners.
+NS_IMETHODIMP nsMsgDatabase::NotifyHdrChangeAll(nsIMsgDBHdr *aHdrChanged,
+ uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ // We will only notify the change if the header exists in the database.
+ // This allows database functions to be usable in both the case where the
+ // header is in the db, or the header is not so no notifications should be
+ // given.
+ nsMsgKey key;
+ bool inDb = false;
+ if (aHdrChanged)
+ {
+ aHdrChanged->GetMessageKey(&key);
+ ContainsKey(key, &inDb);
+ }
+ if (inDb)
+ NOTIFY_LISTENERS(OnHdrFlagsChanged,
+ (aHdrChanged, aOldFlags, aNewFlags, aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyReadChanged(nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnReadChanged, (aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyJunkScoreChanged(nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnJunkScoreChanged, (aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyHdrDeletedAll(nsIMsgDBHdr *aHdrDeleted,
+ nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnHdrDeleted, (aHdrDeleted, aParentKey, aFlags, aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyHdrAddedAll(nsIMsgDBHdr *aHdrAdded,
+ nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener *aInstigator)
+{
+#ifdef DEBUG_bienvenu1
+ printf("notifying add of %ld parent %ld\n", keyAdded, parentKey);
+#endif
+ NOTIFY_LISTENERS(OnHdrAdded, (aHdrAdded, aParentKey, aFlags, aInstigator));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::NotifyParentChangedAll(nsMsgKey aKeyReparented,
+ nsMsgKey aOldParent,
+ nsMsgKey aNewParent,
+ nsIDBChangeListener *aInstigator)
+{
+ NOTIFY_LISTENERS(OnParentChanged,
+ (aKeyReparented, aOldParent, aNewParent, aInstigator));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::NotifyAnnouncerGoingAway(void)
+{
+ NOTIFY_LISTENERS(OnAnnouncerGoingAway, (this));
+ return NS_OK;
+}
+
+bool nsMsgDatabase::MatchDbName(nsIFile *dbName) // returns true if they match
+{
+ nsCString dbPath;
+ dbName->GetNativePath(dbPath);
+ return dbPath.Equals(m_dbName);
+}
+
+void nsMsgDBService::AddToCache(nsMsgDatabase* pMessageDB)
+{
+#ifdef DEBUG_David_Bienvenu
+ NS_ASSERTION(m_dbCache.Length() < 50, "50 or more open db's");
+#endif
+#ifdef DEBUG
+ if (pMessageDB->m_folder)
+ {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ CachedDBForFolder(pMessageDB->m_folder, getter_AddRefs(msgDB));
+ NS_ASSERTION(!msgDB, "shouldn't have db in cache");
+ }
+#endif
+ m_dbCache.AppendElement(pMessageDB);
+}
+
+/**
+ * Log the open db's, and how many headers are in memory.
+ */
+void nsMsgDBService::DumpCache()
+{
+ nsMsgDatabase* db = nullptr;
+ MOZ_LOG(DBLog, LogLevel::Info, ("%d open DB's\n", m_dbCache.Length()));
+ for (uint32_t i = 0; i < m_dbCache.Length(); i++)
+ {
+ db = m_dbCache.ElementAt(i);
+ MOZ_LOG(DBLog, LogLevel::Info, ("%s - %ld hdrs in use\n",
+ (const char*)db->m_dbName.get(),
+ db->m_headersInUse ? db->m_headersInUse->EntryCount() : 0));
+ }
+}
+
+// Memory Reporting implementations
+
+size_t nsMsgDatabase::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t totalSize = 0;
+ if (m_dbFolderInfo)
+ totalSize += m_dbFolderInfo->SizeOfExcludingThis(aMallocSizeOf);
+ if (m_mdbEnv)
+ {
+ nsIMdbHeap *morkHeap = nullptr;
+ m_mdbEnv->GetHeap(&morkHeap);
+ if (morkHeap)
+ totalSize += morkHeap->GetUsedSize();
+ }
+ totalSize += m_newSet.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ totalSize += m_ChangeListeners.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ totalSize += m_threads.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ // We have two tables of header objects, but every header in m_cachedHeaders
+ // should be in m_headersInUse.
+ // double-counting...
+ size_t headerSize = 0;
+ if (m_headersInUse)
+ {
+ headerSize = m_headersInUse->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ for (auto iter = m_headersInUse->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<MsgHdrHashElement*>(iter.Get());
+ // Sigh, this is dangerous, but so long as this is a closed system, this
+ // is safe.
+ headerSize += static_cast<nsMsgHdr*>(entry->mHdr)->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ totalSize += headerSize;
+ if (m_msgReferences)
+ totalSize += m_msgReferences->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ return totalSize;
+}
+
+namespace mozilla {
+namespace mailnews {
+
+MOZ_DEFINE_MALLOC_SIZE_OF(GetMallocSize)
+
+class MsgDBReporter final : public nsIMemoryReporter
+{
+ nsMsgDatabase *mDatabase;
+public:
+ MsgDBReporter(nsMsgDatabase *db) : mDatabase(db) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD GetName(nsACString &aName)
+ {
+ aName.AssignLiteral("msg-database-objects");
+ return NS_OK;
+ }
+
+ NS_IMETHOD CollectReports(nsIMemoryReporterCallback*aCb,
+ nsISupports* aClosure,
+ bool aAnonymize) override
+ {
+ nsCString path;
+ GetPath(path, aAnonymize);
+ return aCb->Callback(EmptyCString(), path,
+ nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES,
+ mDatabase->SizeOfIncludingThis(GetMallocSize),
+ NS_LITERAL_CSTRING("Memory used for the folder database."),
+ aClosure);
+ }
+
+ void GetPath(nsACString &memoryPath, bool aAnonymize)
+ {
+ memoryPath.AssignLiteral("explicit/maildb/database(");
+ nsCOMPtr<nsIMsgFolder> folder;
+ mDatabase->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ {
+ if (aAnonymize)
+ memoryPath.AppendLiteral("<anonymized>");
+ else
+ {
+ nsAutoCString folderURL;
+ folder->GetFolderURL(folderURL);
+ MsgReplaceChar(folderURL, '/', '\\');
+ memoryPath += folderURL;
+ }
+ } else {
+ memoryPath.AppendLiteral("UNKNOWN-FOLDER");
+ }
+ memoryPath.Append(')');
+ }
+
+private:
+ ~MsgDBReporter() {}
+};
+
+NS_IMPL_ISUPPORTS(MsgDBReporter, nsIMemoryReporter)
+}
+}
+
+nsMsgDatabase::nsMsgDatabase()
+ : m_dbFolderInfo(nullptr),
+ m_nextPseudoMsgKey(kFirstPseudoKey),
+ m_mdbEnv(nullptr), m_mdbStore(nullptr),
+ m_mdbAllMsgHeadersTable(nullptr), m_mdbAllThreadsTable(nullptr),
+ m_create(false),
+ m_leaveInvalidDB(false),
+ m_dbName(""),
+ m_mdbTokensInitialized(false),
+ m_hdrRowScopeToken(0),
+ m_hdrTableKindToken(0),
+ m_threadTableKindToken(0),
+ m_subjectColumnToken(0),
+ m_senderColumnToken(0),
+ m_messageIdColumnToken(0),
+ m_referencesColumnToken(0),
+ m_recipientsColumnToken(0),
+ m_dateColumnToken(0),
+ m_messageSizeColumnToken(0),
+ m_flagsColumnToken(0),
+ m_priorityColumnToken(0),
+ m_labelColumnToken(0),
+ m_statusOffsetColumnToken(0),
+ m_numLinesColumnToken(0),
+ m_ccListColumnToken(0),
+ m_bccListColumnToken(0),
+ m_threadFlagsColumnToken(0),
+ m_threadIdColumnToken(0),
+ m_threadChildrenColumnToken(0),
+ m_threadUnreadChildrenColumnToken(0),
+ m_messageThreadIdColumnToken(0),
+ m_threadSubjectColumnToken(0),
+ m_messageCharSetColumnToken(0),
+ m_threadParentColumnToken(0),
+ m_threadRootKeyColumnToken(0),
+ m_threadNewestMsgDateColumnToken(0),
+ m_offlineMsgOffsetColumnToken(0),
+ m_offlineMessageSizeColumnToken(0),
+ m_headersInUse(nullptr),
+ m_cachedHeaders(nullptr),
+ m_bCacheHeaders(true),
+ m_cachedThreadId(nsMsgKey_None),
+ m_msgReferences(nullptr),
+ m_cacheSize(kMaxHdrsInCache)
+{
+ mMemReporter = new mozilla::mailnews::MsgDBReporter(this);
+ mozilla::RegisterWeakMemoryReporter(mMemReporter);
+}
+
+nsMsgDatabase::~nsMsgDatabase()
+{
+ mozilla::UnregisterWeakMemoryReporter(mMemReporter);
+ // Close(FALSE); // better have already been closed.
+ ClearCachedObjects(true);
+ ClearEnumerators();
+ delete m_cachedHeaders;
+ delete m_headersInUse;
+
+ if (m_msgReferences)
+ {
+ delete m_msgReferences;
+ m_msgReferences = nullptr;
+ }
+
+ MOZ_LOG(DBLog, LogLevel::Info, ("closing database %s\n",
+ (const char*)m_dbName.get()));
+
+ nsCOMPtr<nsIMsgDBService> serv(do_GetService(NS_MSGDB_SERVICE_CONTRACTID));
+ if (serv)
+ static_cast<nsMsgDBService*>(serv.get())->RemoveFromCache(this);
+
+ // if the db folder info refers to the mdb db, we must clear it because
+ // the reference will be a dangling one soon.
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->ReleaseExternalReferences();
+
+ NS_IF_RELEASE(m_dbFolderInfo);
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->Release();
+
+ if (m_mdbAllThreadsTable)
+ m_mdbAllThreadsTable->Release();
+
+ if (m_mdbStore)
+ m_mdbStore->Release();
+
+ if (m_mdbEnv)
+ {
+ m_mdbEnv->Release(); //??? is this right?
+ m_mdbEnv = nullptr;
+ }
+ m_ChangeListeners.Clear();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDatabase, nsIMsgDatabase, nsIDBChangeAnnouncer)
+
+void nsMsgDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory)
+{
+ if (!mMdbFactory)
+ {
+ nsresult rv;
+ nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && mdbFactoryService)
+ mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
+ }
+ NS_IF_ADDREF(*aMdbFactory = mMdbFactory);
+}
+
+// aLeaveInvalidDB: true if caller wants back a db even out of date.
+// If so, they'll extract out the interesting info from the db, close it,
+// delete it, and then try to open the db again, prior to reparsing.
+nsresult nsMsgDatabase::Open(nsMsgDBService *aDBService, nsIFile *aFolderName,
+ bool aCreate, bool aLeaveInvalidDB)
+{
+ return nsMsgDatabase::OpenInternal(aDBService, aFolderName, aCreate, aLeaveInvalidDB,
+ true /* open synchronously */);
+}
+
+nsresult nsMsgDatabase::OpenInternal(nsMsgDBService *aDBService,
+ nsIFile *summaryFile, bool aCreate,
+ bool aLeaveInvalidDB, bool sync)
+{
+ nsAutoCString summaryFilePath;
+ summaryFile->GetNativePath(summaryFilePath);
+
+ MOZ_LOG(DBLog, LogLevel::Info, ("nsMsgDatabase::Open(%s, %s, %p, %s)\n",
+ (const char*)summaryFilePath.get(), aCreate ? "TRUE":"FALSE",
+ this, aLeaveInvalidDB ? "TRUE":"FALSE"));
+
+
+ nsresult rv = OpenMDB(summaryFilePath.get(), aCreate, sync);
+ if (NS_FAILED(rv))
+ MOZ_LOG(DBLog, LogLevel::Info, ("error opening db %lx", rv));
+
+ if (MOZ_LOG_TEST(DBLog, LogLevel::Debug))
+ aDBService->DumpCache();
+
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return rv;
+
+ m_create = aCreate;
+ m_leaveInvalidDB = aLeaveInvalidDB;
+ if (!sync && NS_SUCCEEDED(rv))
+ {
+ aDBService->AddToCache(this);
+ // remember open options for when the parsing is complete.
+ return rv;
+ }
+ return CheckForErrors(rv, true, aDBService, summaryFile);
+}
+
+nsresult nsMsgDatabase::CheckForErrors(nsresult err, bool sync,
+ nsMsgDBService *aDBService,
+ nsIFile *summaryFile)
+{
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ bool summaryFileExists;
+ bool newFile = false;
+ bool deleteInvalidDB = false;
+
+ bool exists;
+ int64_t fileSize;
+ summaryFile->Exists(&exists);
+ summaryFile->GetFileSize(&fileSize);
+ // if the old summary doesn't exist, we're creating a new one.
+ if ((!exists || !fileSize) && m_create)
+ newFile = true;
+
+ summaryFileExists = exists && fileSize > 0;
+
+ if (NS_SUCCEEDED(err))
+ {
+ if (!m_dbFolderInfo)
+ {
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ else
+ {
+ if (!newFile && summaryFileExists)
+ {
+ bool valid = false;
+ nsresult rv = GetSummaryValid(&valid);
+ if (NS_FAILED(rv) || !valid)
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ // compare current version of db versus filed out version info.
+ uint32_t version;
+ m_dbFolderInfo->GetVersion(&version);
+ if (GetCurVersion() != version)
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+
+ // Check if we should force a reparse because, for example, we have
+ // reached the key limit.
+ bool forceReparse;
+ m_dbFolderInfo->GetBooleanProperty("forceReparse", false, &forceReparse);
+ if (forceReparse)
+ {
+ NS_WARNING("Forcing a reparse presumably because key limit reached");
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+
+ }
+ if (NS_FAILED(err) && !m_leaveInvalidDB)
+ deleteInvalidDB = true;
+ }
+ else
+ {
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ deleteInvalidDB = true;
+ }
+
+ if (deleteInvalidDB)
+ {
+ // this will make the db folder info release its ref to the mail db...
+ NS_IF_RELEASE(m_dbFolderInfo);
+ ForceClosed();
+ if (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ summaryFile->Remove(false);
+ }
+ if (NS_FAILED(err) || newFile)
+ {
+ // if we couldn't open file, or we have a blank one, and we're supposed
+ // to upgrade, updgrade it.
+ if (newFile && !m_leaveInvalidDB) // caller is upgrading, and we have empty summary file,
+ { // leave db around and open so caller can upgrade it.
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
+ }
+ else if (NS_FAILED(err) && err != NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ {
+ Close(false);
+ summaryFile->Remove(false); // blow away the db if it's corrupt.
+ }
+ }
+ if (sync && (NS_SUCCEEDED(err) || err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING))
+ aDBService->AddToCache(this);
+ return (summaryFileExists) ? err : NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
+}
+
+/**
+ * Open the MDB database synchronously or async based on sync argument.
+ * If successful, this routine will set up the m_mdbStore and m_mdbEnv of
+ * the database object so other database calls can work.
+ */
+nsresult nsMsgDatabase::OpenMDB(const char *dbName, bool create, bool sync)
+{
+ nsresult ret = NS_OK;
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ GetMDBFactory(getter_AddRefs(mdbFactory));
+ if (mdbFactory)
+ {
+ ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
+ if (NS_SUCCEEDED(ret))
+ {
+ nsIMdbHeap* dbHeap = nullptr;
+
+ if (m_mdbEnv)
+ m_mdbEnv->SetAutoClear(true);
+ m_dbName = dbName;
+ bool exists = false;
+ nsCOMPtr<nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &ret);
+ if (NS_SUCCEEDED(ret) && dbFile) {
+ ret = dbFile->InitWithNativePath(m_dbName);
+ if (NS_SUCCEEDED(ret))
+ ret = dbFile->Exists(&exists);
+ }
+ if (!exists)
+ {
+ ret = NS_MSG_ERROR_FOLDER_SUMMARY_MISSING;
+ }
+ // If m_thumb is set, we're asynchronously opening the db already.
+ else if (!m_thumb)
+ {
+ mdbOpenPolicy inOpenPolicy;
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+
+ nsIMdbFile* oldFile = nullptr;
+ ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, dbName,
+ mdbBool_kFalse, // not readonly, we want modifiable
+ &oldFile);
+ if (oldFile)
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // the file to investigate
+ &canOpen, &outFormatVersion);
+ if (NS_SUCCEEDED(ret) && canOpen)
+ {
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap,
+ oldFile, &inOpenPolicy, getter_AddRefs(m_thumb));
+ }
+ else
+ ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ NS_RELEASE(oldFile); // always release our file ref, store has own
+ }
+ }
+ if (NS_SUCCEEDED(ret) && m_thumb && sync)
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do
+ {
+ ret = m_thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
+ if (NS_FAILED(ret))
+ {// mork isn't really doing NS errors yet.
+ outDone = true;
+ break;
+ }
+ }
+ while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
+ // m_mdbEnv->ClearErrors(); // ### temporary...
+ // only 0 is a non-error return.
+ if (NS_SUCCEEDED(ret) && outDone)
+ {
+ ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, m_thumb, &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = (m_mdbStore) ? InitExistingDB() : NS_ERROR_FAILURE;
+ }
+#ifdef DEBUG_bienvenu1
+ DumpContents();
+#endif
+ m_thumb = nullptr;
+ }
+ else if (create) // ### need error code saying why open file store failed
+ {
+ nsIMdbFile* newFile = 0;
+ ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, dbName, &newFile);
+ if (NS_FAILED(ret))
+ ret = NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ if ( newFile )
+ {
+ if (NS_SUCCEEDED(ret))
+ {
+ mdbOpenPolicy inOpenPolicy;
+
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap,
+ newFile, &inOpenPolicy, &m_mdbStore);
+ if (NS_SUCCEEDED(ret))
+ ret = (m_mdbStore) ? InitNewDB() : NS_ERROR_FAILURE;
+ }
+ NS_RELEASE(newFile); // always release our file ref, store has own
+ }
+ }
+ }
+ }
+#ifdef DEBUG_David_Bienvenu
+// NS_ASSERTION(NS_SUCCEEDED(ret), "failed opening mdb");
+#endif
+ return ret;
+}
+
+nsresult nsMsgDatabase::CloseMDB(bool commit)
+{
+ if (commit)
+ Commit(nsMsgDBCommitType::kSessionCommit);
+ return(NS_OK);
+}
+
+
+// force the database to close - this'll flush out anybody holding onto
+// a database without having a listener!
+// This is evil in the com world, but there are times we need to delete the file.
+NS_IMETHODIMP nsMsgDatabase::ForceClosed()
+{
+ nsresult err = NS_OK;
+
+ // make sure someone has a reference so object won't get deleted out from under us.
+ AddRef();
+ NotifyAnnouncerGoingAway();
+ // make sure dbFolderInfo isn't holding onto mork stuff because mork db is going away
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->ReleaseExternalReferences();
+ NS_IF_RELEASE(m_dbFolderInfo);
+
+ err = CloseMDB(true); // Backup DB will try to recover info, so commit
+ ClearCachedObjects(true);
+ ClearEnumerators();
+if (m_mdbAllMsgHeadersTable)
+ {
+ m_mdbAllMsgHeadersTable->Release();
+ m_mdbAllMsgHeadersTable = nullptr;
+ }
+ if (m_mdbAllThreadsTable)
+ {
+ m_mdbAllThreadsTable->Release();
+ m_mdbAllThreadsTable = nullptr;
+ }
+ if (m_mdbStore)
+ {
+ m_mdbStore->Release();
+ m_mdbStore = nullptr;
+ }
+
+ // better not be any listeners, because we're going away.
+ NS_ASSERTION(m_ChangeListeners.IsEmpty(), "shouldn't have any listeners left");
+
+ Release();
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDBFolderInfo(nsIDBFolderInfo **result)
+{
+ if (!m_dbFolderInfo)
+ {
+ NS_ERROR("db must be corrupt");
+ return NS_ERROR_NULL_POINTER;
+ }
+ NS_ADDREF(*result = m_dbFolderInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::Commit(nsMsgDBCommit commitType)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIMdbThumb> commitThumb;
+
+ RememberLastUseTime();
+ if (commitType == nsMsgDBCommitType::kLargeCommit || commitType == nsMsgDBCommitType::kSessionCommit)
+ {
+ mdb_percent outActualWaste = 0;
+ mdb_bool outShould;
+ if (m_mdbStore) {
+ err = m_mdbStore->ShouldCompress(GetEnv(), 30, &outActualWaste, &outShould);
+ if (NS_SUCCEEDED(err) && outShould)
+ commitType = nsMsgDBCommitType::kCompressCommit;
+ }
+ }
+ // commitType = nsMsgDBCommitType::kCompressCommit; // ### until incremental writing works.
+
+ if (m_mdbStore)
+ {
+ switch (commitType)
+ {
+ case nsMsgDBCommitType::kLargeCommit:
+ err = m_mdbStore->LargeCommit(GetEnv(), getter_AddRefs(commitThumb));
+ break;
+ case nsMsgDBCommitType::kSessionCommit:
+ err = m_mdbStore->SessionCommit(GetEnv(), getter_AddRefs(commitThumb));
+ break;
+ case nsMsgDBCommitType::kCompressCommit:
+ err = m_mdbStore->CompressCommit(GetEnv(), getter_AddRefs(commitThumb));
+ break;
+ }
+ }
+ if (commitThumb)
+ {
+ mdb_count outTotal = 0; // total somethings to do in operation
+ mdb_count outCurrent = 0; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken = false; // is operation irreparably dead and broken?
+ while (!outDone && !outBroken && NS_SUCCEEDED(err))
+ {
+ err = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken);
+ }
+
+ }
+ // ### do something with error, but clear it now because mork errors out on commits.
+ if (GetEnv())
+ GetEnv()->ClearErrors();
+
+ 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)
+ {
+ nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
+ rv = folderCache->GetCacheElement(m_dbName, false, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement && m_dbFolderInfo)
+ {
+ int32_t totalMessages, unreadMessages, pendingMessages, pendingUnreadMessages;
+
+ m_dbFolderInfo->GetNumMessages(&totalMessages);
+ m_dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
+ m_dbFolderInfo->GetImapUnreadPendingMessages(&pendingUnreadMessages);
+ m_dbFolderInfo->GetImapTotalPendingMessages(&pendingMessages);
+ cacheElement->SetInt32Property("totalMsgs", totalMessages);
+ cacheElement->SetInt32Property("totalUnreadMsgs", unreadMessages);
+ cacheElement->SetInt32Property("pendingMsgs", pendingMessages);
+ cacheElement->SetInt32Property("pendingUnreadMsgs", pendingUnreadMessages);
+ folderCache->Commit(false);
+ }
+ }
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::Close(bool forceCommit /* = TRUE */)
+{
+ return CloseMDB(forceCommit);
+}
+
+const char *kMsgHdrsScope = "ns:msg:db:row:scope:msgs:all"; // scope for all headers table
+const char *kMsgHdrsTableKind = "ns:msg:db:table:kind:msgs";
+const char *kThreadTableKind = "ns:msg:db:table:kind:thread";
+const char *kThreadHdrsScope = "ns:msg:db:row:scope:threads:all"; // scope for all threads table
+const char *kAllThreadsTableKind = "ns:msg:db:table:kind:allthreads"; // kind for table of all threads
+const char *kSubjectColumnName = "subject";
+const char *kSenderColumnName = "sender";
+const char *kMessageIdColumnName = "message-id";
+const char *kReferencesColumnName = "references";
+const char *kRecipientsColumnName = "recipients";
+const char *kDateColumnName = "date";
+const char *kMessageSizeColumnName = "size";
+const char *kFlagsColumnName = "flags";
+const char *kPriorityColumnName = "priority";
+const char *kLabelColumnName = "label";
+const char *kStatusOffsetColumnName = "statusOfset";
+const char *kNumLinesColumnName = "numLines";
+const char *kCCListColumnName = "ccList";
+const char *kBCCListColumnName = "bccList";
+const char *kMessageThreadIdColumnName = "msgThreadId";
+const char *kThreadFlagsColumnName = "threadFlags";
+const char *kThreadIdColumnName = "threadId";
+const char *kThreadChildrenColumnName = "children";
+const char *kThreadUnreadChildrenColumnName = "unreadChildren";
+const char *kThreadSubjectColumnName = "threadSubject";
+const char *kMessageCharSetColumnName = "msgCharSet";
+const char *kThreadParentColumnName = "threadParent";
+const char *kThreadRootColumnName = "threadRoot";
+const char *kThreadNewestMsgDateColumnName = "threadNewestMsgDate";
+const char *kOfflineMsgOffsetColumnName = "msgOffset";
+const char *kOfflineMsgSizeColumnName = "offlineMsgSize";
+struct mdbOid gAllMsgHdrsTableOID;
+struct mdbOid gAllThreadsTableOID;
+const char *kFixedBadRefThreadingProp = "fixedBadRefThreading";
+
+// set up empty tables, dbFolderInfo, etc.
+nsresult nsMsgDatabase::InitNewDB()
+{
+ nsresult err = NS_OK;
+
+ err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ nsDBFolderInfo *dbFolderInfo = new nsDBFolderInfo(this);
+ if (dbFolderInfo)
+ {
+ NS_ADDREF(dbFolderInfo);
+ err = dbFolderInfo->AddToNewMDB();
+ dbFolderInfo->SetVersion(GetCurVersion());
+ dbFolderInfo->SetBooleanProperty("forceReparse", false);
+ dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true);
+ nsIMdbStore *store = GetStore();
+ // create the unique table for the dbFolderInfo.
+ struct mdbOid allMsgHdrsTableOID;
+ struct mdbOid allThreadsTableOID;
+ if (!store)
+ return NS_ERROR_NULL_POINTER;
+
+ allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
+ allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ allThreadsTableOID.mOid_Id = kAllThreadsTableKey;
+
+ // TODO: check this error value?
+ (void) store->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken,
+ false, nullptr, &m_mdbAllMsgHeadersTable);
+
+ // error here is not fatal.
+ (void) store->NewTableWithOid(GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken,
+ false, nullptr, &m_mdbAllThreadsTable);
+
+ m_dbFolderInfo = dbFolderInfo;
+
+ }
+ else
+ err = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::GetTableCreateIfMissing(const char *scope, const char *kind, nsIMdbTable **table,
+ mdb_token &scopeToken, mdb_token &kindToken)
+{
+ struct mdbOid tableOID;
+
+ if (!m_mdbStore)
+ return NS_ERROR_FAILURE;
+ (void) m_mdbStore->StringToToken(GetEnv(), scope, &scopeToken);
+ (void) m_mdbStore->StringToToken(GetEnv(), kind, &kindToken);
+ tableOID.mOid_Scope = scopeToken;
+ tableOID.mOid_Id = 1;
+
+ nsresult rv = m_mdbStore->GetTable(GetEnv(), &tableOID, table);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // create new all all offline ops table, if it doesn't exist.
+ if (NS_SUCCEEDED(rv) && !*table)
+ {
+ rv = m_mdbStore->NewTable(GetEnv(), scopeToken,kindToken,
+ false, nullptr, table);
+ if (NS_FAILED(rv) || !*table)
+ rv = NS_ERROR_FAILURE;
+ }
+ NS_ASSERTION(NS_SUCCEEDED(rv), "couldn't create offline ops table");
+ return rv;
+}
+
+nsresult nsMsgDatabase::InitExistingDB()
+{
+ nsresult err = NS_OK;
+
+ err = InitMDBInfo();
+ if (NS_SUCCEEDED(err))
+ {
+ err = GetStore()->GetTable(GetEnv(), &gAllMsgHdrsTableOID, &m_mdbAllMsgHeadersTable);
+ if (NS_SUCCEEDED(err))
+ {
+ m_dbFolderInfo = new nsDBFolderInfo(this);
+ if (m_dbFolderInfo)
+ {
+ NS_ADDREF(m_dbFolderInfo);
+ err = m_dbFolderInfo->InitFromExistingDB();
+ }
+ }
+ else
+ err = NS_ERROR_FAILURE;
+
+ NS_ASSERTION(NS_SUCCEEDED(err), "failed initing existing db");
+ NS_ENSURE_SUCCESS(err, err);
+ // create new all msg hdrs table, if it doesn't exist.
+ if (NS_SUCCEEDED(err) && !m_mdbAllMsgHeadersTable)
+ {
+ struct mdbOid allMsgHdrsTableOID;
+ allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ allMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
+
+ nsresult mdberr = GetStore()->NewTableWithOid(GetEnv(), &allMsgHdrsTableOID, m_hdrTableKindToken,
+ false, nullptr, &m_mdbAllMsgHeadersTable);
+ if (NS_FAILED(mdberr) || !m_mdbAllMsgHeadersTable)
+ err = NS_ERROR_FAILURE;
+ }
+ struct mdbOid allThreadsTableOID;
+ allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ allThreadsTableOID.mOid_Id = kAllThreadsTableKey;
+ err = GetStore()->GetTable(GetEnv(), &gAllThreadsTableOID, &m_mdbAllThreadsTable);
+ if (!m_mdbAllThreadsTable)
+ {
+
+ nsresult mdberr = GetStore()->NewTableWithOid(GetEnv(), &allThreadsTableOID, m_allThreadsTableKindToken,
+ false, nullptr, &m_mdbAllThreadsTable);
+ if (NS_FAILED(mdberr) || !m_mdbAllThreadsTable)
+ err = NS_ERROR_FAILURE;
+ }
+ }
+ if (NS_SUCCEEDED(err) && m_dbFolderInfo)
+ {
+ bool fixedBadRefThreading;
+ m_dbFolderInfo->GetBooleanProperty(kFixedBadRefThreadingProp, false, &fixedBadRefThreading);
+ if (!fixedBadRefThreading)
+ {
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ err = EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(err) && enumerator)
+ {
+ bool hasMore;
+
+ while (NS_SUCCEEDED(err = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ err = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(err), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(supports);
+ if (msgHdr && NS_SUCCEEDED(err))
+ {
+ nsCString messageId;
+ nsAutoCString firstReference;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ msgHdr->GetStringReference(0, firstReference);
+ if (messageId.Equals(firstReference))
+ {
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ break;
+ }
+ }
+ }
+ }
+
+ m_dbFolderInfo->SetBooleanProperty(kFixedBadRefThreadingProp, true);
+ }
+
+ }
+ return err;
+}
+
+// initialize the various tokens and tables in our db's env
+nsresult nsMsgDatabase::InitMDBInfo()
+{
+ nsresult err = NS_OK;
+
+ if (!m_mdbTokensInitialized && GetStore())
+ {
+ m_mdbTokensInitialized = true;
+ err = GetStore()->StringToToken(GetEnv(), kMsgHdrsScope, &m_hdrRowScopeToken);
+ if (NS_SUCCEEDED(err))
+ {
+ GetStore()->StringToToken(GetEnv(), kSubjectColumnName, &m_subjectColumnToken);
+ GetStore()->StringToToken(GetEnv(), kSenderColumnName, &m_senderColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageIdColumnName, &m_messageIdColumnToken);
+ // if we just store references as a string, we won't get any savings from the
+ // fact there's a lot of duplication. So we may want to break them up into
+ // multiple columns, r1, r2, etc.
+ GetStore()->StringToToken(GetEnv(), kReferencesColumnName, &m_referencesColumnToken);
+ // similarly, recipients could be tokenized properties
+ GetStore()->StringToToken(GetEnv(), kRecipientsColumnName, &m_recipientsColumnToken);
+ GetStore()->StringToToken(GetEnv(), kDateColumnName, &m_dateColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageSizeColumnName, &m_messageSizeColumnToken);
+ GetStore()->StringToToken(GetEnv(), kFlagsColumnName, &m_flagsColumnToken);
+ GetStore()->StringToToken(GetEnv(), kPriorityColumnName, &m_priorityColumnToken);
+ GetStore()->StringToToken(GetEnv(), kLabelColumnName, &m_labelColumnToken);
+ GetStore()->StringToToken(GetEnv(), kStatusOffsetColumnName, &m_statusOffsetColumnToken);
+ GetStore()->StringToToken(GetEnv(), kNumLinesColumnName, &m_numLinesColumnToken);
+ GetStore()->StringToToken(GetEnv(), kCCListColumnName, &m_ccListColumnToken);
+ GetStore()->StringToToken(GetEnv(), kBCCListColumnName, &m_bccListColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageThreadIdColumnName, &m_messageThreadIdColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadIdColumnName, &m_threadIdColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadFlagsColumnName, &m_threadFlagsColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadNewestMsgDateColumnName, &m_threadNewestMsgDateColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadChildrenColumnName, &m_threadChildrenColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadUnreadChildrenColumnName, &m_threadUnreadChildrenColumnToken);
+ GetStore()->StringToToken(GetEnv(), kThreadSubjectColumnName, &m_threadSubjectColumnToken);
+ GetStore()->StringToToken(GetEnv(), kMessageCharSetColumnName, &m_messageCharSetColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kMsgHdrsTableKind, &m_hdrTableKindToken);
+ if (NS_SUCCEEDED(err))
+ err = GetStore()->StringToToken(GetEnv(), kThreadTableKind, &m_threadTableKindToken);
+ err = GetStore()->StringToToken(GetEnv(), kAllThreadsTableKind, &m_allThreadsTableKindToken);
+ err = GetStore()->StringToToken(GetEnv(), kThreadHdrsScope, &m_threadRowScopeToken);
+ err = GetStore()->StringToToken(GetEnv(), kThreadParentColumnName, &m_threadParentColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kThreadRootColumnName, &m_threadRootKeyColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kOfflineMsgOffsetColumnName, &m_offlineMsgOffsetColumnToken);
+ err = GetStore()->StringToToken(GetEnv(), kOfflineMsgSizeColumnName, &m_offlineMessageSizeColumnToken);
+
+ if (NS_SUCCEEDED(err))
+ {
+ // The table of all message hdrs will have table id 1.
+ gAllMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ gAllMsgHdrsTableOID.mOid_Id = kAllMsgHdrsTableKey;
+ gAllThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ gAllThreadsTableOID.mOid_Id = kAllThreadsTableKey;
+
+ }
+ }
+ }
+ return err;
+}
+
+// Returns if the db contains this key
+NS_IMETHODIMP nsMsgDatabase::ContainsKey(nsMsgKey key, bool *containsKey)
+{
+
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+
+ if (!containsKey || !m_mdbAllMsgHeadersTable)
+ return NS_ERROR_NULL_POINTER;
+ *containsKey = false;
+
+ rowObjectId.mOid_Id = key;
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ if(NS_SUCCEEDED(err))
+ *containsKey = hasOid;
+
+ return err;
+}
+
+// get a message header for the given key. Caller must release()!
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForKey(nsMsgKey key, nsIMsgDBHdr **pmsgHdr)
+{
+ nsresult err = NS_OK;
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+
+ // Because this may be called a lot, and we don't want gettimeofday() to show
+ // up in trace logs, we just remember the most recent time any db was used,
+ // which should be close enough for our purposes.
+ m_lastUseTime = gLastUseTime;
+
+#ifdef DEBUG_bienvenu1
+ NS_ASSERTION(m_folder, "folder should be set");
+#endif
+
+ if (!pmsgHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ *pmsgHdr = NULL;
+ err = GetHdrFromUseCache(key, pmsgHdr);
+ if (NS_SUCCEEDED(err) && *pmsgHdr)
+ return err;
+
+ rowObjectId.mOid_Id = key;
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ err = m_mdbAllMsgHeadersTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ if (NS_SUCCEEDED(err) /* && hasOid */)
+ {
+ nsIMdbRow *hdrRow;
+ err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, &hdrRow);
+
+ if (NS_SUCCEEDED(err))
+ {
+ if (!hdrRow)
+ {
+ err = NS_ERROR_NULL_POINTER;
+ }
+ else
+ {
+ // NS_ASSERTION(hasOid, "we had oid, right?");
+ err = CreateMsgHdr(hdrRow, key, pmsgHdr);
+ }
+ }
+ }
+
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::StartBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::EndBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::DeleteMessage(nsMsgKey key, nsIDBChangeListener *instigator, bool commit)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (!msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = DeleteHeader(msgHdr, instigator, commit, true);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::DeleteMessages(uint32_t aNumKeys, nsMsgKey* nsMsgKeys, nsIDBChangeListener *instigator)
+{
+ nsresult err = NS_OK;
+
+ uint32_t kindex;
+ for (kindex = 0; kindex < aNumKeys; kindex++)
+ {
+ nsMsgKey key = nsMsgKeys[kindex];
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ bool hasKey;
+
+ if (NS_SUCCEEDED(ContainsKey(key, &hasKey)) && hasKey)
+ {
+ err = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(err))
+ {
+ err = NS_MSG_MESSAGE_NOT_FOUND;
+ break;
+ }
+ if (msgHdr)
+ err = DeleteHeader(msgHdr, instigator, kindex % 300 == 0, true);
+ if (NS_FAILED(err))
+ break;
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t size = 0;
+ (void)msgHdr->GetMessageSize(&size);
+ return m_dbFolderInfo->ChangeExpungedBytes (size);
+}
+
+NS_IMETHODIMP nsMsgDatabase::DeleteHeader(nsIMsgDBHdr *msg, nsIDBChangeListener *instigator, bool commit, bool notify)
+{
+ if (!msg)
+ return NS_ERROR_NULL_POINTER;
+
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, so this is ok
+ nsMsgKey key;
+ (void)msg->GetMessageKey(&key);
+ // only need to do this for mail - will this speed up news expiration?
+ SetHdrFlag(msg, true, nsMsgMessageFlags::Expunged); // tell mailbox (mail)
+
+ bool hdrWasNew = m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex;
+ m_newSet.RemoveElement(key);
+
+ if (m_dbFolderInfo != NULL)
+ {
+ bool isRead;
+ m_dbFolderInfo->ChangeNumMessages(-1);
+ IsRead(key, &isRead);
+ if (!isRead)
+ m_dbFolderInfo->ChangeNumUnreadMessages(-1);
+ AdjustExpungedBytesOnDelete(msg);
+ }
+
+ uint32_t flags;
+ nsMsgKey threadParent;
+
+ //Save off flags and threadparent since they will no longer exist after we remove the header from the db.
+ if (notify)
+ {
+ (void)msg->GetFlags(&flags);
+ msg->GetThreadParent(&threadParent);
+ }
+
+ RemoveHeaderFromThread(msgHdr);
+ if (notify)
+ {
+ // If deleted hdr was new, restore the new flag on flags
+ // so saved searches will know to reduce their new msg count.
+ if (hdrWasNew)
+ flags |= nsMsgMessageFlags::New;
+ NotifyHdrDeletedAll(msg, threadParent, flags, instigator); // tell listeners
+ }
+ // if (!onlyRemoveFromThread) // to speed up expiration, try this. But really need to do this in RemoveHeaderFromDB
+ nsresult ret = RemoveHeaderFromDB(msgHdr);
+
+
+ if (commit)
+ Commit(nsMsgDBCommitType::kLargeCommit); // ### dmb is this a good time to commit?
+ return ret;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::UndoDelete(nsIMsgDBHdr *aMsgHdr)
+{
+ if (aMsgHdr)
+ {
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aMsgHdr); // closed system, so this is ok
+ // force deleted flag, so SetHdrFlag won't bail out because deleted flag isn't set
+ msgHdr->m_flags |= nsMsgMessageFlags::Expunged;
+ SetHdrFlag(msgHdr, false, nsMsgMessageFlags::Expunged); // clear deleted flag in db
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::RemoveHeaderFromThread(nsMsgHdr *msgHdr)
+{
+ if (!msgHdr)
+ return NS_ERROR_NULL_POINTER;
+ nsresult ret = NS_OK;
+ nsCOMPtr <nsIMsgThread> thread ;
+ ret = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(ret) && thread)
+ {
+ nsCOMPtr <nsIDBChangeAnnouncer> announcer = do_QueryInterface(this);
+ ret = thread->RemoveChildHdr(msgHdr, announcer);
+ }
+ return ret;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RemoveHeaderMdbRow(nsIMsgDBHdr *msg)
+{
+ NS_ENSURE_ARG_POINTER(msg);
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, so this is ok
+ return RemoveHeaderFromDB(msgHdr);
+}
+
+// This is a lower level routine which doesn't send notifcations or
+// update folder info. One use is when a rule fires moving a header
+// from one db to another, to remove it from the first db.
+
+nsresult nsMsgDatabase::RemoveHeaderFromDB(nsMsgHdr *msgHdr)
+{
+ if (!msgHdr)
+ return NS_ERROR_NULL_POINTER;
+ nsresult ret = NS_OK;
+
+ RemoveHdrFromCache(msgHdr, nsMsgKey_None);
+ if (UseCorrectThreading())
+ RemoveMsgRefsFromHash(msgHdr);
+ nsIMdbRow* row = msgHdr->GetMDBRow();
+ if (row)
+ {
+ ret = m_mdbAllMsgHeadersTable->CutRow(GetEnv(), row);
+ row->CutAllColumns(GetEnv());
+ }
+ msgHdr->m_initedValues = 0; // invalidate cached values.
+ return ret;
+}
+
+nsresult nsMsgDatabase::IsRead(nsMsgKey key, bool *pRead)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+ rv = IsHeaderRead(msgHdr, pRead);
+ return rv;
+}
+
+uint32_t nsMsgDatabase::GetStatusFlags(nsIMsgDBHdr *msgHdr, uint32_t origFlags)
+{
+ uint32_t statusFlags = origFlags;
+ bool isRead = true;
+
+ nsMsgKey key;
+ (void)msgHdr->GetMessageKey(&key);
+ if ((!m_newSet.IsEmpty() && m_newSet[m_newSet.Length() - 1] == key) ||
+ (m_newSet.BinaryIndexOf(key) != m_newSet.NoIndex))
+ statusFlags |= nsMsgMessageFlags::New;
+ if (NS_SUCCEEDED(IsHeaderRead(msgHdr, &isRead)) && isRead)
+ statusFlags |= nsMsgMessageFlags::Read;
+ return statusFlags;
+}
+
+nsresult nsMsgDatabase::IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead)
+{
+ if (!msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsMsgHdr* hdr = static_cast<nsMsgHdr*>(msgHdr); // closed system, cast ok
+ // can't call GetFlags, because it will be recursive.
+ uint32_t flags;
+ hdr->GetRawFlags(&flags);
+ *pRead = !!(flags & nsMsgMessageFlags::Read);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsMarked(nsMsgKey key, bool *pMarked)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pMarked = !!(flags & nsMsgMessageFlags::Marked);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsIgnored(nsMsgKey key, bool *pIgnored)
+{
+ NS_ENSURE_ARG_POINTER(pIgnored);
+
+ nsCOMPtr <nsIMsgThread> threadHdr;
+
+ nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr));
+ // This should be very surprising, but we leave that up to the caller
+ // to determine for now.
+ if (!threadHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ *pIgnored = !!(threadFlags & nsMsgMessageFlags::Ignored);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsWatched(nsMsgKey key, bool *pWatched)
+{
+ NS_ENSURE_ARG_POINTER(pWatched);
+
+ nsCOMPtr <nsIMsgThread> threadHdr;
+
+ nsresult rv = GetThreadForMsgKey(key, getter_AddRefs(threadHdr));
+ // This should be very surprising, but we leave that up to the caller
+ // to determine for now.
+ if (!threadHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ *pWatched = !!(threadFlags & nsMsgMessageFlags::Watched);
+ return rv;
+}
+
+nsresult nsMsgDatabase::HasAttachments(nsMsgKey key, bool *pHasThem)
+{
+ NS_ENSURE_ARG_POINTER(pHasThem);
+
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pHasThem = !!(flags & nsMsgMessageFlags::Attachment);
+ return rv;
+}
+
+bool nsMsgDatabase::SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead)
+{
+ return SetHdrFlag(msgHdr, bRead, nsMsgMessageFlags::Read);
+}
+
+nsresult nsMsgDatabase::MarkHdrReadInDB(nsIMsgDBHdr *msgHdr, bool bRead,
+ nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ nsMsgKey key;
+ uint32_t oldFlags;
+ bool hdrInDB;
+ (void)msgHdr->GetMessageKey(&key);
+ msgHdr->GetFlags(&oldFlags);
+
+ m_newSet.RemoveElement(key);
+ (void) ContainsKey(key, &hdrInDB);
+ if (hdrInDB && m_dbFolderInfo)
+ {
+ if (bRead)
+ m_dbFolderInfo->ChangeNumUnreadMessages(-1);
+ else
+ m_dbFolderInfo->ChangeNumUnreadMessages(1);
+ }
+
+ SetHdrReadFlag(msgHdr, bRead); // this will cause a commit, at least for local mail, so do it after we change
+ // the folder counts above, so they will get committed too.
+ uint32_t flags;
+ rv = msgHdr->GetFlags(&flags);
+ flags &= ~nsMsgMessageFlags::New;
+ msgHdr->SetFlags(flags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (oldFlags == flags)
+ return NS_OK;
+
+ return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkRead(nsMsgKey key, bool bRead,
+ nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ rv = MarkHdrRead(msgHdr, bRead, instigator);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkReplied(nsMsgKey key, bool bReplied,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bReplied, nsMsgMessageFlags::Replied, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkForwarded(nsMsgKey key, bool bForwarded,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bForwarded, nsMsgMessageFlags::Forwarded, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkHasAttachments(nsMsgKey key, bool bHasAttachments,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, bHasAttachments, nsMsgMessageFlags::Attachment, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkThreadRead(nsIMsgThread *thread, nsIDBChangeListener *instigator,
+ uint32_t *aNumMarked, nsMsgKey **aThoseMarked)
+{
+ NS_ENSURE_ARG_POINTER(thread);
+ NS_ENSURE_ARG_POINTER(aNumMarked);
+ NS_ENSURE_ARG_POINTER(aThoseMarked);
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren;
+ nsTArray<nsMsgKey> thoseMarked;
+ thread->GetNumChildren(&numChildren);
+ for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+
+ rv = thread->GetChildHdrAt(curChildIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool isRead = true;
+ IsHeaderRead(child, &isRead);
+ if (!isRead)
+ {
+ nsMsgKey key;
+ if (NS_SUCCEEDED(child->GetMessageKey(&key)))
+ thoseMarked.AppendElement(key);
+ MarkHdrRead(child, true, instigator);
+ }
+ }
+ }
+
+ *aNumMarked = thoseMarked.Length();
+
+ if (thoseMarked.Length())
+ {
+ *aThoseMarked =
+ (nsMsgKey *) nsMemory::Clone(&thoseMarked[0],
+ thoseMarked.Length() * sizeof(nsMsgKey));
+ if (!*aThoseMarked)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ *aThoseMarked = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkThreadIgnored(nsIMsgThread *thread, nsMsgKey threadKey, bool bIgnored,
+ nsIDBChangeListener *instigator)
+{
+ NS_ENSURE_ARG(thread);
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t oldThreadFlags = threadFlags; // not quite right, since we probably want msg hdr flags.
+ if (bIgnored)
+ {
+ threadFlags |= nsMsgMessageFlags::Ignored;
+ threadFlags &= ~nsMsgMessageFlags::Watched; // ignore is implicit un-watch
+ }
+ else
+ threadFlags &= ~nsMsgMessageFlags::Ignored;
+ thread->SetFlags(threadFlags);
+
+ nsCOMPtr <nsIMsgDBHdr> msg;
+ nsresult rv = GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkHeaderKilled(nsIMsgDBHdr *msg, bool bIgnored,
+ nsIDBChangeListener *instigator)
+{
+ uint32_t msgFlags;
+ msg->GetFlags(&msgFlags);
+ uint32_t oldFlags = msgFlags;
+ if (bIgnored)
+ msgFlags |= nsMsgMessageFlags::Ignored;
+ else
+ msgFlags &= ~nsMsgMessageFlags::Ignored;
+ msg->SetFlags(msgFlags);
+
+ return NotifyHdrChangeAll(msg, oldFlags, msgFlags, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkThreadWatched(nsIMsgThread *thread, nsMsgKey threadKey, bool bWatched,
+ nsIDBChangeListener *instigator)
+{
+ NS_ENSURE_ARG(thread);
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t oldThreadFlags = threadFlags; // not quite right, since we probably want msg hdr flags.
+ if (bWatched)
+ {
+ threadFlags |= nsMsgMessageFlags::Watched;
+ threadFlags &= ~nsMsgMessageFlags::Ignored; // watch is implicit un-ignore
+ }
+ else
+ threadFlags &= ~nsMsgMessageFlags::Watched;
+
+ nsCOMPtr <nsIMsgDBHdr> msg;
+ GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
+
+ nsresult rv = NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
+ thread->SetFlags(threadFlags);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkMarked(nsMsgKey key, bool mark,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, mark, nsMsgMessageFlags::Marked, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkOffline(nsMsgKey key, bool offline,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, offline, nsMsgMessageFlags::Offline, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetStringProperty(nsMsgKey aKey, const char *aProperty, const char *aValue)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForKey(aKey, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+ return SetStringPropertyByHdr(msgHdr, aProperty, aValue);
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetStringPropertyByHdr(nsIMsgDBHdr *msgHdr, const char *aProperty, const char *aValue)
+{
+ // don't do notifications if message not yet added to database.
+ // Ignore errors (consequences of failure are minor).
+ bool notify = true;
+ nsMsgKey key = nsMsgKey_None;
+ msgHdr->GetMessageKey(&key);
+ ContainsKey(key, &notify);
+
+ nsCString oldValue;
+ nsresult rv = msgHdr->GetStringProperty(aProperty, getter_Copies(oldValue));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // if no change to this string property, bail out
+ if (oldValue.Equals(aValue))
+ return NS_OK;
+
+ // Precall OnHdrPropertyChanged to store prechange status
+ nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
+ uint32_t status;
+ nsCOMPtr<nsIDBChangeListener> listener;
+ if (notify)
+ {
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ while (listeners.HasMore())
+ {
+ listener = listeners.GetNext();
+ listener->OnHdrPropertyChanged(msgHdr, true, &status, nullptr);
+ // ignore errors, but append element to keep arrays in sync
+ statusArray.AppendElement(status);
+ }
+ }
+
+ rv = msgHdr->SetStringProperty(aProperty, aValue);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ //Postcall OnHdrPropertyChanged to process the change
+ if (notify)
+ {
+ // if this is the junk score property notify, as long as we're not going
+ // from no value to non junk
+ if (!strcmp(aProperty, "junkscore") && !(oldValue.IsEmpty() && !strcmp(aValue, "0")))
+ NotifyJunkScoreChanged(nullptr);
+
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ for (uint32_t i = 0; listeners.HasMore(); i++)
+ {
+ listener = listeners.GetNext();
+ status = statusArray[i];
+ listener->OnHdrPropertyChanged(msgHdr, false, &status, nullptr);
+ // ignore errors
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::SetUint32PropertyByHdr(nsIMsgDBHdr *aMsgHdr,
+ const char *aProperty,
+ uint32_t aValue)
+{
+ // If no change to this property, bail out.
+ uint32_t oldValue;
+ nsresult rv = aMsgHdr->GetUint32Property(aProperty, &oldValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (oldValue == aValue)
+ return NS_OK;
+
+ // Don't do notifications if message not yet added to database.
+ bool notify = true;
+ nsMsgKey key = nsMsgKey_None;
+ aMsgHdr->GetMessageKey(&key);
+ ContainsKey(key, &notify);
+
+ // Precall OnHdrPropertyChanged to store prechange status.
+ nsTArray<uint32_t> statusArray(m_ChangeListeners.Length());
+ uint32_t status;
+ nsCOMPtr<nsIDBChangeListener> listener;
+ if (notify)
+ {
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ while (listeners.HasMore())
+ {
+ listener = listeners.GetNext();
+ listener->OnHdrPropertyChanged(aMsgHdr, true, &status, nullptr);
+ // Ignore errors, but append element to keep arrays in sync.
+ statusArray.AppendElement(status);
+ }
+ }
+
+ rv = aMsgHdr->SetUint32Property(aProperty, aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Postcall OnHdrPropertyChanged to process the change.
+ if (notify)
+ {
+ nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
+ for (uint32_t i = 0; listeners.HasMore(); i++)
+ {
+ listener = listeners.GetNext();
+ status = statusArray[i];
+ listener->OnHdrPropertyChanged(aMsgHdr, false, &status, nullptr);
+ // Ignore errors.
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetLabel(nsMsgKey key, nsMsgLabelValue label)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ nsMsgLabelValue oldLabel;
+ msgHdr->GetLabel(&oldLabel);
+
+ msgHdr->SetLabel(label);
+ // clear old label
+ if (oldLabel != label)
+ {
+ if (oldLabel != 0)
+ rv = SetKeyFlag(key, false, oldLabel << 25, nullptr);
+ // set the flag in the x-mozilla-status2 line.
+ rv = SetKeyFlag(key, true, label << 25, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkImapDeleted(nsMsgKey key, bool deleted,
+ nsIDBChangeListener *instigator)
+{
+ return SetKeyFlag(key, deleted, nsMsgMessageFlags::IMAPDeleted, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkMDNNeeded(nsMsgKey key, bool bNeeded,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bNeeded, nsMsgMessageFlags::MDNReportNeeded, instigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::IsMDNNeeded(nsMsgKey key, bool *pNeeded)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pNeeded = !!(flags & nsMsgMessageFlags::MDNReportNeeded);
+ return rv;
+}
+
+
+nsresult nsMsgDatabase::MarkMDNSent(nsMsgKey key, bool bSent,
+ nsIDBChangeListener *instigator /* = NULL */)
+{
+ return SetKeyFlag(key, bSent, nsMsgMessageFlags::MDNReportSent, instigator);
+}
+
+
+nsresult nsMsgDatabase::IsMDNSent(nsMsgKey key, bool *pSent)
+{
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ nsresult rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+ *pSent = !!(flags & nsMsgMessageFlags::MDNReportSent);
+ return rv;
+}
+
+
+nsresult nsMsgDatabase::SetKeyFlag(nsMsgKey key, bool set, uint32_t flag,
+ nsIDBChangeListener *instigator)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+
+ rv = GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv) || !msgHdr)
+ return NS_MSG_MESSAGE_NOT_FOUND; // XXX return rv?
+
+ uint32_t oldFlags;
+ msgHdr->GetFlags(&oldFlags);
+
+ SetHdrFlag(msgHdr, set, flag);
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+
+ if (oldFlags == flags)
+ return NS_OK;
+
+ return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
+}
+
+nsresult nsMsgDatabase::SetMsgHdrFlag(nsIMsgDBHdr *msgHdr, bool set, uint32_t flag, nsIDBChangeListener *instigator)
+{
+ uint32_t oldFlags;
+ msgHdr->GetFlags(&oldFlags);
+
+ SetHdrFlag(msgHdr, set, flag);
+
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+
+ if (oldFlags == flags)
+ return NS_OK;
+
+ return NotifyHdrChangeAll(msgHdr, oldFlags, flags, instigator);
+}
+
+// Helper routine - lowest level of flag setting - returns true if flags change,
+// false otherwise.
+bool nsMsgDatabase::SetHdrFlag(nsIMsgDBHdr *msgHdr, bool bSet, nsMsgMessageFlagType flag)
+{
+ uint32_t statusFlags;
+ (void)msgHdr->GetFlags(&statusFlags);
+ uint32_t currentStatusFlags = GetStatusFlags(msgHdr, statusFlags);
+ bool flagAlreadySet = (currentStatusFlags & flag) != 0;
+
+ if ((flagAlreadySet && !bSet) || (!flagAlreadySet && bSet))
+ {
+ uint32_t resultFlags;
+ if (bSet)
+ msgHdr->OrFlags(flag, &resultFlags);
+ else
+ msgHdr->AndFlags(~flag, &resultFlags);
+ return true;
+ }
+ return false;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::MarkHdrRead(nsIMsgDBHdr *msgHdr, bool bRead,
+ nsIDBChangeListener *instigator)
+{
+ bool isReadInDB = true;
+ nsresult rv = nsMsgDatabase::IsHeaderRead(msgHdr, &isReadInDB);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isRead = true;
+ rv = IsHeaderRead(msgHdr, &isRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if the flag is already correct in the db, don't change it.
+ // Check msg flags as well as IsHeaderRead in case it's a newsgroup
+ // and the msghdr flags are out of sync with the newsrc settings.
+ // (we could override this method for news db's, but it's a trivial fix here.
+ if (bRead != isRead || isRead != isReadInDB)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+
+ bool inDB = false;
+ (void)ContainsKey(msgKey, &inDB);
+
+ if (inDB)
+ {
+ nsCOMPtr <nsIMsgThread> threadHdr;
+ rv = GetThreadForMsgKey(msgKey, getter_AddRefs(threadHdr));
+ if (threadHdr)
+ threadHdr->MarkChildRead(bRead);
+ }
+ return MarkHdrReadInDB(msgHdr, bRead, instigator);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkHdrReplied(nsIMsgDBHdr *msgHdr, bool bReplied,
+ nsIDBChangeListener *instigator)
+{
+ return SetMsgHdrFlag(msgHdr, bReplied, nsMsgMessageFlags::Replied, instigator);
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::MarkHdrMarked(nsIMsgDBHdr *msgHdr, bool mark,
+ nsIDBChangeListener *instigator)
+{
+ return SetMsgHdrFlag(msgHdr, mark, nsMsgMessageFlags::Marked, instigator);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::MarkHdrNotNew(nsIMsgDBHdr *aMsgHdr,
+ nsIDBChangeListener *aInstigator)
+{
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsMsgKey msgKey;
+ aMsgHdr->GetMessageKey(&msgKey);
+ m_newSet.RemoveElement(msgKey);
+ return SetMsgHdrFlag(aMsgHdr, false, nsMsgMessageFlags::New, aInstigator);
+}
+
+NS_IMETHODIMP nsMsgDatabase::MarkAllRead(uint32_t *aNumKeys, nsMsgKey **aThoseMarked)
+{
+ NS_ENSURE_ARG_POINTER(aNumKeys);
+ NS_ENSURE_ARG_POINTER(aThoseMarked);
+ nsMsgHdr *pHeader;
+
+ nsCOMPtr<nsISimpleEnumerator> hdrs;
+ nsTArray<nsMsgKey> thoseMarked;
+ nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ bool isRead;
+ IsHeaderRead(pHeader, &isRead);
+
+ if (!isRead)
+ {
+ nsMsgKey key;
+ (void)pHeader->GetMessageKey(&key);
+ thoseMarked.AppendElement(key);
+ rv = MarkHdrRead(pHeader, true, nullptr); // ### dmb - blow off error?
+ }
+ NS_RELEASE(pHeader);
+ }
+
+ *aNumKeys = thoseMarked.Length();
+
+ if (thoseMarked.Length())
+ {
+ *aThoseMarked = (nsMsgKey *) nsMemory::Clone(&thoseMarked[0],
+ thoseMarked.Length() * sizeof(nsMsgKey));
+ if (!*aThoseMarked)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ *aThoseMarked = nullptr;
+
+ // force num new to 0.
+ int32_t numUnreadMessages;
+
+ rv = m_dbFolderInfo->GetNumUnreadMessages(&numUnreadMessages);
+ if (NS_SUCCEEDED(rv))
+ m_dbFolderInfo->ChangeNumUnreadMessages(-numUnreadMessages);
+ // caller will Commit the db, so no need to do it here.
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::AddToNewList(nsMsgKey key)
+{
+ // we add new keys in increasing order...
+ if (m_newSet.IsEmpty() || (m_newSet[m_newSet.Length() - 1] < key))
+ m_newSet.AppendElement(key);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::ClearNewList(bool notify /* = FALSE */)
+{
+ nsresult err = NS_OK;
+ if (notify && !m_newSet.IsEmpty()) // need to update view
+ {
+ nsTArray<nsMsgKey> saveNewSet;
+ // clear m_newSet so that the code that's listening to the key change
+ // doesn't think we have new messages and send notifications all over
+ // that we have new messages.
+ saveNewSet.SwapElements(m_newSet);
+ for (uint32_t elementIndex = saveNewSet.Length() - 1; ; elementIndex--)
+ {
+ nsMsgKey lastNewKey = saveNewSet.ElementAt(elementIndex);
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ err = GetMsgHdrForKey(lastNewKey, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(err))
+ {
+ uint32_t flags;
+ (void)msgHdr->GetFlags(&flags);
+
+ if ((flags | nsMsgMessageFlags::New) != flags)
+ {
+ msgHdr->AndFlags(~nsMsgMessageFlags::New, &flags);
+ NotifyHdrChangeAll(msgHdr, flags | nsMsgMessageFlags::New, flags, nullptr);
+ }
+ }
+ if (elementIndex == 0)
+ break;
+ }
+ }
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::HasNew(bool *_retval)
+{
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = (m_newSet.Length() > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetFirstNew(nsMsgKey *result)
+{
+ bool hasnew;
+ nsresult rv = HasNew(&hasnew);
+ if (NS_FAILED(rv)) return rv;
+ *result = (hasnew) ? m_newSet.ElementAt(0) : nsMsgKey_None;
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsMsgDBEnumerator::nsMsgDBEnumerator(nsMsgDatabase* db,
+ nsIMdbTable *table,
+ nsMsgDBEnumeratorFilter filter,
+ void* closure,
+ bool iterateForwards)
+ : mDB(db),
+ mDone(false),
+ mIterateForwards(iterateForwards),
+ mFilter(filter),
+ mClosure(closure),
+ mStopPos(-1)
+{
+ mNextPrefetched = false;
+ mTable = table;
+ mRowPos = 0;
+ mDB->m_enumerators.AppendElement(this);
+}
+
+nsMsgDBEnumerator::~nsMsgDBEnumerator()
+{
+ Clear();
+}
+
+void nsMsgDBEnumerator::Clear()
+{
+ mRowCursor = nullptr;
+ mTable = nullptr;
+ mResultHdr = nullptr;
+ if (mDB)
+ mDB->m_enumerators.RemoveElement(this);
+ mDB = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDBEnumerator, nsISimpleEnumerator)
+
+nsresult nsMsgDBEnumerator::GetRowCursor()
+{
+ mDone = false;
+
+ if (!mDB || !mTable)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mIterateForwards)
+ {
+ mRowPos = -1;
+ }
+ else
+ {
+ mdb_count numRows;
+ mTable->GetCount(mDB->GetEnv(), &numRows);
+ mRowPos = numRows; // startPos is 0 relative.
+ }
+ return mTable->GetTableRowCursor(mDB->GetEnv(), mRowPos, getter_AddRefs(mRowCursor));
+}
+
+NS_IMETHODIMP nsMsgDBEnumerator::GetNext(nsISupports **aItem)
+{
+ if (!aItem)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ if (!mNextPrefetched)
+ rv = PrefetchNext();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mResultHdr)
+ {
+ *aItem = mResultHdr;
+ NS_ADDREF(*aItem);
+ mNextPrefetched = false;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgDBEnumerator::PrefetchNext()
+{
+ nsresult rv = NS_OK;
+ nsIMdbRow* hdrRow;
+ uint32_t flags;
+
+ if (!mRowCursor)
+ {
+ rv = GetRowCursor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ do
+ {
+ mResultHdr = nullptr;
+ if (mIterateForwards)
+ rv = mRowCursor->NextRow(mDB->GetEnv(), &hdrRow, &mRowPos);
+ else
+ rv = mRowCursor->PrevRow(mDB->GetEnv(), &hdrRow, &mRowPos);
+ if (!hdrRow)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ rv = hdrRow->GetOid(mDB->GetEnv(), &outOid);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ key = outOid.mOid_Id;
+
+ rv = mDB->GetHdrFromUseCache(key, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ hdrRow->Release();
+ else {
+ rv = mDB->CreateMsgHdr(hdrRow, key, getter_AddRefs(mResultHdr));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+
+ if (mResultHdr)
+ mResultHdr->GetFlags(&flags);
+ else
+ flags = 0;
+ }
+ while (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure)) && !(flags & nsMsgMessageFlags::Expunged));
+
+ if (mResultHdr)
+ {
+ mNextPrefetched = true;
+ return NS_OK;
+ }
+ else
+ mNextPrefetched = false;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBEnumerator::HasMoreElements(bool *aResult)
+{
+ if (!aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mNextPrefetched && (NS_FAILED(PrefetchNext())))
+ mDone = true;
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+nsMsgFilteredDBEnumerator::nsMsgFilteredDBEnumerator(nsMsgDatabase* db,
+ nsIMdbTable *table,
+ bool reverse,
+ nsIArray *searchTerms)
+ : nsMsgDBEnumerator(db, table, nullptr, nullptr, !reverse)
+{
+}
+
+nsMsgFilteredDBEnumerator::~nsMsgFilteredDBEnumerator()
+{
+}
+
+/**
+ * Create the search session for the enumerator,
+ * add the scope term for "folder" to the search session, and add the search
+ * terms in the array to the search session.
+ */
+nsresult nsMsgFilteredDBEnumerator::InitSearchSession(nsIArray *searchTerms, nsIMsgFolder *folder)
+{
+ nsresult rv;
+ m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, folder);
+ // add each item in termsArray to the search session
+ uint32_t numTerms;
+ rv = searchTerms->GetLength(&numTerms);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < numTerms; i++)
+ {
+ nsCOMPtr <nsIMsgSearchTerm> searchTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(searchTerm));
+ m_searchSession->AppendTerm(searchTerm);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgFilteredDBEnumerator::PrefetchNext()
+{
+ nsresult rv;
+ do
+ {
+ rv = nsMsgDBEnumerator::PrefetchNext();
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ bool matches;
+ rv = m_searchSession->MatchHdr(mResultHdr, mDB, &matches);
+ if (NS_SUCCEEDED(rv) && matches)
+ break;
+ mResultHdr = nullptr;
+ }
+ else
+ break;
+ } while (mStopPos == -1 || mRowPos != mStopPos);
+
+ if (!mResultHdr)
+ mNextPrefetched = false;
+
+ return rv;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgDatabase::EnumerateMessages(nsISimpleEnumerator* *result)
+{
+ RememberLastUseTime();
+ NS_ENSURE_ARG_POINTER(result);
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
+ nullptr, nullptr);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::ReverseEnumerateMessages(nsISimpleEnumerator* *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable,
+ nullptr, nullptr, false);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::GetFilterEnumerator(nsIArray *searchTerms, bool aReverse,
+ nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ RefPtr<nsMsgFilteredDBEnumerator> e =
+ new nsMsgFilteredDBEnumerator(this, m_mdbAllMsgHeadersTable, aReverse,
+ searchTerms);
+
+ NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = e->InitSearchSession(searchTerms, m_folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return CallQueryInterface(e.get(), aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::NextMatchingHdrs(nsISimpleEnumerator *aEnumerator,
+ int32_t aNumHdrsToLookAt, int32_t aMaxResults,
+ nsIMutableArray *aMatchingHdrs,
+ int32_t *aNumMatches, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aEnumerator);
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsMsgFilteredDBEnumerator *enumerator =
+ static_cast<nsMsgFilteredDBEnumerator *> (aEnumerator);
+
+ // Force mRowPos to be initialized.
+ if (!enumerator->mRowCursor)
+ enumerator->GetRowCursor();
+
+ if (aNumHdrsToLookAt)
+ {
+ enumerator->mStopPos = enumerator->mIterateForwards ?
+ enumerator->mRowPos + aNumHdrsToLookAt :
+ enumerator->mRowPos - aNumHdrsToLookAt;
+ if (enumerator->mStopPos < 0)
+ enumerator->mStopPos = 0;
+ }
+ int32_t numMatches = 0;
+ nsresult rv;
+ do
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ nsCOMPtr <nsIMsgDBHdr> nextMessage = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv) && nextMessage)
+ {
+ if (aMatchingHdrs)
+ aMatchingHdrs->AppendElement(nextMessage, false);
+ ++numMatches;
+ if (aMaxResults && numMatches == aMaxResults)
+ break;
+ }
+ else
+ break;
+ }
+ while (true);
+
+ if (aNumMatches)
+ *aNumMatches = numMatches;
+
+ *aResult = !enumerator->mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::SyncCounts()
+{
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+
+ mdb_count numHdrsInTable = 0;
+ int32_t numUnread = 0;
+ int32_t numHdrs = 0;
+
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrsInTable);
+ else
+ return NS_ERROR_NULL_POINTER;
+
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports);
+
+ bool isRead;
+ IsHeaderRead(pHeader, &isRead);
+ if (!isRead)
+ numUnread++;
+ numHdrs++;
+ }
+
+ int32_t oldTotal, oldUnread;
+ (void) m_dbFolderInfo->GetNumUnreadMessages(&oldUnread);
+ (void) m_dbFolderInfo->GetNumMessages(&oldTotal);
+ if (oldUnread != numUnread)
+ m_dbFolderInfo->ChangeNumUnreadMessages(numUnread - oldUnread);
+ if (oldTotal != numHdrs)
+ m_dbFolderInfo->ChangeNumMessages(numHdrs - oldTotal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ListAllKeys(nsIMsgKeyArray *aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aKeys);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+
+ RememberLastUseTime();
+
+ if (m_mdbAllMsgHeadersTable)
+ {
+ uint32_t numMsgs = 0;
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numMsgs);
+ aKeys->SetCapacity(numMsgs);
+ rv = m_mdbAllMsgHeadersTable->GetTableRowCursor(GetEnv(), -1,
+ getter_AddRefs(rowCursor));
+ while (NS_SUCCEEDED(rv) && rowCursor)
+ {
+ mdbOid outOid;
+ mdb_pos outPos;
+
+ rv = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos);
+ // is this right? Mork is returning a 0 id, but that should valid.
+ if (outPos < 0 || outOid.mOid_Id == (mdb_id) -1)
+ break;
+ if (NS_SUCCEEDED(rv))
+ aKeys->AppendElement(outOid.mOid_Id);
+ }
+ }
+ return rv;
+}
+
+class nsMsgDBThreadEnumerator : public nsISimpleEnumerator, public nsIDBChangeListener
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ NS_DECL_NSIDBCHANGELISTENER
+
+ // nsMsgDBEnumerator methods:
+ typedef nsresult (*nsMsgDBThreadEnumeratorFilter)(nsIMsgThread* thread);
+
+ nsMsgDBThreadEnumerator(nsMsgDatabase* db, nsMsgDBThreadEnumeratorFilter filter);
+
+protected:
+ virtual ~nsMsgDBThreadEnumerator();
+ nsresult GetTableCursor(void);
+ nsresult PrefetchNext();
+ nsMsgDatabase* mDB;
+ nsCOMPtr <nsIMdbPortTableCursor> mTableCursor;
+ nsIMsgThread* mResultThread;
+ bool mDone;
+ bool mNextPrefetched;
+ nsMsgDBThreadEnumeratorFilter mFilter;
+};
+
+nsMsgDBThreadEnumerator::nsMsgDBThreadEnumerator(nsMsgDatabase* db,
+ nsMsgDBThreadEnumeratorFilter filter)
+ : mDB(db), mTableCursor(nullptr), mResultThread(nullptr), mDone(false),
+ mFilter(filter)
+{
+ mDB->AddListener(this);
+ mNextPrefetched = false;
+}
+
+nsMsgDBThreadEnumerator::~nsMsgDBThreadEnumerator()
+{
+ mTableCursor = nullptr;
+ NS_IF_RELEASE(mResultThread);
+ if (mDB)
+ mDB->RemoveListener(this);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDBThreadEnumerator, nsISimpleEnumerator, nsIDBChangeListener)
+
+
+/* void OnHdrFlagsChanged (in nsIMsgDBHdr aHdrChanged, in unsigned long aOldFlags, in unsigned long aNewFlags, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+//void OnHdrPropertyChanged(in nsIMsgDBHdr aHdrToChange, in bool aPreChange,
+// inout uint32_t aStatus, in nsIDBChangeListener aInstigator);
+NS_IMETHODIMP
+nsMsgDBThreadEnumerator::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus,
+ nsIDBChangeListener * aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onHdrDeleted (in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onHdrAdded (in nsIMsgDBHdr aHdrChanged, in nsMsgKey aParentKey, in long aFlags, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator)
+{
+ mTableCursor = nullptr;
+ NS_IF_RELEASE(mResultThread);
+ mDB->RemoveListener(this);
+ mDB = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnEvent(nsIMsgDatabase *aDB, const char *aEvent)
+{
+ return NS_OK;
+}
+
+/* void onReadChanged (in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnReadChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+/* void onJunkScoreChanged (in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP nsMsgDBThreadEnumerator::OnJunkScoreChanged(nsIDBChangeListener *aInstigator)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgDBThreadEnumerator::GetTableCursor(void)
+{
+ nsresult rv = NS_OK;
+
+ if (!mDB || !mDB->m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ mDB->m_mdbStore->GetPortTableCursor(mDB->GetEnv(), mDB->m_hdrRowScopeToken, mDB->m_threadTableKindToken,
+ getter_AddRefs(mTableCursor));
+
+ if (NS_FAILED(rv))
+ return rv;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBThreadEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ *aItem = nullptr;
+ nsresult rv = NS_OK;
+ if (!mNextPrefetched)
+ rv = PrefetchNext();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mResultThread)
+ {
+ *aItem = mResultThread;
+ NS_ADDREF(mResultThread);
+ mNextPrefetched = false;
+ }
+ }
+ return rv;
+}
+
+
+nsresult nsMsgDBThreadEnumerator::PrefetchNext()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMdbTable> table;
+
+ if (!mDB)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mTableCursor)
+ {
+ rv = GetTableCursor();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ while (true)
+ {
+ NS_IF_RELEASE(mResultThread);
+ mResultThread = nullptr;
+ rv = mTableCursor->NextTable(mDB->GetEnv(), getter_AddRefs(table));
+ if (!table)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+
+ mdbOid tableId;
+ table->GetOid(mDB->GetEnv(), &tableId);
+
+ mResultThread = mDB->FindExistingThread(tableId.mOid_Id);
+ if (!mResultThread)
+ mResultThread = new nsMsgThread(mDB, table);
+
+ if (mResultThread)
+ {
+ uint32_t numChildren = 0;
+ NS_ADDREF(mResultThread);
+ mResultThread->GetNumChildren(&numChildren);
+ // we've got empty thread; don't tell caller about it.
+ if (numChildren == 0)
+ continue;
+ }
+ if (mFilter && NS_FAILED(mFilter(mResultThread)))
+ continue;
+ else
+ break;
+ }
+ if (mResultThread)
+ {
+ mNextPrefetched = true;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBThreadEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!mNextPrefetched)
+ PrefetchNext();
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::EnumerateThreads(nsISimpleEnumerator* *result)
+{
+ RememberLastUseTime();
+ nsMsgDBThreadEnumerator* e = new nsMsgDBThreadEnumerator(this, nullptr);
+ if (e == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+// only return headers with a particular flag set
+static nsresult
+nsMsgFlagSetFilter(nsIMsgDBHdr *msg, void *closure)
+{
+ uint32_t msgFlags, desiredFlags;
+ desiredFlags = * (uint32_t *) closure;
+ msg->GetFlags(&msgFlags);
+ return (msgFlags & desiredFlags) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsMsgDatabase::EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, uint32_t *pFlag)
+{
+ RememberLastUseTime();
+
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsMsgFlagSetFilter, pFlag);
+ if (!e)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::CreateNewHdr(nsMsgKey key, nsIMsgDBHdr **pnewHdr)
+{
+ nsresult err = NS_OK;
+ nsIMdbRow *hdrRow = nullptr;
+ struct mdbOid allMsgHdrsTableOID;
+
+ if (!pnewHdr || !m_mdbAllMsgHeadersTable || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ if (key != nsMsgKey_None)
+ {
+ allMsgHdrsTableOID.mOid_Scope = m_hdrRowScopeToken;
+ allMsgHdrsTableOID.mOid_Id = key; // presumes 0 is valid key value
+
+ err = m_mdbStore->GetRow(GetEnv(), &allMsgHdrsTableOID, &hdrRow);
+ if (!hdrRow)
+ err = m_mdbStore->NewRowWithOid(GetEnv(), &allMsgHdrsTableOID, &hdrRow);
+ }
+ else
+ {
+ // Mork will assign an ID to the new row, generally the next available ID.
+ err = m_mdbStore->NewRow(GetEnv(), m_hdrRowScopeToken, &hdrRow);
+ if (hdrRow)
+ {
+ struct mdbOid oid;
+ hdrRow->GetOid(GetEnv(), &oid);
+ key = oid.mOid_Id;
+ }
+ else
+ {
+ // We failed to create a new row. That can happen if we run out of keys,
+ // which will force a reparse.
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ if (NS_SUCCEEDED(ListAllKeys(keys)))
+ {
+ uint32_t numKeys;
+ keys->GetLength(&numKeys);
+ for (uint32_t i = 0; i < numKeys; i++)
+ {
+ if (keys->m_keys[i] >= kForceReparseKey)
+ {
+ // Force a reparse.
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->SetBooleanProperty("forceReparse", true);
+ break;
+ }
+ }
+ }
+ err = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ }
+ if (NS_FAILED(err))
+ return err;
+ err = CreateMsgHdr(hdrRow, key, pnewHdr);
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::AddNewHdrToDB(nsIMsgDBHdr *newHdr, bool notify)
+{
+ NS_ENSURE_ARG_POINTER(newHdr);
+ nsMsgHdr* hdr = static_cast<nsMsgHdr*>(newHdr); // closed system, cast ok
+ bool newThread;
+ bool hasKey;
+ ContainsKey(hdr->m_messageKey, &hasKey);
+ if (hasKey)
+ {
+ NS_ERROR("adding hdr that already exists");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult err = ThreadNewHdr(hdr, newThread);
+ // we thread header before we add it to the all headers table
+ // so that subject and reference threading will work (otherwise,
+ // when we try to find the first header with the same subject or
+ // reference, we get the new header!)
+ if (NS_SUCCEEDED(err))
+ {
+ nsMsgKey key;
+ uint32_t flags;
+
+ newHdr->GetMessageKey(&key);
+ hdr->GetRawFlags(&flags);
+ // use raw flags instead of GetFlags, because GetFlags will
+ // pay attention to what's in m_newSet, and this new hdr isn't
+ // in m_newSet yet.
+ if (flags & nsMsgMessageFlags::New)
+ {
+ uint32_t newFlags;
+ newHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags); // make sure not filed out
+ AddToNewList(key);
+ }
+ if (m_dbFolderInfo != NULL)
+ {
+ m_dbFolderInfo->ChangeNumMessages(1);
+ bool isRead = true;
+ IsHeaderRead(newHdr, &isRead);
+ if (!isRead)
+ m_dbFolderInfo->ChangeNumUnreadMessages(1);
+ m_dbFolderInfo->OnKeyAdded(key);
+ }
+
+ err = m_mdbAllMsgHeadersTable->AddRow(GetEnv(), hdr->GetMDBRow());
+ if (notify)
+ {
+ nsMsgKey threadParent;
+
+ newHdr->GetThreadParent(&threadParent);
+ NotifyHdrAddedAll(newHdr, threadParent, flags, NULL);
+ }
+
+ if (UseCorrectThreading())
+ err = AddMsgRefsToHash(newHdr);
+ }
+ NS_ASSERTION(NS_SUCCEEDED(err), "error creating thread");
+ return err;
+}
+
+NS_IMETHODIMP nsMsgDatabase::CopyHdrFromExistingHdr(nsMsgKey key, nsIMsgDBHdr *existingHdr, bool addHdrToDB, nsIMsgDBHdr **newHdr)
+{
+ nsresult err = NS_OK;
+
+ if (existingHdr)
+ {
+ nsMsgHdr* sourceMsgHdr = static_cast<nsMsgHdr*>(existingHdr); // closed system, cast ok
+ nsMsgHdr *destMsgHdr = nullptr;
+ CreateNewHdr(key, (nsIMsgDBHdr **) &destMsgHdr);
+ nsIMdbRow *sourceRow = sourceMsgHdr->GetMDBRow();
+ if (!destMsgHdr || !sourceRow)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsIMdbRow *destRow = destMsgHdr->GetMDBRow();
+ if (!destRow)
+ return NS_ERROR_UNEXPECTED;
+
+ err = destRow->SetRow(GetEnv(), sourceRow);
+ if (NS_SUCCEEDED(err))
+ {
+ // we may have gotten the header from a cache - calling SetRow
+ // basically invalidates any cached values, so invalidate them.
+ destMsgHdr->m_initedValues = 0;
+ if(addHdrToDB)
+ err = AddNewHdrToDB(destMsgHdr, true);
+ if (NS_SUCCEEDED(err) && newHdr)
+ *newHdr = destMsgHdr;
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnTonsString(nsIMdbRow *hdrRow, mdb_token columnToken, nsAString &resultStr)
+{
+ NS_ENSURE_ARG_POINTER(hdrRow);
+
+ struct mdbYarn yarn;
+ nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ YarnTonsString(&yarn, resultStr);
+ return NS_OK;
+}
+
+// as long as the row still exists, and isn't changed, the returned const char ** will be valid.
+// But be very careful using this data - the caller should never return it in turn to another caller.
+nsresult nsMsgDatabase::RowCellColumnToConstCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, const char **ptr)
+{
+ NS_ENSURE_ARG_POINTER(hdrRow);
+
+ struct mdbYarn yarn;
+ nsresult rv = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *ptr = (const char*)yarn.mYarn_Buf;
+ return NS_OK;
+}
+
+nsIMimeConverter *nsMsgDatabase::GetMimeConverter()
+{
+ if (!m_mimeConverter)
+ {
+ // apply mime decode
+ m_mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+ }
+ return m_mimeConverter;
+}
+
+nsresult nsMsgDatabase::GetEffectiveCharset(nsIMdbRow *row, nsACString &resultCharset)
+{
+ resultCharset.Truncate();
+ bool characterSetOverride;
+ m_dbFolderInfo->GetCharacterSetOverride(&characterSetOverride);
+ nsresult rv = RowCellColumnToCharPtr(row, m_messageCharSetColumnToken, getter_Copies(resultCharset));
+ if (NS_FAILED(rv) || resultCharset.IsEmpty() ||
+ resultCharset.Equals("us-ascii") || characterSetOverride)
+ {
+ rv = m_dbFolderInfo->GetEffectiveCharacterSet(resultCharset);
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToMime2DecodedString(nsIMdbRow *row, mdb_token columnToken, nsAString &resultStr)
+{
+ nsresult err = NS_OK;
+ const char *nakedString = nullptr;
+ err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString);
+ if (NS_SUCCEEDED(err) && nakedString && strlen(nakedString))
+ {
+ GetMimeConverter();
+ if (m_mimeConverter)
+ {
+ nsAutoString decodedStr;
+ nsCString charSet;
+ GetEffectiveCharset(row, charSet);
+
+ err = m_mimeConverter->DecodeMimeHeader(nakedString, charSet.get(),
+ false, true, resultStr);
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToAddressCollationKey(nsIMdbRow *row, mdb_token colToken, uint8_t **result, uint32_t *len)
+{
+ nsString sender;
+ nsresult rv = RowCellColumnToMime2DecodedString(row, colToken, sender);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString name;
+ ExtractName(DecodedHeader(sender), name);
+ return CreateCollationKey(name, len, result);
+}
+
+nsresult nsMsgDatabase::GetCollationKeyGenerator()
+{
+ nsresult err = NS_OK;
+ if (!m_collationKeyGenerator)
+ {
+ nsCOMPtr <nsILocale> locale;
+ nsAutoString localeName;
+
+ // get a locale service
+ nsCOMPtr <nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &err);
+ if (NS_SUCCEEDED(err))
+ {
+ // do this for a new db if no UI to be provided for locale selection
+ err = localeService->GetApplicationLocale(getter_AddRefs(locale));
+
+ if (locale)
+ {
+ // or generate a locale from a stored locale name ("en_US", "fr_FR")
+ //err = localeFactory->NewLocale(&localeName, &locale);
+
+ nsCOMPtr <nsICollationFactory> f = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &err);
+ if (NS_SUCCEEDED(err) && f)
+ {
+ // get a collation interface instance
+ err = f->CreateCollation(locale, getter_AddRefs(m_collationKeyGenerator));
+ }
+ }
+ }
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToCollationKey(nsIMdbRow *row, mdb_token columnToken, uint8_t **result, uint32_t *len)
+{
+ const char *nakedString = nullptr;
+ nsresult err;
+
+ err = RowCellColumnToConstCharPtr(row, columnToken, &nakedString);
+ if (!nakedString)
+ nakedString = "";
+ if (NS_SUCCEEDED(err))
+ {
+ GetMimeConverter();
+ if (m_mimeConverter)
+ {
+ nsCString decodedStr;
+ nsCString charSet;
+ GetEffectiveCharset(row, charSet);
+
+ err = m_mimeConverter->DecodeMimeHeaderToUTF8(
+ nsDependentCString(nakedString), charSet.get(), false,
+ true, decodedStr);
+ if (NS_SUCCEEDED(err))
+ err = CreateCollationKey(NS_ConvertUTF8toUTF16(decodedStr), len, result);
+ }
+ }
+ return err;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::CompareCollationKeys(uint32_t len1, uint8_t *key1, uint32_t len2,
+ uint8_t *key2, int32_t *result)
+{
+ nsresult rv = GetCollationKeyGenerator();
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;
+
+ rv = m_collationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,result);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::CreateCollationKey(const nsAString& sourceString, uint32_t *len,
+ uint8_t **result)
+{
+ nsresult err = GetCollationKeyGenerator();
+ NS_ENSURE_SUCCESS(err,err);
+ if (!m_collationKeyGenerator) return NS_ERROR_FAILURE;
+
+ err = m_collationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, sourceString, result, len);
+ NS_ENSURE_SUCCESS(err,err);
+ return err;
+}
+
+nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow *hdrRow, mdb_token columnToken, uint32_t &uint32Result, uint32_t defaultValue)
+{
+ return RowCellColumnToUInt32(hdrRow, columnToken, &uint32Result, defaultValue);
+}
+
+nsresult nsMsgDatabase::RowCellColumnToUInt32(nsIMdbRow *hdrRow, mdb_token columnToken, uint32_t *uint32Result, uint32_t defaultValue)
+{
+ nsresult err = NS_OK;
+
+ if (uint32Result)
+ *uint32Result = defaultValue;
+ if (hdrRow) // ### probably should be an error if hdrRow is NULL...
+ {
+ struct mdbYarn yarn;
+ err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ if (NS_SUCCEEDED(err))
+ YarnToUInt32(&yarn, uint32Result);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::UInt32ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint32_t value)
+{
+ struct mdbYarn yarn;
+ char yarnBuf[100];
+
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+ yarn.mYarn_Fill = yarn.mYarn_Size;
+ yarn.mYarn_Form = 0;
+ yarn.mYarn_Grow = NULL;
+ return row->AddColumn(GetEnv(), columnToken, UInt32ToYarn(&yarn, value));
+}
+
+nsresult nsMsgDatabase::UInt64ToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, uint64_t value)
+{
+ NS_ENSURE_ARG_POINTER(row);
+ struct mdbYarn yarn;
+ char yarnBuf[17]; // max string is 16 bytes, + 1 for null.
+
+ yarn.mYarn_Buf = (void *) yarnBuf;
+ yarn.mYarn_Size = sizeof(yarnBuf);
+ yarn.mYarn_Form = 0;
+ yarn.mYarn_Grow = NULL;
+ PR_snprintf((char *) yarn.mYarn_Buf, yarn.mYarn_Size, "%llx", value);
+ yarn.mYarn_Fill = PL_strlen((const char *) yarn.mYarn_Buf);
+ return row->AddColumn(GetEnv(), columnToken, &yarn);
+}
+
+nsresult
+nsMsgDatabase::RowCellColumnToUInt64(nsIMdbRow *hdrRow, mdb_token columnToken,
+ uint64_t *uint64Result,
+ uint64_t defaultValue)
+{
+ nsresult err = NS_OK;
+
+ if (uint64Result)
+ *uint64Result = defaultValue;
+ if (hdrRow) // ### probably should be an error if hdrRow is NULL...
+ {
+ struct mdbYarn yarn;
+ err = hdrRow->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ if (NS_SUCCEEDED(err))
+ YarnToUInt64(&yarn, uint64Result);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::CharPtrToRowCellColumn(nsIMdbRow *row, mdb_token columnToken, const char *charPtr)
+{
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ struct mdbYarn yarn;
+ yarn.mYarn_Buf = (void *) charPtr;
+ yarn.mYarn_Size = PL_strlen((const char *) yarn.mYarn_Buf) + 1;
+ yarn.mYarn_Fill = yarn.mYarn_Size - 1;
+ yarn.mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr...
+
+ return row->AddColumn(GetEnv(), columnToken, &yarn);
+}
+
+// caller must NS_Free result
+nsresult nsMsgDatabase::RowCellColumnToCharPtr(nsIMdbRow *row, mdb_token columnToken, char **result)
+{
+ nsresult err = NS_ERROR_NULL_POINTER;
+
+ if (row && result)
+ {
+ struct mdbYarn yarn;
+ err = row->AliasCellYarn(GetEnv(), columnToken, &yarn);
+ if (NS_SUCCEEDED(err))
+ {
+ *result = (char *)NS_Alloc(yarn.mYarn_Fill + 1);
+ if (*result)
+ {
+ if (yarn.mYarn_Fill > 0)
+ memcpy(*result, yarn.mYarn_Buf, yarn.mYarn_Fill);
+ (*result)[yarn.mYarn_Fill] = '\0';
+ }
+ else
+ err = NS_ERROR_OUT_OF_MEMORY;
+
+ }
+ }
+ return err;
+}
+
+
+
+/* static */struct mdbYarn *nsMsgDatabase::nsStringToYarn(struct mdbYarn *yarn, const nsAString &str)
+{
+ yarn->mYarn_Buf = ToNewCString(NS_ConvertUTF16toUTF8(str));
+ yarn->mYarn_Size = str.Length() + 1;
+ yarn->mYarn_Fill = yarn->mYarn_Size - 1;
+ yarn->mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr...
+ return yarn;
+}
+
+/* static */struct mdbYarn *nsMsgDatabase::UInt32ToYarn(struct mdbYarn *yarn, uint32_t i)
+{
+ PR_snprintf((char *) yarn->mYarn_Buf, yarn->mYarn_Size, "%lx", i);
+ yarn->mYarn_Fill = PL_strlen((const char *) yarn->mYarn_Buf);
+ yarn->mYarn_Form = 0; // what to do with this? Should be parsed out of the mime2 header?
+ return yarn;
+}
+
+/* static */struct mdbYarn *nsMsgDatabase::UInt64ToYarn(struct mdbYarn *yarn, uint64_t i)
+{
+ PR_snprintf((char *) yarn->mYarn_Buf, yarn->mYarn_Size, "%llx", i);
+ yarn->mYarn_Fill = PL_strlen((const char *) yarn->mYarn_Buf);
+ yarn->mYarn_Form = 0;
+ return yarn;
+}
+
+/* static */void nsMsgDatabase::YarnTonsString(struct mdbYarn *yarn, nsAString &str)
+{
+ const char* buf = (const char*)yarn->mYarn_Buf;
+ if (buf)
+ CopyASCIItoUTF16(Substring(buf, buf + yarn->mYarn_Fill), str);
+ else
+ str.Truncate();
+}
+
+/* static */void nsMsgDatabase::YarnTonsCString(struct mdbYarn *yarn, nsACString &str)
+{
+ const char* buf = (const char*)yarn->mYarn_Buf;
+ if (buf)
+ str.Assign(buf, yarn->mYarn_Fill);
+ else
+ str.Truncate();
+}
+
+// WARNING - if yarn is empty, *pResult will not be changed!!!!
+// this is so we can leave default values as they were.
+/* static */void nsMsgDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult)
+{
+ uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
+
+ if (numChars == 0)
+ return;
+
+ *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
+}
+
+// WARNING - if yarn is empty, *pResult will not be changed!!!!
+// this is so we can leave default values as they were.
+/* static */void nsMsgDatabase::YarnToUInt64(struct mdbYarn *yarn, uint64_t *pResult)
+{
+ uint8_t numChars = std::min<mdb_fill>(16, yarn->mYarn_Fill);
+
+ if (numChars == 0)
+ return;
+
+ *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars);
+}
+
+nsresult nsMsgDatabase::GetProperty(nsIMdbRow *row, const char *propertyName, char **result)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ if (m_mdbStore)
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ else
+ err = NS_ERROR_NULL_POINTER;
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnToCharPtr(row, property_token, result);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::SetProperty(nsIMdbRow *row, const char *propertyName, const char *propertyVal)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ CharPtrToRowCellColumn(row, property_token, propertyVal);
+ return err;
+}
+
+nsresult nsMsgDatabase::GetPropertyAsNSString(nsIMdbRow *row, const char *propertyName, nsAString &result)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnTonsString(row, property_token, result);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::SetPropertyFromNSString(nsIMdbRow *row, const char *propertyName, const nsAString &propertyVal)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ return SetNSStringPropertyWithToken(row, property_token, propertyVal);
+
+ return err;
+}
+
+
+nsresult nsMsgDatabase::GetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t *result, uint32_t defaultValue)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnToUInt32(row, property_token, result, defaultValue);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::GetUint64Property(nsIMdbRow *row, const char *propertyName, uint64_t *result, uint64_t defaultValue)
+{
+ nsresult err = NS_OK;
+ mdb_token property_token;
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ err = RowCellColumnToUInt64(row, property_token, result, defaultValue);
+
+ return err;
+}
+
+nsresult nsMsgDatabase::SetUint32Property(nsIMdbRow *row, const char *propertyName, uint32_t propertyVal)
+{
+ struct mdbYarn yarn;
+ char int32StrBuf[20];
+ yarn.mYarn_Buf = int32StrBuf;
+ yarn.mYarn_Size = sizeof(int32StrBuf);
+ yarn.mYarn_Fill = sizeof(int32StrBuf);
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token property_token;
+
+ nsresult err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ {
+ UInt32ToYarn(&yarn, propertyVal);
+ err = row->AddColumn(GetEnv(), property_token, &yarn);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::SetUint64Property(nsIMdbRow *row,
+ const char *propertyName,
+ uint64_t propertyVal)
+{
+ struct mdbYarn yarn;
+ char int64StrBuf[100];
+ yarn.mYarn_Buf = int64StrBuf;
+ yarn.mYarn_Size = sizeof(int64StrBuf);
+ yarn.mYarn_Fill = sizeof(int64StrBuf);
+
+ NS_ENSURE_STATE(m_mdbStore); // db might have been closed out from under us.
+ if (!row)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token property_token;
+
+ nsresult err = m_mdbStore->StringToToken(GetEnv(), propertyName, &property_token);
+ if (NS_SUCCEEDED(err))
+ {
+ UInt64ToYarn(&yarn, propertyVal);
+ err = row->AddColumn(GetEnv(), property_token, &yarn);
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::GetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool *result,
+ bool defaultValue /* = false */)
+{
+ uint32_t res;
+ nsresult rv = GetUint32Property(row, propertyName, &res, (uint32_t) defaultValue);
+ *result = !!res;
+ return rv;
+}
+
+nsresult nsMsgDatabase::SetBooleanProperty(nsIMdbRow *row, const char *propertyName,
+ bool propertyVal)
+{
+ return SetUint32Property(row, propertyName, (uint32_t) propertyVal);
+}
+
+nsresult nsMsgDatabase::SetNSStringPropertyWithToken(nsIMdbRow *row, mdb_token aProperty, const nsAString &propertyStr)
+{
+ NS_ENSURE_ARG(row);
+ struct mdbYarn yarn;
+
+ yarn.mYarn_Grow = NULL;
+ nsresult err = row->AddColumn(GetEnv(), aProperty, nsStringToYarn(&yarn, propertyStr));
+ free((char *)yarn.mYarn_Buf); // won't need this when we have nsCString
+ return err;
+}
+
+
+uint32_t nsMsgDatabase::GetCurVersion()
+{
+ return kMsgDBVersion;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetSummaryValid(bool valid /* = true */)
+{
+ // If the file was invalid when opened (for example in folder compact), then it may
+ // not have been added to the cache. Add it now if missing.
+ if (valid)
+ {
+ nsCOMPtr<nsIMsgDBService> serv(mozilla::services::GetDBService());
+ static_cast<nsMsgDBService*>(serv.get())->EnsureCached(this);
+ }
+ // setting the version to 0 ought to make it pretty invalid.
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->SetVersion(valid ? GetCurVersion() : 0);
+
+ // for default db (and news), there's no nothing to set to make it it valid
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::GetSummaryValid(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+
+// protected routines
+
+// should we thread messages with common subjects that don't start with Re: together?
+// I imagine we might have separate preferences for mail and news, so this is a virtual method.
+bool nsMsgDatabase::ThreadBySubjectWithoutRe()
+{
+ GetGlobalPrefs();
+ return gThreadWithoutRe;
+}
+
+bool nsMsgDatabase::UseStrictThreading()
+{
+ GetGlobalPrefs();
+ return gStrictThreading;
+}
+
+// Should we make sure messages are always threaded correctly (see bug 181446)
+bool nsMsgDatabase::UseCorrectThreading()
+{
+ GetGlobalPrefs();
+ return gCorrectThreading;
+}
+
+// adapted from removed PL_DHashFreeStringKey
+static void
+msg_DHashFreeStringKey(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+ const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry;
+ free((void*)stub->key);
+ PLDHashTable::ClearEntryStub(aTable, aEntry);
+}
+
+PLDHashTableOps nsMsgDatabase::gRefHashTableOps =
+{
+ PLDHashTable::HashStringKey,
+ PLDHashTable::MatchStringKey,
+ PLDHashTable::MoveEntryStub,
+ msg_DHashFreeStringKey,
+ nullptr
+};
+
+nsresult nsMsgDatabase::GetRefFromHash(nsCString &reference, nsMsgKey *threadId)
+{
+ // Initialize the reference hash
+ if (!m_msgReferences)
+ {
+ nsresult rv = InitRefHash();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Find reference from the hash
+ PLDHashEntryHdr *entry =
+ m_msgReferences->Search((const void *) reference.get());
+ if (entry)
+ {
+ RefHashElement *element = static_cast<RefHashElement *>(entry);
+ *threadId = element->mThreadId;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgDatabase::AddRefToHash(nsCString &reference, nsMsgKey threadId)
+{
+ if (m_msgReferences)
+ {
+ PLDHashEntryHdr *entry = m_msgReferences->Add((void *) reference.get(), mozilla::fallible);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY; // XXX out of memory
+
+ RefHashElement *element = static_cast<RefHashElement *>(entry);
+ if (!element->mRef)
+ {
+ element->mRef = ToNewCString(reference); // Will be freed in msg_DHashFreeStringKey()
+ element->mThreadId = threadId;
+ element->mCount = 1;
+ }
+ else
+ element->mCount++;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::AddMsgRefsToHash(nsIMsgDBHdr *msgHdr)
+{
+ uint16_t numReferences = 0;
+ nsMsgKey threadId;
+ nsresult rv = NS_OK;
+
+ msgHdr->GetThreadId(&threadId);
+ msgHdr->GetNumReferences(&numReferences);
+
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString reference;
+
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ rv = AddRefToHash(reference, threadId);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDatabase::RemoveRefFromHash(nsCString &reference)
+{
+ if (m_msgReferences)
+ {
+ PLDHashEntryHdr *entry =
+ m_msgReferences->Search((const void *) reference.get());
+ if (entry)
+ {
+ RefHashElement *element = static_cast<RefHashElement *>(entry);
+ if (--element->mCount == 0)
+ m_msgReferences->Remove((void *) reference.get());
+ }
+ }
+ return NS_OK;
+}
+
+// Filter only messages with one or more references
+nsresult nsMsgDatabase::RemoveMsgRefsFromHash(nsIMsgDBHdr *msgHdr)
+{
+ uint16_t numReferences = 0;
+ nsresult rv = NS_OK;
+
+ msgHdr->GetNumReferences(&numReferences);
+
+ for (int32_t i = 0; i < numReferences; i++)
+ {
+ nsAutoCString reference;
+
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty())
+ break;
+
+ rv = RemoveRefFromHash(reference);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+static nsresult nsReferencesOnlyFilter(nsIMsgDBHdr *msg, void *closure)
+{
+ uint16_t numReferences = 0;
+ msg->GetNumReferences(&numReferences);
+ return (numReferences) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgDatabase::InitRefHash()
+{
+ // Delete an existing table just in case
+ if (m_msgReferences)
+ delete m_msgReferences;
+
+ // Create new table
+ m_msgReferences = new PLDHashTable(&gRefHashTableOps, sizeof(struct RefHashElement), MSG_HASH_SIZE);
+ if (!m_msgReferences)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Create enumerator to go through all messages with references
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ enumerator = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsReferencesOnlyFilter, nullptr);
+ if (enumerator == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Populate table with references of existing messages
+ bool hasMore;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(supports);
+ if (msgHdr && NS_SUCCEEDED(rv))
+ rv = AddMsgRefsToHash(msgHdr);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDatabase::CreateNewThread(nsMsgKey threadId, const char *subject, nsMsgThread **pnewThread)
+{
+ nsresult err = NS_OK;
+ nsCOMPtr<nsIMdbTable> threadTable;
+ struct mdbOid threadTableOID;
+ struct mdbOid allThreadsTableOID;
+
+ if (!pnewThread || !m_mdbStore)
+ return NS_ERROR_NULL_POINTER;
+
+ threadTableOID.mOid_Scope = m_hdrRowScopeToken;
+ threadTableOID.mOid_Id = threadId;
+
+ // Under some circumstances, mork seems to reuse an old table when we create one.
+ // Prevent problems from that by finding any old table first, and deleting its rows.
+ nsresult res = GetStore()->GetTable(GetEnv(), &threadTableOID, getter_AddRefs(threadTable));
+ if (NS_SUCCEEDED(res) && threadTable)
+ threadTable->CutAllRows(GetEnv());
+
+ err = GetStore()->NewTableWithOid(GetEnv(), &threadTableOID, m_threadTableKindToken,
+ false, nullptr, getter_AddRefs(threadTable));
+ if (NS_FAILED(err))
+ return err;
+
+ allThreadsTableOID.mOid_Scope = m_threadRowScopeToken;
+ allThreadsTableOID.mOid_Id = threadId;
+
+ // add a row for this thread in the table of all threads that we'll use
+ // to do our mapping between subject strings and threads.
+ nsCOMPtr<nsIMdbRow> threadRow;
+
+ err = m_mdbStore->GetRow(GetEnv(), &allThreadsTableOID,
+ getter_AddRefs(threadRow));
+ if (!threadRow)
+ {
+ err = m_mdbStore->NewRowWithOid(GetEnv(), &allThreadsTableOID,
+ getter_AddRefs(threadRow));
+ if (NS_SUCCEEDED(err) && threadRow)
+ {
+ if (m_mdbAllThreadsTable)
+ m_mdbAllThreadsTable->AddRow(GetEnv(), threadRow);
+ err = CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject);
+ }
+ }
+ else
+ {
+#ifdef DEBUG_David_Bienvenu
+ NS_WARNING("odd that thread row already exists");
+#endif
+ threadRow->CutAllColumns(GetEnv());
+ nsCOMPtr<nsIMdbRow> metaRow;
+ threadTable->GetMetaRow(GetEnv(), nullptr, nullptr, getter_AddRefs(metaRow));
+ if (metaRow)
+ metaRow->CutAllColumns(GetEnv());
+
+ CharPtrToRowCellColumn(threadRow, m_threadSubjectColumnToken, subject);
+ }
+
+
+ *pnewThread = new nsMsgThread(this, threadTable);
+ if (*pnewThread)
+ {
+ (*pnewThread)->SetThreadKey(threadId);
+ m_cachedThread = *pnewThread;
+ m_cachedThreadId = threadId;
+ }
+ return err;
+}
+
+
+nsIMsgThread *nsMsgDatabase::GetThreadForReference(nsCString &msgID, nsIMsgDBHdr **pMsgHdr)
+{
+ nsMsgKey threadId;
+ nsIMsgDBHdr *msgHdr = nullptr;
+ GetMsgHdrForMessageID(msgID.get(), &msgHdr);
+ nsIMsgThread *thread = NULL;
+
+ if (msgHdr != NULL)
+ {
+ if (NS_SUCCEEDED(msgHdr->GetThreadId(&threadId)))
+ {
+ // find thread header for header whose message id we matched.
+ thread = GetThreadForThreadId(threadId);
+ }
+ if (pMsgHdr)
+ *pMsgHdr = msgHdr;
+ else
+ msgHdr->Release();
+ }
+ // Referenced message not found, check if there are messages that reference same message
+ else if (UseCorrectThreading())
+ {
+ if (NS_SUCCEEDED(GetRefFromHash(msgID, &threadId)))
+ thread = GetThreadForThreadId(threadId);
+ }
+
+ return thread;
+}
+
+nsIMsgThread * nsMsgDatabase::GetThreadForSubject(nsCString &subject)
+{
+ nsIMsgThread *thread = nullptr;
+
+ mdbYarn subjectYarn;
+
+ subjectYarn.mYarn_Buf = (void*)subject.get();
+ subjectYarn.mYarn_Fill = PL_strlen(subject.get());
+ subjectYarn.mYarn_Form = 0;
+ subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill;
+
+ nsCOMPtr <nsIMdbRow> threadRow;
+ mdbOid outRowId;
+ if (m_mdbStore)
+ {
+ nsresult result = m_mdbStore->FindRow(GetEnv(), m_threadRowScopeToken,
+ m_threadSubjectColumnToken, &subjectYarn, &outRowId, getter_AddRefs(threadRow));
+ if (NS_SUCCEEDED(result) && threadRow)
+ {
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ if (NS_SUCCEEDED(threadRow->GetOid(GetEnv(), &outOid)))
+ key = outOid.mOid_Id;
+ // find thread header for header whose message id we matched.
+ // It is fine if key was not found,
+ // GetThreadForThreadId(nsMsgKey_None) returns nullptr.
+ thread = GetThreadForThreadId(key);
+ }
+#ifdef DEBUG_bienvenu1
+ else
+ {
+ nsresult rv;
+ RefPtr<nsMsgThread> pThread;
+
+ nsCOMPtr <nsIMdbPortTableCursor> tableCursor;
+ m_mdbStore->GetPortTableCursor(GetEnv(), m_hdrRowScopeToken, m_threadTableKindToken,
+ getter_AddRefs(tableCursor));
+
+ nsCOMPtr<nsIMdbTable> table;
+
+ while (true)
+ {
+ rv = tableCursor->NextTable(GetEnv(), getter_AddRefs(table));
+ if (!table)
+ break;
+ if (NS_FAILED(rv))
+ break;
+
+ pThread = new nsMsgThread(this, table);
+ if (pThread)
+ {
+ nsCString curSubject;
+ pThread->GetSubject(curSubject);
+ if (subject.Equals(curSubject))
+ {
+ NS_ERROR("thread with subject exists, but FindRow didn't find it\n");
+ break;
+ }
+ }
+ else
+ break;
+ }
+ }
+#endif
+ }
+ return thread;
+}
+
+// Returns thread that contains a message that references the passed message ID
+nsIMsgThread *nsMsgDatabase::GetThreadForMessageId(nsCString &msgId)
+{
+ nsIMsgThread *thread = NULL;
+ nsMsgKey threadId;
+
+ if (NS_SUCCEEDED(GetRefFromHash(msgId, &threadId)))
+ thread = GetThreadForThreadId(threadId);
+
+ return thread;
+}
+
+nsresult nsMsgDatabase::ThreadNewHdr(nsMsgHdr* newHdr, bool &newThread)
+{
+ nsresult result=NS_ERROR_UNEXPECTED;
+ nsCOMPtr <nsIMsgThread> thread;
+ nsCOMPtr <nsIMsgDBHdr> replyToHdr;
+ nsMsgKey threadId = nsMsgKey_None, newHdrKey;
+
+ if (!newHdr)
+ return NS_ERROR_NULL_POINTER;
+
+ newHdr->SetThreadParent(nsMsgKey_None); // if we're undoing, could have a thread parent
+ uint16_t numReferences = 0;
+ uint32_t newHdrFlags = 0;
+
+ // use raw flags instead of GetFlags, because GetFlags will
+ // pay attention to what's in m_newSet, and this new hdr isn't
+ // in m_newSet yet.
+ newHdr->GetRawFlags(&newHdrFlags);
+ newHdr->GetNumReferences(&numReferences);
+ newHdr->GetMessageKey(&newHdrKey);
+
+ // try reference threading first
+ for (int32_t i = numReferences - 1; i >= 0; i--)
+ {
+ nsAutoCString reference;
+
+ newHdr->GetStringReference(i, reference);
+ // first reference we have hdr for is best top-level hdr.
+ // but we have to handle case of promoting new header to top-level
+ // in case the top-level header comes after a reply.
+
+ if (reference.IsEmpty())
+ break;
+
+ thread = dont_AddRef(GetThreadForReference(reference, getter_AddRefs(replyToHdr))) ;
+ if (thread)
+ {
+ if (replyToHdr)
+ {
+ nsMsgKey replyToKey;
+ replyToHdr->GetMessageKey(&replyToKey);
+ // message claims to be a reply to itself - ignore that since it leads to corrupt threading.
+ if (replyToKey == newHdrKey)
+ {
+ // bad references - throw them all away.
+ newHdr->SetMessageId("");
+ thread = nullptr;
+ break;
+ }
+ }
+ thread->GetThreadKey(&threadId);
+ newHdr->SetThreadId(threadId);
+ result = AddToThread(newHdr, thread, replyToHdr, true);
+ break;
+ }
+ }
+ // if user hasn't said "only thread by ref headers", thread by subject
+ if (!thread && !UseStrictThreading())
+ {
+ // try subject threading if we couldn't find a reference and the subject starts with Re:
+ nsCString subject;
+ newHdr->GetSubject(getter_Copies(subject));
+ if (ThreadBySubjectWithoutRe() || (newHdrFlags & nsMsgMessageFlags::HasRe))
+ {
+ nsAutoCString cSubject(subject);
+ thread = dont_AddRef(GetThreadForSubject(cSubject));
+ if(thread)
+ {
+ thread->GetThreadKey(&threadId);
+ newHdr->SetThreadId(threadId);
+ //TRACE("threading based on subject %s\n", (const char *) msgHdr->m_subject);
+ // if we move this and do subject threading after, ref threading,
+ // don't thread within children, since we know it won't work. But for now, pass TRUE.
+ result = AddToThread(newHdr, thread, nullptr, true);
+ }
+ }
+ }
+
+ // Check if this is a new parent to an existing message (that has a reference to this message)
+ if (!thread && UseCorrectThreading())
+ {
+ nsCString msgId;
+ newHdr->GetMessageId(getter_Copies(msgId));
+
+ thread = dont_AddRef(GetThreadForMessageId(msgId));
+ if (thread)
+ {
+ thread->GetThreadKey(&threadId);
+ newHdr->SetThreadId(threadId);
+ result = AddToThread(newHdr, thread, nullptr, true);
+ }
+ }
+
+ if (!thread)
+ {
+ // Not a parent or child, make it a new thread for now
+ result = AddNewThread(newHdr);
+ newThread = true;
+ }
+ else
+ {
+ newThread = false;
+ }
+ return result;
+}
+
+nsresult nsMsgDatabase::AddToThread(nsMsgHdr *newHdr, nsIMsgThread *thread, nsIMsgDBHdr *inReplyTo, bool threadInThread)
+{
+ // don't worry about real threading yet.
+ nsCOMPtr <nsIDBChangeAnnouncer> announcer = do_QueryInterface(this);
+
+ return thread->AddChild(newHdr, inReplyTo, threadInThread, announcer);
+}
+
+nsMsgHdr * nsMsgDatabase::GetMsgHdrForReference(nsCString &reference)
+{
+ NS_ASSERTION(false, "not implemented yet.");
+ return nullptr;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForMessageID(const char *aMsgID, nsIMsgDBHdr **aHdr)
+{
+ NS_ENSURE_ARG_POINTER(aHdr);
+ NS_ENSURE_ARG_POINTER(aMsgID);
+ nsIMsgDBHdr *msgHdr = nullptr;
+ nsresult rv = NS_OK;
+ mdbYarn messageIdYarn;
+
+ messageIdYarn.mYarn_Buf = (void *) aMsgID;
+ messageIdYarn.mYarn_Fill = PL_strlen(aMsgID);
+ messageIdYarn.mYarn_Form = 0;
+ messageIdYarn.mYarn_Size = messageIdYarn.mYarn_Fill;
+
+ nsIMdbRow *hdrRow;
+ mdbOid outRowId;
+ nsresult result;
+ if (m_mdbStore)
+ result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken,
+ m_messageIdColumnToken, &messageIdYarn, &outRowId,
+ &hdrRow);
+ else
+ return NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(result) && hdrRow)
+ {
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ rv = hdrRow->GetOid(GetEnv(), &outOid);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ key = outOid.mOid_Id;
+
+ rv = GetHdrFromUseCache(key, &msgHdr);
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ hdrRow->Release();
+ else {
+ rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+ }
+ *aHdr = msgHdr; // already addreffed above.
+ return NS_OK; // it's not an error not to find a msg hdr.
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgHdrForGMMsgID(const char *aGMMsgId, nsIMsgDBHdr **aHdr)
+{
+ NS_ENSURE_ARG_POINTER(aGMMsgId);
+ NS_ENSURE_ARG_POINTER(aHdr);
+ nsIMsgDBHdr *msgHdr = nullptr;
+ nsresult rv = NS_OK;
+ mdbYarn gMailMessageIdYarn;
+ gMailMessageIdYarn.mYarn_Buf = (void *) aGMMsgId;
+ gMailMessageIdYarn.mYarn_Fill = strlen(aGMMsgId);
+ gMailMessageIdYarn.mYarn_Form = 0;
+ gMailMessageIdYarn.mYarn_Size = gMailMessageIdYarn.mYarn_Fill;
+
+ nsIMdbRow *hdrRow;
+ mdbOid outRowId;
+ nsresult result;
+ mdb_token property_token;
+ NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
+ result = m_mdbStore->StringToToken(GetEnv(), "X-GM-MSGID",
+ &property_token);
+ NS_ENSURE_SUCCESS(result, result);
+ result = m_mdbStore->FindRow(GetEnv(), m_hdrRowScopeToken,
+ property_token, &gMailMessageIdYarn, &outRowId, &hdrRow);
+ if (NS_SUCCEEDED(result) && hdrRow)
+ {
+ // Get key from row
+ mdbOid outOid;
+ rv = hdrRow->GetOid(GetEnv(), &outOid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey key = outOid.mOid_Id;
+ rv = GetHdrFromUseCache(key, &msgHdr);
+ if ((NS_SUCCEEDED(rv) && msgHdr))
+ hdrRow->Release();
+ else {
+ rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+ }
+ *aHdr = msgHdr;
+ return NS_OK; // it's not an error not to find a msg hdr.
+}
+
+nsIMsgDBHdr *nsMsgDatabase::GetMsgHdrForSubject(nsCString &subject)
+{
+ nsIMsgDBHdr *msgHdr = nullptr;
+ nsresult rv = NS_OK;
+ mdbYarn subjectYarn;
+
+ subjectYarn.mYarn_Buf = (void*)subject.get();
+ subjectYarn.mYarn_Fill = PL_strlen(subject.get());
+ subjectYarn.mYarn_Form = 0;
+ subjectYarn.mYarn_Size = subjectYarn.mYarn_Fill;
+
+ nsIMdbRow *hdrRow;
+ mdbOid outRowId;
+ nsresult result = GetStore()->FindRow(GetEnv(), m_hdrRowScopeToken,
+ m_subjectColumnToken, &subjectYarn, &outRowId,
+ &hdrRow);
+ if (NS_SUCCEEDED(result) && hdrRow)
+ {
+ //Get key from row
+ mdbOid outOid;
+ nsMsgKey key = nsMsgKey_None;
+ rv = hdrRow->GetOid(GetEnv(), &outOid);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return nullptr;
+ key = outOid.mOid_Id;
+
+ rv = GetHdrFromUseCache(key, &msgHdr);
+ if (NS_SUCCEEDED(rv) && msgHdr)
+ hdrRow->Release();
+ else {
+ rv = CreateMsgHdr(hdrRow, key, &msgHdr);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return nullptr;
+ }
+ }
+ return msgHdr;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **result)
+{
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+ nsMsgKey threadId = nsMsgKey_None;
+ (void)msgHdr->GetThreadId(&threadId);
+ if (threadId != nsMsgKey_None)
+ *result = GetThreadForThreadId(threadId);
+
+ // if we can't find the thread, try using the msg key as the thread id,
+ // because the msg hdr might not have the thread id set correctly
+ // Or maybe the message was deleted?
+ if (!*result)
+ {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ *result = GetThreadForThreadId(msgKey);
+ }
+ // failure is normal when message was deleted
+ return (*result) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+
+nsresult nsMsgDatabase::GetThreadForMsgKey(nsMsgKey msgKey, nsIMsgThread **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr <nsIMsgDBHdr> msg;
+ nsresult rv = GetMsgHdrForKey(msgKey, getter_AddRefs(msg));
+
+ if (NS_SUCCEEDED(rv) && msg)
+ rv = GetThreadContainingMsgHdr(msg, aResult);
+
+ return rv;
+}
+
+// caller needs to unrefer.
+nsIMsgThread * nsMsgDatabase::GetThreadForThreadId(nsMsgKey threadId)
+{
+
+ nsIMsgThread *retThread = (threadId == m_cachedThreadId && m_cachedThread) ?
+ m_cachedThread.get() : FindExistingThread(threadId);
+ if (retThread)
+ {
+ NS_ADDREF(retThread);
+ return retThread;
+ }
+ if (m_mdbStore)
+ {
+ mdbOid tableId;
+ tableId.mOid_Id = threadId;
+ tableId.mOid_Scope = m_hdrRowScopeToken;
+
+ nsCOMPtr<nsIMdbTable> threadTable;
+ nsresult res = m_mdbStore->GetTable(GetEnv(), &tableId,
+ getter_AddRefs(threadTable));
+
+ if (NS_SUCCEEDED(res) && threadTable)
+ {
+ retThread = new nsMsgThread(this, threadTable);
+ if (retThread)
+ {
+ NS_ADDREF(retThread);
+ m_cachedThread = retThread;
+ m_cachedThreadId = threadId;
+ }
+ }
+ }
+ return retThread;
+}
+
+// make the passed in header a thread header
+nsresult nsMsgDatabase::AddNewThread(nsMsgHdr *msgHdr)
+{
+
+ if (!msgHdr)
+ return NS_ERROR_NULL_POINTER;
+
+ nsMsgThread *threadHdr = nullptr;
+
+ nsCString subject;
+ nsMsgKey threadKey = msgHdr->m_messageKey;
+ // can't have a thread with key 1 since that's the table id of the all msg hdr table,
+ // so give it kTableKeyForThreadOne (0xfffffffe).
+ if (threadKey == kAllMsgHdrsTableKey)
+ threadKey = kTableKeyForThreadOne;
+
+ nsresult err = msgHdr->GetSubject(getter_Copies(subject));
+
+ err = CreateNewThread(threadKey, subject.get(), &threadHdr);
+ msgHdr->SetThreadId(threadKey);
+ if (threadHdr)
+ {
+ threadHdr->AddRef();
+ // err = msgHdr->GetSubject(subject);
+ // threadHdr->SetThreadKey(msgHdr->m_messageKey);
+ // threadHdr->SetSubject(subject.get());
+ // need to add the thread table to the db.
+ AddToThread(msgHdr, threadHdr, nullptr, false);
+ threadHdr->Release();
+ }
+ return err;
+}
+
+nsresult nsMsgDatabase::GetBoolPref(const char *prefName, bool *result)
+{
+ bool prefValue = false;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetBoolPref(prefName, &prefValue);
+ *result = prefValue;
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::GetIntPref(const char *prefName, int32_t *result)
+{
+ int32_t prefValue = 0;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (pPrefBranch)
+ {
+ rv = pPrefBranch->GetIntPref(prefName, &prefValue);
+ *result = prefValue;
+ }
+ return rv;
+}
+
+
+nsresult nsMsgDatabase::ListAllThreads(nsTArray<nsMsgKey> *threadIds)
+{
+ nsresult rv;
+ nsMsgThread *pThread;
+
+ nsCOMPtr <nsISimpleEnumerator> threads;
+ rv = EnumerateThreads(getter_AddRefs(threads));
+ if (NS_FAILED(rv)) return rv;
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = threads->GetNext((nsISupports**)&pThread);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (threadIds) {
+ nsMsgKey key;
+ (void)pThread->GetThreadKey(&key);
+ threadIds->AppendElement(key);
+ }
+ // NS_RELEASE(pThread);
+ pThread = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetAttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ const char *propertyVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetUint32AttributeOnPendingHdr(nsIMsgDBHdr *pendingHdr, const char *property,
+ uint32_t propertyVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::SetUint64AttributeOnPendingHdr(nsIMsgDBHdr *aPendingHdr,
+ const char *aProperty,
+ uint64_t aPropertyVal)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::UpdatePendingAttributes(nsIMsgDBHdr *aNewHdr)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetOfflineOpForKey(nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation **offlineOp)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation *op)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsMsgDatabase::ListAllOfflineMsgs(nsIMsgKeyArray *aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aKeys);
+ nsCOMPtr <nsISimpleEnumerator> enumerator;
+ uint32_t flag = nsMsgMessageFlags::Offline;
+ // if we change this routine to return an enumerator that generates the keys
+ // one by one, we'll need to somehow make a copy of flag for the enumerator
+ // to own, since the enumerator will persist past the life of flag on the stack.
+ nsresult rv = EnumerateMessagesWithFlag(getter_AddRefs(enumerator), &flag);
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMoreElements;
+ while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && hasMoreElements)
+ {
+ nsCOMPtr<nsISupports> childSupports;
+ rv = enumerator->GetNext(getter_AddRefs(childSupports));
+ if(NS_FAILED(rv))
+ return rv;
+
+ // clear out db hdr, because it won't be valid when we get rid of the .msf file
+ nsCOMPtr<nsIMsgDBHdr> dbMessage(do_QueryInterface(childSupports, &rv));
+ if(NS_SUCCEEDED(rv) && dbMessage)
+ {
+ nsMsgKey msgKey;
+ dbMessage->GetMessageKey(&msgKey);
+ aKeys->AppendElement(msgKey);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDatabase::EnumerateOfflineOps(nsISimpleEnumerator **enumerator)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ListAllOfflineOpIds(nsTArray<nsMsgKey> *offlineOpIds)
+{
+ NS_ASSERTION(false, "overridden by nsMailDatabase");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ListAllOfflineDeletes(nsTArray<nsMsgKey> *offlineDeletes)
+{
+ nsresult ret = NS_OK;
+ if (!offlineDeletes)
+ return NS_ERROR_NULL_POINTER;
+
+ // technically, notimplemented, but no one's putting offline ops in anyway.
+ return ret;
+}
+NS_IMETHODIMP nsMsgDatabase::GetHighWaterArticleNum(nsMsgKey *key)
+{
+ if (!m_dbFolderInfo)
+ return NS_ERROR_NULL_POINTER;
+ return m_dbFolderInfo->GetHighWater(key);
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetLowWaterArticleNum(nsMsgKey *key)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* attribute nsMsgKey NextPseudoMsgKey */
+
+NS_IMETHODIMP nsMsgDatabase::GetNextPseudoMsgKey(nsMsgKey *nextPseudoMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(nextPseudoMsgKey);
+ *nextPseudoMsgKey = m_nextPseudoMsgKey--;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetNextPseudoMsgKey(nsMsgKey nextPseudoMsgKey)
+{
+ m_nextPseudoMsgKey = nextPseudoMsgKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetNextFakeOfflineMsgKey(nsMsgKey *nextFakeOfflineMsgKey)
+{
+ NS_ENSURE_ARG_POINTER(nextFakeOfflineMsgKey);
+ // iterate over hdrs looking for first non-existant fake offline msg key
+ nsMsgKey fakeMsgKey = kIdStartOfFake;
+
+ bool containsKey;
+ do
+ {
+ ContainsKey(fakeMsgKey, &containsKey);
+ if (!containsKey)
+ break;
+ fakeMsgKey--;
+ }
+ while (containsKey);
+
+ *nextFakeOfflineMsgKey = fakeMsgKey;
+ return NS_OK;
+}
+
+#ifdef DEBUG
+nsresult nsMsgDatabase::DumpContents()
+{
+ nsMsgKey key;
+ uint32_t i;
+
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ if (!keys)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = ListAllKeys(keys);
+ uint32_t numKeys;
+ keys->GetLength(&numKeys);
+ for (i = 0; i < numKeys; i++) {
+ key = keys->m_keys[i];
+ nsIMsgDBHdr *msg = NULL;
+ rv = GetMsgHdrForKey(key, &msg);
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(msg); // closed system, cast ok
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCString author;
+ nsCString subject;
+
+ msgHdr->GetMessageKey(&key);
+ msgHdr->GetAuthor(getter_Copies(author));
+ msgHdr->GetSubject(getter_Copies(subject));
+ printf("hdr key = %u, author = %s subject = %s\n", key, author.get(), subject.get());
+ NS_RELEASE(msgHdr);
+ }
+ }
+ nsTArray<nsMsgKey> threads;
+ rv = ListAllThreads(&threads);
+ for ( i = 0; i < threads.Length(); i++)
+ {
+ key = threads[i];
+ printf("thread key = %u\n", key);
+ // DumpThread(key);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::DumpMsgChildren(nsIMsgDBHdr *msgHdr)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::DumpThread(nsMsgKey threadId)
+{
+ nsresult rv = NS_OK;
+ nsIMsgThread *thread = nullptr;
+
+ thread = GetThreadForThreadId(threadId);
+ if (thread)
+ {
+ nsISimpleEnumerator *enumerator = nullptr;
+
+ rv = thread->EnumerateMessages(nsMsgKey_None, &enumerator);
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ nsCOMPtr <nsIMsgDBHdr> pMessage = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !pMessage)
+ break;
+ }
+ NS_RELEASE(enumerator);
+
+ }
+ }
+ return rv;
+}
+#endif /* DEBUG */
+
+NS_IMETHODIMP nsMsgDatabase::SetMsgRetentionSettings(nsIMsgRetentionSettings *retentionSettings)
+{
+ m_retentionSettings = retentionSettings;
+ if (retentionSettings && m_dbFolderInfo)
+ {
+ nsresult rv;
+
+ nsMsgRetainByPreference retainByPreference;
+ uint32_t daysToKeepHdrs;
+ uint32_t numHeadersToKeep;
+ uint32_t daysToKeepBodies;
+ bool cleanupBodiesByDays;
+ bool useServerDefaults;
+ bool applyToFlaggedMessages;
+
+ rv = retentionSettings->GetRetainByPreference(&retainByPreference);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = retentionSettings->GetDaysToKeepBodies(&daysToKeepBodies);
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void) retentionSettings->GetCleanupBodiesByDays(&cleanupBodiesByDays);
+ (void) retentionSettings->GetUseServerDefaults(&useServerDefaults);
+ rv = retentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // need to write this to the db. We'll just use the dbfolderinfo to write properties.
+ m_dbFolderInfo->SetUint32Property("retainBy", retainByPreference);
+ m_dbFolderInfo->SetUint32Property("daysToKeepHdrs", daysToKeepHdrs);
+ m_dbFolderInfo->SetUint32Property("numHdrsToKeep", numHeadersToKeep);
+ m_dbFolderInfo->SetUint32Property("daysToKeepBodies", daysToKeepBodies);
+ m_dbFolderInfo->SetBooleanProperty("cleanupBodies", cleanupBodiesByDays);
+ m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults);
+ m_dbFolderInfo->SetBooleanProperty("applyToFlaggedMessages", applyToFlaggedMessages);
+ }
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgRetentionSettings(nsIMsgRetentionSettings **retentionSettings)
+{
+ NS_ENSURE_ARG_POINTER(retentionSettings);
+ if (!m_retentionSettings)
+ {
+ // create a new one, and initialize it from the db.
+ m_retentionSettings = new nsMsgRetentionSettings;
+ if (m_retentionSettings && m_dbFolderInfo)
+ {
+ nsMsgRetainByPreference retainByPreference;
+ uint32_t daysToKeepHdrs = 0;
+ uint32_t numHeadersToKeep = 0;
+ bool useServerDefaults;
+ uint32_t daysToKeepBodies = 0;
+ bool cleanupBodiesByDays = false;
+ bool applyToFlaggedMessages;
+
+ m_dbFolderInfo->GetUint32Property("retainBy", nsIMsgRetentionSettings::nsMsgRetainAll, &retainByPreference);
+ m_dbFolderInfo->GetUint32Property("daysToKeepHdrs", 0, &daysToKeepHdrs);
+ m_dbFolderInfo->GetUint32Property("numHdrsToKeep", 0, &numHeadersToKeep);
+ m_dbFolderInfo->GetUint32Property("daysToKeepBodies", 0, &daysToKeepBodies);
+ m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true, &useServerDefaults);
+ m_dbFolderInfo->GetBooleanProperty("cleanupBodies", false, &cleanupBodiesByDays);
+ m_dbFolderInfo->GetBooleanProperty("applyToFlaggedMessages", false,
+ &applyToFlaggedMessages);
+ m_retentionSettings->SetRetainByPreference(retainByPreference);
+ m_retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs);
+ m_retentionSettings->SetNumHeadersToKeep(numHeadersToKeep);
+ m_retentionSettings->SetDaysToKeepBodies(daysToKeepBodies);
+ m_retentionSettings->SetUseServerDefaults(useServerDefaults);
+ m_retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays);
+ m_retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages);
+ }
+ }
+ *retentionSettings = m_retentionSettings;
+ NS_IF_ADDREF(*retentionSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::SetMsgDownloadSettings(nsIMsgDownloadSettings *downloadSettings)
+{
+ m_downloadSettings = downloadSettings;
+ if (downloadSettings && m_dbFolderInfo)
+ {
+ nsresult rv;
+
+ bool useServerDefaults;
+ bool downloadByDate;
+ uint32_t ageLimitOfMsgsToDownload;
+ bool downloadUnreadOnly;
+
+ rv = downloadSettings->GetUseServerDefaults(&useServerDefaults);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadSettings->GetDownloadByDate(&downloadByDate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // need to write this to the db. We'll just use the dbfolderinfo to write properties.
+ m_dbFolderInfo->SetBooleanProperty("useServerDefaults", useServerDefaults);
+ m_dbFolderInfo->SetBooleanProperty("downloadByDate", downloadByDate);
+ m_dbFolderInfo->SetBooleanProperty("downloadUnreadOnly", downloadUnreadOnly);
+ m_dbFolderInfo->SetUint32Property("ageLimit", ageLimitOfMsgsToDownload);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetMsgDownloadSettings(nsIMsgDownloadSettings **downloadSettings)
+{
+ NS_ENSURE_ARG_POINTER(downloadSettings);
+ if (!m_downloadSettings)
+ {
+ // create a new one, and initialize it from the db.
+ m_downloadSettings = new nsMsgDownloadSettings;
+ if (m_downloadSettings && m_dbFolderInfo)
+ {
+ bool useServerDefaults;
+ bool downloadByDate;
+ uint32_t ageLimitOfMsgsToDownload;
+ bool downloadUnreadOnly;
+
+ m_dbFolderInfo->GetBooleanProperty("useServerDefaults", true, &useServerDefaults);
+ m_dbFolderInfo->GetBooleanProperty("downloadByDate", false, &downloadByDate);
+ m_dbFolderInfo->GetBooleanProperty("downloadUnreadOnly", false, &downloadUnreadOnly);
+ m_dbFolderInfo->GetUint32Property("ageLimit", 0, &ageLimitOfMsgsToDownload);
+
+ m_downloadSettings->SetUseServerDefaults(useServerDefaults);
+ m_downloadSettings->SetDownloadByDate(downloadByDate);
+ m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly);
+ m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload);
+ }
+ }
+ *downloadSettings = m_downloadSettings;
+ NS_IF_ADDREF(*downloadSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ApplyRetentionSettings(nsIMsgRetentionSettings *aMsgRetentionSettings,
+ bool aDeleteViaFolder)
+{
+ NS_ENSURE_ARG_POINTER(aMsgRetentionSettings);
+ nsresult rv = NS_OK;
+
+ if (!m_folder)
+ return NS_ERROR_NULL_POINTER;
+
+ bool isDraftsTemplatesOutbox;
+ uint32_t dtoFlags = nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates |
+ nsMsgFolderFlags::Queue;
+ (void) m_folder->IsSpecialFolder(dtoFlags, true, &isDraftsTemplatesOutbox);
+ // Never apply retention settings to Drafts/Templates/Outbox.
+ if (isDraftsTemplatesOutbox)
+ return NS_OK;
+
+ nsCOMPtr <nsIMutableArray> msgHdrsToDelete;
+ if (aDeleteViaFolder)
+ {
+ msgHdrsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsMsgRetainByPreference retainByPreference;
+ aMsgRetentionSettings->GetRetainByPreference(&retainByPreference);
+
+ bool applyToFlaggedMessages = false;
+ aMsgRetentionSettings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
+
+ uint32_t daysToKeepHdrs = 0;
+ uint32_t numHeadersToKeep = 0;
+ switch (retainByPreference)
+ {
+ case nsIMsgRetentionSettings::nsMsgRetainAll:
+ break;
+ case nsIMsgRetentionSettings::nsMsgRetainByAge:
+ aMsgRetentionSettings->GetDaysToKeepHdrs(&daysToKeepHdrs);
+ rv = PurgeMessagesOlderThan(daysToKeepHdrs,
+ applyToFlaggedMessages, msgHdrsToDelete);
+ break;
+ case nsIMsgRetentionSettings::nsMsgRetainByNumHeaders:
+ aMsgRetentionSettings->GetNumHeadersToKeep(&numHeadersToKeep);
+ rv = PurgeExcessMessages(numHeadersToKeep,
+ applyToFlaggedMessages, msgHdrsToDelete);
+ break;
+ }
+ if (m_folder)
+ {
+ // update the time we attempted to purge this folder
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded);
+ m_folder->SetStringProperty("LastPurgeTime", nsDependentCString(dateBuf));
+ }
+ if (msgHdrsToDelete)
+ {
+ uint32_t count;
+ msgHdrsToDelete->GetLength(&count);
+ if (count > 0)
+ rv = m_folder->DeleteMessages(msgHdrsToDelete, nullptr, true, false, nullptr, false);
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::PurgeMessagesOlderThan(uint32_t daysToKeepHdrs,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete)
+{
+ nsresult rv = NS_OK;
+ nsMsgHdr *pHeader;
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ rv = EnumerateMessages(getter_AddRefs(hdrs));
+ nsTArray<nsMsgKey> keysToDelete;
+
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+
+ PRTime cutOffDay = PR_Now() - daysToKeepHdrs * PR_USEC_PER_DAY;
+
+ // so now cutOffDay is the PRTime cut-off point. Any msg with a date less than that will get purged.
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ bool purgeHdr = false;
+
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ if (!applyToFlaggedMessages)
+ {
+ uint32_t flags;
+ (void)pHeader->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Marked)
+ continue;
+ }
+
+ if (!purgeHdr)
+ {
+ PRTime date;
+ pHeader->GetDate(&date);
+ if (date < cutOffDay)
+ purgeHdr = true;
+ }
+ if (purgeHdr)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ if (hdrsToDelete)
+ hdrsToDelete->AppendElement(pHeader, false);
+ }
+ NS_RELEASE(pHeader);
+ }
+
+ if (!hdrsToDelete)
+ {
+ DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
+
+ if (keysToDelete.Length() > 10) // compress commit if we deleted more than 10
+ Commit(nsMsgDBCommitType::kCompressCommit);
+ else if (!keysToDelete.IsEmpty())
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+nsresult nsMsgDatabase::PurgeExcessMessages(uint32_t numHeadersToKeep,
+ bool applyToFlaggedMessages,
+ nsIMutableArray *hdrsToDelete)
+{
+ nsresult rv = NS_OK;
+ nsMsgHdr *pHeader;
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+ bool hasMore = false;
+ nsTArray<nsMsgKey> keysToDelete;
+
+ mdb_count numHdrs = 0;
+ if (m_mdbAllMsgHeadersTable)
+ m_mdbAllMsgHeadersTable->GetCount(GetEnv(), &numHdrs);
+ else
+ return NS_ERROR_NULL_POINTER;
+
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ bool purgeHdr = false;
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ break;
+
+ if (!applyToFlaggedMessages)
+ {
+ uint32_t flags;
+ (void)pHeader->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Marked)
+ continue;
+ }
+
+ // this isn't quite right - we want to prefer unread messages (keep all of those we can)
+ if (numHdrs > numHeadersToKeep)
+ purgeHdr = true;
+
+ if (purgeHdr)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ numHdrs--;
+ if (hdrsToDelete)
+ hdrsToDelete->AppendElement(pHeader, false);
+ }
+ NS_RELEASE(pHeader);
+ }
+
+ if (!hdrsToDelete)
+ {
+ int32_t numKeysToDelete = keysToDelete.Length();
+ if (numKeysToDelete > 0)
+ {
+ DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
+ if (numKeysToDelete > 10) // compress commit if we deleted more than 10
+ Commit(nsMsgDBCommitType::kCompressCommit);
+ else
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgRetentionSettings, nsIMsgRetentionSettings)
+
+// Initialise the member variables to resonable defaults.
+nsMsgRetentionSettings::nsMsgRetentionSettings()
+: m_retainByPreference(1),
+ m_daysToKeepHdrs(0),
+ m_numHeadersToKeep(0),
+ m_useServerDefaults(true),
+ m_cleanupBodiesByDays(false),
+ m_daysToKeepBodies(0),
+ m_applyToFlaggedMessages(false)
+{
+}
+
+nsMsgRetentionSettings::~nsMsgRetentionSettings()
+{
+}
+
+/* attribute unsigned long retainByPreference */
+
+NS_IMETHODIMP nsMsgRetentionSettings::GetRetainByPreference(nsMsgRetainByPreference *retainByPreference)
+{
+ NS_ENSURE_ARG_POINTER(retainByPreference);
+ *retainByPreference = m_retainByPreference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgRetentionSettings::SetRetainByPreference(nsMsgRetainByPreference retainByPreference)
+{
+ m_retainByPreference = retainByPreference;
+ return NS_OK;
+}
+
+/* attribute long daysToKeepHdrs; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepHdrs(uint32_t *aDaysToKeepHdrs)
+{
+ NS_ENSURE_ARG_POINTER(aDaysToKeepHdrs);
+ *aDaysToKeepHdrs = m_daysToKeepHdrs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepHdrs(uint32_t aDaysToKeepHdrs)
+{
+ m_daysToKeepHdrs = aDaysToKeepHdrs;
+ return NS_OK;
+}
+
+/* attribute long numHeadersToKeep; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetNumHeadersToKeep(uint32_t *aNumHeadersToKeep)
+{
+ NS_ENSURE_ARG_POINTER(aNumHeadersToKeep);
+ *aNumHeadersToKeep = m_numHeadersToKeep;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetNumHeadersToKeep(uint32_t aNumHeadersToKeep)
+{
+ m_numHeadersToKeep = aNumHeadersToKeep;
+ return NS_OK;
+}
+/* attribute boolean useServerDefaults; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetUseServerDefaults(bool *aUseServerDefaults)
+{
+ NS_ENSURE_ARG_POINTER(aUseServerDefaults);
+ *aUseServerDefaults = m_useServerDefaults;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetUseServerDefaults(bool aUseServerDefaults)
+{
+ m_useServerDefaults = aUseServerDefaults;
+ return NS_OK;
+}
+
+/* attribute boolean cleanupBodiesByDays; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetCleanupBodiesByDays(bool *aCleanupBodiesByDays)
+{
+ NS_ENSURE_ARG_POINTER(aCleanupBodiesByDays);
+ *aCleanupBodiesByDays = m_cleanupBodiesByDays;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetCleanupBodiesByDays(bool aCleanupBodiesByDays)
+{
+ m_cleanupBodiesByDays = aCleanupBodiesByDays;
+ return NS_OK;
+}
+
+/* attribute long daysToKeepBodies; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetDaysToKeepBodies(uint32_t *aDaysToKeepBodies)
+{
+ NS_ENSURE_ARG_POINTER(aDaysToKeepBodies);
+ *aDaysToKeepBodies = m_daysToKeepBodies;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetDaysToKeepBodies(uint32_t aDaysToKeepBodies)
+{
+ m_daysToKeepBodies = aDaysToKeepBodies;
+ return NS_OK;
+}
+
+/* attribute boolean applyToFlaggedMessages; */
+NS_IMETHODIMP nsMsgRetentionSettings::GetApplyToFlaggedMessages(bool *aApplyToFlaggedMessages)
+{
+ NS_ENSURE_ARG_POINTER(aApplyToFlaggedMessages);
+ *aApplyToFlaggedMessages = m_applyToFlaggedMessages;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgRetentionSettings::SetApplyToFlaggedMessages(bool aApplyToFlaggedMessages)
+{
+ m_applyToFlaggedMessages = aApplyToFlaggedMessages;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgDownloadSettings, nsIMsgDownloadSettings)
+
+nsMsgDownloadSettings::nsMsgDownloadSettings()
+{
+ m_useServerDefaults = false;
+ m_downloadUnreadOnly = false;
+ m_downloadByDate = false;
+ m_ageLimitOfMsgsToDownload = 0;
+}
+
+nsMsgDownloadSettings::~nsMsgDownloadSettings()
+{
+}
+
+/* attribute boolean useServerDefaults; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetUseServerDefaults(bool *aUseServerDefaults)
+{
+ NS_ENSURE_ARG_POINTER(aUseServerDefaults);
+ *aUseServerDefaults = m_useServerDefaults;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgDownloadSettings::SetUseServerDefaults(bool aUseServerDefaults)
+{
+ m_useServerDefaults = aUseServerDefaults;
+ return NS_OK;
+}
+
+
+/* attribute boolean downloadUnreadOnly; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadUnreadOnly(bool *aDownloadUnreadOnly)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadUnreadOnly);
+ *aDownloadUnreadOnly = m_downloadUnreadOnly;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadUnreadOnly(bool aDownloadUnreadOnly)
+{
+ m_downloadUnreadOnly = aDownloadUnreadOnly;
+ return NS_OK;
+}
+
+/* attribute boolean downloadByDate; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetDownloadByDate(bool *aDownloadByDate)
+{
+ NS_ENSURE_ARG_POINTER(aDownloadByDate);
+ *aDownloadByDate = m_downloadByDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDownloadSettings::SetDownloadByDate(bool aDownloadByDate)
+{
+ m_downloadByDate = aDownloadByDate;
+ return NS_OK;
+}
+
+
+/* attribute long ageLimitOfMsgsToDownload; */
+NS_IMETHODIMP nsMsgDownloadSettings::GetAgeLimitOfMsgsToDownload(uint32_t *ageLimitOfMsgsToDownload)
+{
+ NS_ENSURE_ARG_POINTER(ageLimitOfMsgsToDownload);
+ *ageLimitOfMsgsToDownload = m_ageLimitOfMsgsToDownload;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgDownloadSettings::SetAgeLimitOfMsgsToDownload(uint32_t ageLimitOfMsgsToDownload)
+{
+ m_ageLimitOfMsgsToDownload = ageLimitOfMsgsToDownload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultViewFlags);
+ GetIntPref("mailnews.default_view_flags", aDefaultViewFlags);
+ if (*aDefaultViewFlags < nsMsgViewFlagsType::kNone ||
+ *aDefaultViewFlags > (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kShowIgnored |
+ nsMsgViewFlagsType::kUnreadOnly |
+ nsMsgViewFlagsType::kExpandAll |
+ nsMsgViewFlagsType::kGroupBySort))
+ *aDefaultViewFlags = nsMsgViewFlagsType::kNone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortType);
+ GetIntPref("mailnews.default_sort_type", aDefaultSortType);
+ if (*aDefaultSortType < nsMsgViewSortType::byDate ||
+ *aDefaultSortType > nsMsgViewSortType::byAccount)
+ *aDefaultSortType = nsMsgViewSortType::byDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortOrder);
+ GetIntPref("mailnews.default_sort_order", aDefaultSortOrder);
+ if (*aDefaultSortOrder != nsMsgViewSortOrder::descending)
+ *aDefaultSortOrder = nsMsgViewSortOrder::ascending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::ResetHdrCacheSize(uint32_t aSize)
+{
+ if (m_cacheSize > aSize)
+ {
+ m_cacheSize = aSize;
+ ClearHdrCache(false);
+ }
+ return NS_OK;
+}
+
+/**
+ void getNewList(out unsigned long count, [array, size_is(count)] out long newKeys);
+ */
+NS_IMETHODIMP
+nsMsgDatabase::GetNewList(uint32_t *aCount, nsMsgKey **aNewKeys)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aNewKeys);
+
+ *aCount = m_newSet.Length();
+ if (*aCount > 0)
+ {
+ *aNewKeys = static_cast<nsMsgKey *>(moz_xmalloc(*aCount * sizeof(nsMsgKey)));
+ if (!*aNewKeys)
+ return NS_ERROR_OUT_OF_MEMORY;
+ memcpy(*aNewKeys, m_newSet.Elements(), *aCount * sizeof(nsMsgKey));
+ return NS_OK;
+ }
+ // if there were no new messages, signal this by returning a null pointer
+ //
+ *aNewKeys = nullptr;
+ return NS_OK;
+}
+
+nsresult nsMsgDatabase::GetSearchResultsTable(const char *searchFolderUri, bool createIfMissing, nsIMdbTable **table)
+{
+ mdb_kind kindToken;
+ mdb_count numTables;
+ mdb_bool mustBeUnique;
+ NS_ENSURE_TRUE(m_mdbStore, NS_ERROR_NULL_POINTER);
+
+ nsresult err = m_mdbStore->StringToToken(GetEnv(), searchFolderUri, &kindToken);
+ err = m_mdbStore->GetTableKind(GetEnv(), m_hdrRowScopeToken, kindToken,
+ &numTables, &mustBeUnique, table);
+ if ((!*table || NS_FAILED(err)) && createIfMissing)
+ err = m_mdbStore->NewTable(GetEnv(), m_hdrRowScopeToken, kindToken, true, nullptr, table);
+
+ return *table ? err : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgDatabase::GetCachedHits(const char *aSearchFolderUri, nsISimpleEnumerator **aEnumerator)
+{
+ nsCOMPtr <nsIMdbTable> table;
+ (void) GetSearchResultsTable(aSearchFolderUri, false, getter_AddRefs(table));
+ if (!table)
+ return NS_ERROR_FAILURE; // expected result for no cached hits
+ nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, table, nullptr, nullptr);
+ if (e == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aEnumerator = e);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDatabase::RefreshCache(const char *aSearchFolderUri, uint32_t aNumKeys, nsMsgKey *aNewHits, uint32_t *aNumBadHits, nsMsgKey **aStaleHits)
+{
+ nsCOMPtr <nsIMdbTable> table;
+ nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
+ NS_ENSURE_SUCCESS(err, err);
+ // update the table so that it just contains aNewHits.
+ // And, keep track of the headers in the original table but not in aNewHits, so we
+ // can put those in aStaleHits.
+ // both aNewHits and the db table are sorted by uid/key.
+ // So, start at the beginning of the table and the aNewHits array.
+ uint32_t newHitIndex = 0;
+ uint32_t tableRowIndex = 0;
+
+ uint32_t rowCount;
+ table->GetCount(GetEnv(), &rowCount);
+ nsTArray<nsMsgKey> staleHits;
+ // should assert that each array is sorted
+ while (newHitIndex < aNumKeys || tableRowIndex < rowCount)
+ {
+ mdbOid oid;
+ nsMsgKey tableRowKey = nsMsgKey_None;
+ if (tableRowIndex < rowCount)
+ {
+ nsresult ret = table->PosToOid (GetEnv(), tableRowIndex, &oid);
+ if (NS_FAILED(ret))
+ {
+ tableRowIndex++;
+ continue;
+ }
+ tableRowKey = oid.mOid_Id; // ### TODO need the real key for the 0th key problem.
+ }
+
+ if (newHitIndex < aNumKeys && aNewHits[newHitIndex] == tableRowKey)
+ {
+ newHitIndex++;
+ tableRowIndex++;
+ continue;
+ }
+ else if (tableRowIndex >= rowCount || (newHitIndex < aNumKeys && aNewHits[newHitIndex] < tableRowKey))
+ {
+ nsCOMPtr <nsIMdbRow> hdrRow;
+ mdbOid rowObjectId;
+
+ rowObjectId.mOid_Id = aNewHits[newHitIndex];
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(hdrRow));
+ if (hdrRow)
+ {
+ table->AddRow(GetEnv(), hdrRow);
+ mdb_pos newPos;
+ table->MoveRow(GetEnv(), hdrRow, rowCount, tableRowIndex, &newPos);
+ rowCount++;
+ tableRowIndex++;
+ }
+ newHitIndex++;
+ continue;
+ }
+ else if (newHitIndex >= aNumKeys || aNewHits[newHitIndex] > tableRowKey)
+ {
+ staleHits.AppendElement(tableRowKey);
+ table->CutOid(GetEnv(), &oid);
+ rowCount--;
+ continue; // don't increment tableRowIndex since we removed that row.
+ }
+ }
+ *aNumBadHits = staleHits.Length();
+ if (*aNumBadHits)
+ {
+ *aStaleHits = static_cast<nsMsgKey *>(moz_xmalloc(*aNumBadHits * sizeof(nsMsgKey)));
+ if (!*aStaleHits)
+ return NS_ERROR_OUT_OF_MEMORY;
+ memcpy(*aStaleHits, staleHits.Elements(), *aNumBadHits * sizeof(nsMsgKey));
+ }
+ else
+ *aStaleHits = nullptr;
+
+#ifdef DEBUG_David_Bienvenu
+ printf("after refreshing cache\n");
+ // iterate over table and assert that it's in id order
+ table->GetCount(GetEnv(), &rowCount);
+ mdbOid oid;
+ tableRowIndex = 0;
+ mdb_id prevId = 0;
+ while (tableRowIndex < rowCount)
+ {
+ nsresult ret = table->PosToOid (m_mdbEnv, tableRowIndex++, &oid);
+ if (tableRowIndex > 1 && oid.mOid_Id <= prevId)
+ {
+ NS_ASSERTION(false, "inserting row into cached hits table, not sorted correctly");
+ printf("key %lx is before or equal %lx\n", prevId, oid.mOid_Id);
+ }
+ prevId = oid.mOid_Id;
+ }
+
+#endif
+ Commit(nsMsgDBCommitType::kLargeCommit);
+ return NS_OK;
+}
+
+// search sorted table
+mdb_pos nsMsgDatabase::FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert)
+{
+ mdb_pos searchPos = 0;
+ uint32_t rowCount;
+ table->GetCount(GetEnv(), &rowCount);
+ mdb_pos hi = rowCount;
+ mdb_pos lo = 0;
+
+ while (hi > lo)
+ {
+ mdbOid outOid;
+ searchPos = (lo + hi - 1) / 2;
+ table->PosToOid(GetEnv(), searchPos, &outOid);
+ if (outOid.mOid_Id == idToInsert)
+ {
+ NS_ASSERTION(false, "id shouldn't be in table");
+ return hi;
+ }
+ if (outOid.mOid_Id > idToInsert)
+ hi = searchPos;
+ else // if (outOid.mOid_Id < idToInsert)
+ lo = searchPos + 1;
+ }
+ return hi;
+}
+NS_IMETHODIMP
+nsMsgDatabase::UpdateHdrInCache(const char *aSearchFolderUri, nsIMsgDBHdr *aHdr, bool aAdd)
+{
+ nsCOMPtr <nsIMdbTable> table;
+ nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
+ NS_ENSURE_SUCCESS(err, err);
+ nsMsgKey key;
+ aHdr->GetMessageKey(&key);
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(aHdr); // closed system, so this is ok
+ if (NS_SUCCEEDED(err) && m_mdbStore && msgHdr->m_mdbRow)
+ {
+ if (!aAdd)
+ {
+ table->CutRow(m_mdbEnv, msgHdr->m_mdbRow);
+ }
+ else
+ {
+ mdbOid rowId;
+ msgHdr->m_mdbRow->GetOid(m_mdbEnv, &rowId);
+ mdb_pos insertPos = FindInsertIndexInSortedTable(table, rowId.mOid_Id);
+ uint32_t rowCount;
+ table->GetCount(m_mdbEnv, &rowCount);
+ table->AddRow(m_mdbEnv, msgHdr->m_mdbRow);
+ mdb_pos newPos;
+ table->MoveRow(m_mdbEnv, msgHdr->m_mdbRow, rowCount, insertPos, &newPos);
+ }
+ }
+
+// if (aAdd)
+ // if we need to add this hdr, we need to insert it in key order.
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsMsgDatabase::HdrIsInCache(const char* aSearchFolderUri, nsIMsgDBHdr *aHdr, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr <nsIMdbTable> table;
+ nsresult err = GetSearchResultsTable(aSearchFolderUri, true, getter_AddRefs(table));
+ NS_ENSURE_SUCCESS(err, err);
+ nsMsgKey key;
+ aHdr->GetMessageKey(&key);
+ mdbOid rowObjectId;
+ rowObjectId.mOid_Id = key;
+ rowObjectId.mOid_Scope = m_hdrRowScopeToken;
+ mdb_bool hasOid;
+ err = table->HasOid(GetEnv(), &rowObjectId, &hasOid);
+ *aResult = hasOid;
+ return err;
+}
+
diff --git a/mailnews/db/msgdb/src/nsMsgHdr.cpp b/mailnews/db/msgdb/src/nsMsgHdr.cpp
new file mode 100644
index 000000000..ba1663e3b
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgHdr.cpp
@@ -0,0 +1,1098 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsMsgHdr.h"
+#include "nsMsgDatabase.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgThread.h"
+#include "nsMsgMimeCID.h"
+#include "nsIMimeConverter.h"
+#include "mozilla/Attributes.h"
+
+using namespace mozilla::mailnews;
+
+NS_IMPL_ISUPPORTS(nsMsgHdr, nsIMsgDBHdr)
+
+#define FLAGS_INITED 0x1
+#define CACHED_VALUES_INITED 0x2
+#define REFERENCES_INITED 0x4
+#define THREAD_PARENT_INITED 0x8
+
+nsMsgHdr::nsMsgHdr(nsMsgDatabase *db, nsIMdbRow *dbRow)
+{
+ m_mdb = db;
+ Init();
+ m_mdbRow = dbRow;
+ if(m_mdb)
+ {
+ m_mdb->AddRef();
+ mdbOid outOid;
+ if (dbRow && NS_SUCCEEDED(dbRow->GetOid(m_mdb->GetEnv(), &outOid)))
+ {
+ m_messageKey = outOid.mOid_Id;
+ m_mdb->AddHdrToUseCache((nsIMsgDBHdr *) this, m_messageKey);
+ }
+ }
+}
+
+
+void nsMsgHdr::Init()
+{
+ m_initedValues = 0;
+ m_statusOffset = 0xffffffff;
+ m_messageKey = nsMsgKey_None;
+ m_messageSize = 0;
+ m_date = 0;
+ m_flags = 0;
+ m_mdbRow = NULL;
+ m_threadId = nsMsgKey_None;
+ m_threadParent = nsMsgKey_None;
+}
+
+nsresult nsMsgHdr::InitCachedValues()
+{
+ nsresult err = NS_OK;
+
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!(m_initedValues & CACHED_VALUES_INITED))
+ {
+ uint32_t uint32Value;
+ mdbOid outOid;
+ if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid)))
+ m_messageKey = outOid.mOid_Id;
+
+ err = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &m_messageSize);
+
+ err = GetUInt32Column(m_mdb->m_dateColumnToken, &uint32Value);
+ Seconds2PRTime(uint32Value, &m_date);
+
+ err = GetUInt32Column(m_mdb->m_messageThreadIdColumnToken, &m_threadId);
+
+ if (NS_SUCCEEDED(err))
+ m_initedValues |= CACHED_VALUES_INITED;
+ }
+ return err;
+}
+
+nsresult nsMsgHdr::InitFlags()
+{
+
+ nsresult err = NS_OK;
+
+ if (!m_mdb)
+ return NS_ERROR_NULL_POINTER;
+
+ if(!(m_initedValues & FLAGS_INITED))
+ {
+ err = GetUInt32Column(m_mdb->m_flagsColumnToken, &m_flags);
+ m_flags &= ~nsMsgMessageFlags::New; // don't get new flag from MDB
+
+ if(NS_SUCCEEDED(err))
+ m_initedValues |= FLAGS_INITED;
+ }
+
+ return err;
+
+}
+
+nsMsgHdr::~nsMsgHdr()
+{
+ if (m_mdbRow)
+ {
+ if (m_mdb)
+ {
+ NS_RELEASE(m_mdbRow);
+ m_mdb->RemoveHdrFromUseCache((nsIMsgDBHdr *) this, m_messageKey);
+ }
+ }
+ NS_IF_RELEASE(m_mdb);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMessageKey(nsMsgKey *result)
+{
+ if (m_messageKey == nsMsgKey_None && m_mdbRow != NULL)
+ {
+ mdbOid outOid;
+ if (NS_SUCCEEDED(m_mdbRow->GetOid(m_mdb->GetEnv(), &outOid)))
+ m_messageKey = outOid.mOid_Id;
+
+ }
+ *result = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetThreadId(nsMsgKey *result)
+{
+
+ if (!(m_initedValues & CACHED_VALUES_INITED))
+ InitCachedValues();
+
+ if (result)
+ {
+ *result = m_threadId;
+ return NS_OK;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetThreadId(nsMsgKey inKey)
+{
+ m_threadId = inKey;
+ return SetUInt32Column(m_threadId, m_mdb->m_messageThreadIdColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageKey(nsMsgKey value)
+{
+ m_messageKey = value;
+ return NS_OK;
+}
+
+nsresult nsMsgHdr::GetRawFlags(uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetFlags(uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ if (m_mdb)
+ *result = m_mdb->GetStatusFlags(this, m_flags);
+ else
+ *result = m_flags;
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(! (*result & (nsMsgMessageFlags::Elided)), "shouldn't be set in db");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetFlags(uint32_t flags)
+{
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(! (flags & (nsMsgMessageFlags::Elided)), "shouldn't set this flag on db");
+#endif
+ m_initedValues |= FLAGS_INITED;
+ m_flags = flags;
+ // don't write out nsMsgMessageFlags::New to MDB.
+ return SetUInt32Column(m_flags & ~nsMsgMessageFlags::New, m_mdb->m_flagsColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::OrFlags(uint32_t flags, uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ if ((m_flags & flags) != flags)
+ SetFlags (m_flags | flags);
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::AndFlags(uint32_t flags, uint32_t *result)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ if ((m_flags & flags) != m_flags)
+ SetFlags (m_flags & flags);
+ *result = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::MarkHasAttachments(bool bHasAttachments)
+{
+ nsresult rv = NS_OK;
+
+ if(m_mdb)
+ {
+ nsMsgKey key;
+ rv = GetMessageKey(&key);
+ if(NS_SUCCEEDED(rv))
+ rv = m_mdb->MarkHasAttachments(key, bHasAttachments, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgHdr::MarkRead(bool bRead)
+{
+ nsresult rv = NS_OK;
+
+ if(m_mdb)
+ {
+ nsMsgKey key;
+ rv = GetMessageKey(&key);
+ if(NS_SUCCEEDED(rv))
+ rv = m_mdb->MarkRead(key, bRead, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgHdr::MarkFlagged(bool bFlagged)
+{
+ nsresult rv = NS_OK;
+
+ if(m_mdb)
+ {
+ nsMsgKey key;
+ rv = GetMessageKey(&key);
+ if(NS_SUCCEEDED(rv))
+ rv = m_mdb->MarkMarked(key, bFlagged, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetProperty(const char *propertyName, nsAString &resultProperty)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->GetPropertyAsNSString(m_mdbRow, propertyName, resultProperty);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetProperty(const char *propertyName, const nsAString &propertyStr)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->SetPropertyFromNSString(m_mdbRow, propertyName, propertyStr);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetStringProperty(const char *propertyName, const char *propertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->SetProperty(m_mdbRow, propertyName, propertyValue);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetStringProperty(const char *propertyName, char **aPropertyValue)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->GetProperty(m_mdbRow, propertyName, aPropertyValue);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetUint32Property(const char *propertyName, uint32_t *pResult)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->GetUint32Property(m_mdbRow, propertyName, pResult);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetUint32Property(const char *propertyName, uint32_t value)
+{
+ NS_ENSURE_ARG_POINTER(propertyName);
+ if (!m_mdb || !m_mdbRow)
+ return NS_ERROR_NULL_POINTER;
+ return m_mdb->SetUint32Property(m_mdbRow, propertyName, value);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetNumReferences(uint16_t *result)
+{
+ if (!(m_initedValues & REFERENCES_INITED))
+ {
+ const char *references;
+ if (NS_SUCCEEDED(m_mdb->RowCellColumnToConstCharPtr(GetMDBRow(),
+ m_mdb->m_referencesColumnToken, &references)))
+ ParseReferences(references);
+ m_initedValues |= REFERENCES_INITED;
+ }
+
+ if (result)
+ *result = m_references.Length();
+ // there is no real failure here; if there are no references, there are no
+ // references.
+ return NS_OK;
+}
+
+nsresult nsMsgHdr::ParseReferences(const char *references)
+{
+ const char *startNextRef = references;
+ nsAutoCString resultReference;
+ nsCString messageId;
+ GetMessageId(getter_Copies(messageId));
+
+ while (startNextRef && *startNextRef)
+ {
+ startNextRef = GetNextReference(startNextRef, resultReference,
+ startNextRef == references);
+ // Don't add self-references.
+ if (!resultReference.IsEmpty() && !resultReference.Equals(messageId))
+ m_references.AppendElement(resultReference);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetStringReference(int32_t refNum, nsACString& resultReference)
+{
+ nsresult err = NS_OK;
+
+ if(!(m_initedValues & REFERENCES_INITED))
+ GetNumReferences(nullptr); // it can handle the null
+
+ if ((uint32_t)refNum < m_references.Length())
+ resultReference = m_references.ElementAt(refNum);
+ else
+ err = NS_ERROR_ILLEGAL_VALUE;
+ return err;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetDate(PRTime *result)
+{
+ if (!(m_initedValues & CACHED_VALUES_INITED))
+ InitCachedValues();
+
+ *result = m_date;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetDateInSeconds(uint32_t *aResult)
+{
+ return GetUInt32Column(m_mdb->m_dateColumnToken, aResult);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageId(const char *messageId)
+{
+ if (messageId && *messageId == '<')
+ {
+ nsAutoCString tempMessageID(messageId + 1);
+ if (tempMessageID.CharAt(tempMessageID.Length() - 1) == '>')
+ tempMessageID.SetLength(tempMessageID.Length() - 1);
+ return SetStringColumn(tempMessageID.get(), m_mdb->m_messageIdColumnToken);
+ }
+ return SetStringColumn(messageId, m_mdb->m_messageIdColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetSubject(const char *subject)
+{
+ return SetStringColumn(subject, m_mdb->m_subjectColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetAuthor(const char *author)
+{
+ return SetStringColumn(author, m_mdb->m_senderColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetReferences(const char *references)
+{
+ NS_ENSURE_ARG_POINTER(references);
+ m_references.Clear();
+ ParseReferences(references);
+
+ m_initedValues |= REFERENCES_INITED;
+
+ return SetStringColumn(references, m_mdb->m_referencesColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetRecipients(const char *recipients)
+{
+ // need to put in rfc822 address parsing code here (or make caller do it...)
+ return SetStringColumn(recipients, m_mdb->m_recipientsColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetCcList(const char *ccList)
+{
+ return SetStringColumn(ccList, m_mdb->m_ccListColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetBccList(const char *bccList)
+{
+ return SetStringColumn(bccList, m_mdb->m_bccListColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageSize(uint32_t messageSize)
+{
+ SetUInt32Column(messageSize, m_mdb->m_messageSizeColumnToken);
+ m_messageSize = messageSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetOfflineMessageSize(uint32_t *result)
+{
+ uint32_t size;
+ nsresult res = GetUInt32Column(m_mdb->m_offlineMessageSizeColumnToken, &size);
+
+ *result = size;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetOfflineMessageSize(uint32_t messageSize)
+{
+ return SetUInt32Column(messageSize, m_mdb->m_offlineMessageSizeColumnToken);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::SetLineCount(uint32_t lineCount)
+{
+ SetUInt32Column(lineCount, m_mdb->m_numLinesColumnToken);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetStatusOffset(uint32_t statusOffset)
+{
+ return SetUInt32Column(statusOffset, m_mdb->m_statusOffsetColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetDate(PRTime date)
+{
+ m_date = date;
+ uint32_t seconds;
+ PRTime2Seconds(date, &seconds);
+ return SetUInt32Column((uint32_t) seconds, m_mdb->m_dateColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetStatusOffset(uint32_t *result)
+{
+ uint32_t offset = 0;
+ nsresult res = GetUInt32Column(m_mdb->m_statusOffsetColumnToken, &offset);
+
+ *result = offset;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetPriority(nsMsgPriorityValue priority)
+{
+ SetUInt32Column((uint32_t) priority, m_mdb->m_priorityColumnToken);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetPriority(nsMsgPriorityValue *result)
+{
+ if (!result)
+ return NS_ERROR_NULL_POINTER;
+
+ uint32_t priority = 0;
+ nsresult rv = GetUInt32Column(m_mdb->m_priorityColumnToken, &priority);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = (nsMsgPriorityValue) priority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetLabel(nsMsgLabelValue label)
+{
+ SetUInt32Column((uint32_t) label, m_mdb->m_labelColumnToken);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetLabel(nsMsgLabelValue *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ return GetUInt32Column(m_mdb->m_labelColumnToken, result);
+}
+
+// I'd like to not store the account key, if the msg is in
+// the same account as it was received in, to save disk space and memory.
+// This might be problematic when a message gets moved...
+// And I'm not sure if we should short circuit it here,
+// or at a higher level where it might be more efficient.
+NS_IMETHODIMP nsMsgHdr::SetAccountKey(const char *aAccountKey)
+{
+ return SetStringProperty("account", aAccountKey);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetAccountKey(char **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return GetStringProperty("account", aResult);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetMessageOffset(uint64_t *result)
+{
+ NS_ENSURE_ARG(result);
+
+ // if there is a message offset, use it, otherwise, we'll use the message key.
+ (void) GetUInt64Column(m_mdb->m_offlineMsgOffsetColumnToken, result, (unsigned)-1);
+ if (*result == (unsigned)-1)
+ *result = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetMessageOffset(uint64_t offset)
+{
+ SetUInt64Column(offset, m_mdb->m_offlineMsgOffsetColumnToken);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetMessageSize(uint32_t *result)
+{
+ uint32_t size;
+ nsresult res = GetUInt32Column(m_mdb->m_messageSizeColumnToken, &size);
+
+ *result = size;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetLineCount(uint32_t *result)
+{
+ uint32_t linecount;
+ nsresult res = GetUInt32Column(m_mdb->m_numLinesColumnToken, &linecount);
+ *result = linecount;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgHdr::SetPriorityString(const char *priority)
+{
+ nsMsgPriorityValue priorityVal = nsMsgPriority::Default;
+
+ // We can ignore |NS_MsgGetPriorityFromString()| return value,
+ // since we set a default value for |priorityVal|.
+ NS_MsgGetPriorityFromString(priority, priorityVal);
+
+ return SetPriority(priorityVal);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetAuthor(char* *resultAuthor)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetSubject(char* *resultSubject)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetRecipients(char* *resultRecipients)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetCcList(char * *resultCCList)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_ccListColumnToken, resultCCList);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetBccList(char * *resultBCCList)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_bccListColumnToken, resultBCCList);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMessageId(char * *resultMessageId)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_messageIdColumnToken, resultMessageId);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMime2DecodedAuthor(nsAString &resultAuthor)
+{
+ return m_mdb->RowCellColumnToMime2DecodedString(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMime2DecodedSubject(nsAString &resultSubject)
+{
+ return m_mdb->RowCellColumnToMime2DecodedString(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetMime2DecodedRecipients(nsAString &resultRecipients)
+{
+ return m_mdb->RowCellColumnToMime2DecodedString(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients);
+}
+
+
+NS_IMETHODIMP nsMsgHdr::GetAuthorCollationKey(uint32_t *len, uint8_t **resultAuthor)
+{
+ return m_mdb->RowCellColumnToAddressCollationKey(GetMDBRow(), m_mdb->m_senderColumnToken, resultAuthor, len);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetSubjectCollationKey(uint32_t *len, uint8_t **resultSubject)
+{
+ return m_mdb->RowCellColumnToCollationKey(GetMDBRow(), m_mdb->m_subjectColumnToken, resultSubject, len);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetRecipientsCollationKey(uint32_t *len, uint8_t **resultRecipients)
+{
+ return m_mdb->RowCellColumnToCollationKey(GetMDBRow(), m_mdb->m_recipientsColumnToken, resultRecipients, len);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetCharset(char **aCharset)
+{
+ return m_mdb->RowCellColumnToCharPtr(GetMDBRow(), m_mdb->m_messageCharSetColumnToken, aCharset);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetCharset(const char *aCharset)
+{
+ return SetStringColumn(aCharset, m_mdb->m_messageCharSetColumnToken);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetEffectiveCharset(nsACString &resultCharset)
+{
+ return m_mdb->GetEffectiveCharset(m_mdbRow, resultCharset);
+}
+
+NS_IMETHODIMP nsMsgHdr::SetThreadParent(nsMsgKey inKey)
+{
+ m_threadParent = inKey;
+ if (inKey == m_messageKey)
+ NS_ASSERTION(false, "can't be your own parent");
+ SetUInt32Column(m_threadParent, m_mdb->m_threadParentColumnToken);
+ m_initedValues |= THREAD_PARENT_INITED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetThreadParent(nsMsgKey *result)
+{
+ nsresult res;
+ if(!(m_initedValues & THREAD_PARENT_INITED))
+ {
+ res = GetUInt32Column(m_mdb->m_threadParentColumnToken, &m_threadParent, nsMsgKey_None);
+ if (NS_SUCCEEDED(res))
+ m_initedValues |= THREAD_PARENT_INITED;
+ }
+ *result = m_threadParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetFolder(nsIMsgFolder **result)
+{
+ NS_ENSURE_ARG(result);
+
+ if (m_mdb && m_mdb->m_folder)
+ {
+ *result = m_mdb->m_folder;
+ NS_ADDREF(*result);
+ }
+ else
+ *result = nullptr;
+ return NS_OK;
+}
+
+nsresult nsMsgHdr::SetStringColumn(const char *str, mdb_token token)
+{
+ NS_ENSURE_ARG_POINTER(str);
+ return m_mdb->CharPtrToRowCellColumn(m_mdbRow, token, str);
+}
+
+nsresult nsMsgHdr::SetUInt32Column(uint32_t value, mdb_token token)
+{
+ return m_mdb->UInt32ToRowCellColumn(m_mdbRow, token, value);
+}
+
+nsresult nsMsgHdr::GetUInt32Column(mdb_token token, uint32_t *pvalue, uint32_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt32(GetMDBRow(), token, pvalue, defaultValue);
+}
+
+nsresult nsMsgHdr::SetUInt64Column(uint64_t value, mdb_token token)
+{
+ return m_mdb->UInt64ToRowCellColumn(m_mdbRow, token, value);
+}
+
+nsresult nsMsgHdr::GetUInt64Column(mdb_token token, uint64_t *pvalue, uint64_t defaultValue)
+{
+ return m_mdb->RowCellColumnToUInt64(GetMDBRow(), token, pvalue, defaultValue);
+}
+
+/**
+ * Roughly speaking, get the next message-id (starts with a '<' ends with a
+ * '>'). Except, we also try to handle the case where your reference is of
+ * a prehistoric vintage that just stuck any old random junk in there. Our
+ * old logic would (unintentionally?) just trim the whitespace off the front
+ * and hand you everything after that. We change things at all because that
+ * same behaviour does not make sense if we have already seen a proper message
+ * id. We keep the old behaviour at all because it would seem to have
+ * benefits. (See jwz's non-zero stats: http://www.jwz.org/doc/threading.html)
+ * So, to re-state, if there is a valid message-id in there at all, we only
+ * return valid message-id's (sans bracketing '<' and '>'). If there isn't,
+ * our result (via "references") is a left-trimmed copy of the string. If
+ * there is nothing in there, our result is an empty string.) We do require
+ * that you pass allowNonDelimitedReferences what it demands, though.
+ * For example: "<valid@stuff> this stuff is invalid" would net you
+ * "valid@stuff" and "this stuff is invalid" as results. We now only would
+ * provide "valid-stuff" and an empty string (which you should ignore) as
+ * results. However "this stuff is invalid" would return itself, allowing
+ * anything relying on that behaviour to keep working.
+ *
+ * Note: We accept anything inside the '<' and '>'; technically, we should want
+ * at least a '@' in there (per rfc 2822). But since we're going out of our
+ * way to support weird things...
+ *
+ * @param startNextRef The position to start at; this should either be the start
+ * of your references string or our return value from a previous call.
+ * @param reference You pass a nsCString by reference, we put the reference we
+ * find in it, if we find one. It may be empty! Beware!
+ * @param allowNonDelimitedReferences Should we support the
+ * pre-reasonable-standards form of In-Reply-To where it could be any
+ * arbitrary string and our behaviour was just to take off leading
+ * whitespace. It only makes sense to pass true for your first call to this
+ * function, as if you are around to make a second call, it means we found
+ * a properly formatted message-id and so we should only look for more
+ * properly formatted message-ids.
+ * @returns The next starting position of this routine, which may be pointing at
+ * a nul '\0' character to indicate termination.
+ */
+const char *nsMsgHdr::GetNextReference(const char *startNextRef,
+ nsCString &reference,
+ bool acceptNonDelimitedReferences)
+{
+ const char *ptr = startNextRef;
+ const char *whitespaceEndedAt = nullptr;
+ const char *firstMessageIdChar = nullptr;
+
+ // make the reference result string empty by default; we will set it to
+ // something valid if the time comes.
+ reference.Truncate();
+
+ // walk until we find a '<', but keep track of the first point we found that
+ // was not whitespace (as defined by previous versions of this code.)
+ for (bool foundLessThan = false; !foundLessThan; ptr++)
+ {
+ switch (*ptr)
+ {
+ case '\0':
+ // if we are at the end of the string, we found some non-whitespace, and
+ // the caller requested that we accept non-delimited whitespace,
+ // give them that as their reference. (otherwise, leave it empty)
+ if (acceptNonDelimitedReferences && whitespaceEndedAt)
+ reference = whitespaceEndedAt;
+ return ptr;
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ // do nothing, make default case mean you didn't get whitespace
+ break;
+ case '<':
+ firstMessageIdChar = ++ptr; // skip over the '<'
+ foundLessThan = true; // (flag to stop)
+ // intentional fallthrough so whitespaceEndedAt will definitely have
+ // a non-NULL value, just in case the message-id is not valid (no '>')
+ // and the old-school support is desired.
+ MOZ_FALLTHROUGH;
+ default:
+ if (!whitespaceEndedAt)
+ whitespaceEndedAt = ptr;
+ break;
+ }
+ }
+
+ // keep going until we hit a '>' or hit the end of the string
+ for(; *ptr ; ptr++)
+ {
+ if (*ptr == '>')
+ {
+ // it's valid, update reference, making sure to stop before the '>'
+ reference.Assign(firstMessageIdChar, ptr - firstMessageIdChar);
+ // and return a start point just after the '>'
+ return ++ptr;
+ }
+ }
+
+ // we did not have a fully-formed, valid message-id, so consider falling back
+ if (acceptNonDelimitedReferences && whitespaceEndedAt)
+ reference = whitespaceEndedAt;
+ return ptr;
+}
+
+bool nsMsgHdr::IsParentOf(nsIMsgDBHdr *possibleChild)
+{
+ uint16_t referenceToCheck = 0;
+ possibleChild->GetNumReferences(&referenceToCheck);
+ nsAutoCString reference;
+
+ nsCString messageId;
+ GetMessageId(getter_Copies(messageId));
+
+ while (referenceToCheck > 0)
+ {
+ possibleChild->GetStringReference(referenceToCheck - 1, reference);
+
+ if (reference.Equals(messageId))
+ return true;
+ // if reference didn't match, check if this ref is for a non-existent
+ // header. If it is, continue looking at ancestors.
+ nsCOMPtr <nsIMsgDBHdr> refHdr;
+ if (!m_mdb)
+ break;
+ (void) m_mdb->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr));
+ if (refHdr)
+ break;
+ referenceToCheck--;
+ }
+ return false;
+}
+
+bool nsMsgHdr::IsAncestorOf(nsIMsgDBHdr *possibleChild)
+{
+ const char *references;
+ nsMsgHdr* curHdr = static_cast<nsMsgHdr*>(possibleChild); // closed system, cast ok
+ m_mdb->RowCellColumnToConstCharPtr(curHdr->GetMDBRow(), m_mdb->m_referencesColumnToken, &references);
+ if (!references)
+ return false;
+
+ nsCString messageId;
+ // should put < > around message id to make strstr strictly match
+ GetMessageId(getter_Copies(messageId));
+ return (strstr(references, messageId.get()) != nullptr);
+}
+
+NS_IMETHODIMP nsMsgHdr::GetIsRead(bool *isRead)
+{
+ NS_ENSURE_ARG_POINTER(isRead);
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ *isRead = !!(m_flags & nsMsgMessageFlags::Read);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetIsFlagged(bool *isFlagged)
+{
+ NS_ENSURE_ARG_POINTER(isFlagged);
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ *isFlagged = !!(m_flags & nsMsgMessageFlags::Marked);
+ return NS_OK;
+}
+
+void nsMsgHdr::ReparentInThread(nsIMsgThread *thread)
+{
+ NS_WARNING("Borked message header, attempting to fix!");
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ // bail out early for the singleton thread case.
+ if (numChildren == 1)
+ {
+ SetThreadParent(nsMsgKey_None);
+ return;
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBHdr> curHdr;
+ // loop through thread, looking for our proper parent.
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ thread->GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ // closed system, cast ok
+ nsMsgHdr* curMsgHdr = static_cast<nsMsgHdr*>(curHdr.get());
+ if (curHdr && curMsgHdr->IsParentOf(this))
+ {
+ nsMsgKey curHdrKey;
+ curHdr->GetMessageKey(&curHdrKey);
+ SetThreadParent(curHdrKey);
+ return;
+ }
+ }
+ // we didn't find it. So either the root header is our parent,
+ // or we're the root.
+ int32_t rootIndex;
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ thread->GetRootHdr(&rootIndex, getter_AddRefs(rootHdr));
+ NS_ASSERTION(rootHdr, "thread has no root hdr - shouldn't happen");
+ if (rootHdr)
+ {
+ nsMsgKey rootKey;
+ rootHdr->GetMessageKey(&rootKey);
+ // if we're the root, our thread parent is -1.
+ SetThreadParent(rootKey == m_messageKey ? nsMsgKey_None : rootKey);
+ }
+ }
+}
+
+bool nsMsgHdr::IsAncestorKilled(uint32_t ancestorsToCheck)
+{
+ if (!(m_initedValues & FLAGS_INITED))
+ InitFlags();
+ bool isKilled = m_flags & nsMsgMessageFlags::Ignored;
+
+ if (!isKilled)
+ {
+ nsMsgKey threadParent;
+ GetThreadParent(&threadParent);
+
+ if (threadParent == m_messageKey)
+ {
+ // isKilled is false by virtue of the enclosing if statement
+ NS_ERROR("Thread is parent of itself, please fix!");
+ nsCOMPtr<nsIMsgThread> thread;
+ (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
+ if (!thread)
+ return false;
+ ReparentInThread(thread);
+ // Something's wrong, but the problem happened some time ago, so erroring
+ // out now is probably not a good idea. Ergo, we'll pretend to be OK, show
+ // the user the thread (err on the side of caution), and let the assertion
+ // alert debuggers to a problem.
+ return false;
+ }
+ if (threadParent != nsMsgKey_None)
+ {
+ nsCOMPtr<nsIMsgDBHdr> parentHdr;
+ (void) m_mdb->GetMsgHdrForKey(threadParent, getter_AddRefs(parentHdr));
+
+ if (parentHdr)
+ {
+ // More proofing against crashers. This crasher was derived from the
+ // fact that something got borked, leaving is in hand with a circular
+ // reference to borked headers inducing these loops. The defining
+ // characteristic of these headers is that they don't actually seat
+ // themselves in the thread properly.
+ nsCOMPtr<nsIMsgThread> thread;
+ (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
+ if (thread)
+ {
+ nsCOMPtr<nsIMsgDBHdr> claimant;
+ (void) thread->GetChild(threadParent, getter_AddRefs(claimant));
+ if (!claimant)
+ {
+ // attempt to reparent, and say the thread isn't killed,
+ // erring on the side of safety.
+ ReparentInThread(thread);
+ return false;
+ }
+ }
+
+ if (!ancestorsToCheck)
+ {
+ // We think we have a parent, but we have no more ancestors to check
+ NS_ASSERTION(false, "cycle in parent relationship, please fix!");
+ return false;
+ }
+ // closed system, cast ok
+ nsMsgHdr* parent = static_cast<nsMsgHdr*>(parentHdr.get());
+ return parent->IsAncestorKilled(ancestorsToCheck - 1);
+ }
+ }
+ }
+ return isKilled;
+}
+
+NS_IMETHODIMP nsMsgHdr::GetIsKilled(bool *isKilled)
+{
+ NS_ENSURE_ARG_POINTER(isKilled);
+ *isKilled = false;
+ nsCOMPtr<nsIMsgThread> thread;
+ (void) m_mdb->GetThreadContainingMsgHdr(this, getter_AddRefs(thread));
+ // if we can't find the thread, let's at least check one level; maybe
+ // the header hasn't been added to a thread yet.
+ uint32_t numChildren = 1;
+ if (thread)
+ thread->GetNumChildren(&numChildren);
+ if (!numChildren)
+ return NS_ERROR_FAILURE;
+ // We can't have as many ancestors as there are messages in the thread,
+ // so tell IsAncestorKilled to only check numChildren - 1 ancestors.
+ *isKilled = IsAncestorKilled(numChildren - 1);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#include "nsIStringEnumerator.h"
+#include "nsAutoPtr.h"
+#define NULL_MORK_COLUMN 0
+class nsMsgPropertyEnumerator : public nsIUTF8StringEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ nsMsgPropertyEnumerator(nsMsgHdr* aHdr);
+ void PrefetchNext();
+
+protected:
+ virtual ~nsMsgPropertyEnumerator();
+ nsCOMPtr<nsIMdbRowCellCursor> mRowCellCursor;
+ nsCOMPtr<nsIMdbEnv> m_mdbEnv;
+ nsCOMPtr<nsIMdbStore> m_mdbStore;
+ // Hold a reference to the hdr so it will hold an xpcom reference to the
+ // underlying mdb row. The row cell cursor will crash if the underlying
+ // row goes away.
+ RefPtr<nsMsgHdr> m_hdr;
+ bool mNextPrefetched;
+ mdb_column mNextColumn;
+};
+
+nsMsgPropertyEnumerator::nsMsgPropertyEnumerator(nsMsgHdr* aHdr)
+ : mNextPrefetched(false),
+ mNextColumn(NULL_MORK_COLUMN)
+{
+ RefPtr<nsMsgDatabase> mdb;
+ nsCOMPtr<nsIMdbRow> mdbRow;
+
+ if (aHdr &&
+ (mdbRow = aHdr->GetMDBRow()) &&
+ (m_hdr = aHdr) &&
+ (mdb = aHdr->m_mdb) &&
+ (m_mdbEnv = mdb->m_mdbEnv) &&
+ (m_mdbStore = mdb->m_mdbStore))
+ {
+ mdbRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(mRowCellCursor));
+ }
+}
+
+nsMsgPropertyEnumerator::~nsMsgPropertyEnumerator()
+{
+ // Need to clear this before the nsMsgHdr and its corresponding
+ // nsIMdbRow potentially go away.
+ mRowCellCursor = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgPropertyEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP nsMsgPropertyEnumerator::GetNext(nsACString& aItem)
+{
+ PrefetchNext();
+ if (mNextColumn == NULL_MORK_COLUMN)
+ return NS_ERROR_FAILURE; // call HasMore first
+ if (!m_mdbStore || !m_mdbEnv)
+ return NS_ERROR_NOT_INITIALIZED;
+ mNextPrefetched = false;
+ char columnName[100];
+ struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
+ // Get the column of the cell
+ nsresult rv = m_mdbStore->TokenToString(m_mdbEnv, mNextColumn, &colYarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aItem.Assign(static_cast<char *>(colYarn.mYarn_Buf), colYarn.mYarn_Fill);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPropertyEnumerator::HasMore(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ PrefetchNext();
+ *aResult = (mNextColumn != NULL_MORK_COLUMN);
+ return NS_OK;
+}
+
+void nsMsgPropertyEnumerator::PrefetchNext(void)
+{
+ if (!mNextPrefetched && m_mdbEnv && mRowCellCursor)
+ {
+ mNextPrefetched = true;
+ nsCOMPtr<nsIMdbCell> cell;
+ mRowCellCursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &mNextColumn, nullptr);
+ if (mNextColumn == NULL_MORK_COLUMN)
+ {
+ // free up references
+ m_mdbStore = nullptr;
+ m_mdbEnv = nullptr;
+ mRowCellCursor = nullptr;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgHdr::GetPropertyEnumerator(nsIUTF8StringEnumerator** _result)
+{
+ nsMsgPropertyEnumerator* enumerator = new nsMsgPropertyEnumerator(this);
+ if (!enumerator)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*_result = enumerator);
+ return NS_OK;
+}
diff --git a/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp
new file mode 100644
index 000000000..2ce35047a
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.cpp
@@ -0,0 +1,378 @@
+/* -*- 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 "nsMsgOfflineImapOperation.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+PRLogModuleInfo *IMAPOffline;
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsMsgOfflineImapOperation, nsIMsgOfflineImapOperation)
+
+// property names for offine imap operation fields.
+#define PROP_OPERATION "op"
+#define PROP_OPERATION_FLAGS "opFlags"
+#define PROP_NEW_FLAGS "newFlags"
+#define PROP_MESSAGE_KEY "msgKey"
+#define PROP_SRC_MESSAGE_KEY "srcMsgKey"
+#define PROP_SRC_FOLDER_URI "srcFolderURI"
+#define PROP_MOVE_DEST_FOLDER_URI "moveDest"
+#define PROP_NUM_COPY_DESTS "numCopyDests"
+#define PROP_COPY_DESTS "copyDests" // how to delimit these? Or should we do the "dest1","dest2" etc trick? But then we'd need to shuffle
+ // them around since we delete off the front first.
+#define PROP_KEYWORD_ADD "addedKeywords"
+#define PROP_KEYWORD_REMOVE "removedKeywords"
+#define PROP_MSG_SIZE "msgSize"
+#define PROP_PLAYINGBACK "inPlayback"
+
+nsMsgOfflineImapOperation::nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row)
+{
+ NS_ASSERTION(db, "can't have null db");
+ NS_ASSERTION(row, "can't have null row");
+ m_operation = 0;
+ m_operationFlags = 0;
+ m_messageKey = nsMsgKey_None;
+ m_sourceMessageKey = nsMsgKey_None;
+ m_mdb = db;
+ NS_ADDREF(m_mdb);
+ m_mdbRow = row;
+ m_newFlags = 0;
+ m_mdb->GetUint32Property(m_mdbRow, PROP_OPERATION, (uint32_t *) &m_operation, 0);
+ m_mdb->GetUint32Property(m_mdbRow, PROP_MESSAGE_KEY, &m_messageKey, 0);
+ m_mdb->GetUint32Property(m_mdbRow, PROP_OPERATION_FLAGS, &m_operationFlags, 0);
+ m_mdb->GetUint32Property(m_mdbRow, PROP_NEW_FLAGS, (uint32_t *) &m_newFlags, 0);
+}
+
+nsMsgOfflineImapOperation::~nsMsgOfflineImapOperation()
+{
+ // clear the row first, in case we're holding the last reference
+ // to the db.
+ m_mdbRow = nullptr;
+ NS_IF_RELEASE(m_mdb);
+}
+
+/* attribute nsOfflineImapOperationType operation; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetOperation(nsOfflineImapOperationType *aOperation)
+{
+ NS_ENSURE_ARG(aOperation);
+ *aOperation = m_operation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetOperation(nsOfflineImapOperationType aOperation)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x setOperation was %x add %x", m_messageKey, m_operation, aOperation));
+
+ m_operation |= aOperation;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_OPERATION, m_operation);
+}
+
+/* void clearOperation (in nsOfflineImapOperationType operation); */
+NS_IMETHODIMP nsMsgOfflineImapOperation::ClearOperation(nsOfflineImapOperationType aOperation)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x clearOperation was %x clear %x", m_messageKey, m_operation, aOperation));
+ m_operation &= ~aOperation;
+ switch (aOperation)
+ {
+ case kMsgMoved:
+ case kAppendTemplate:
+ case kAppendDraft:
+ m_moveDestination.Truncate();
+ break;
+ case kMsgCopy:
+ m_copyDestinations.RemoveElementAt(0);
+ break;
+ }
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_OPERATION, m_operation);
+}
+
+/* attribute nsMsgKey messageKey; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetMessageKey(nsMsgKey *aMessageKey)
+{
+ NS_ENSURE_ARG(aMessageKey);
+ *aMessageKey = m_messageKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetMessageKey(nsMsgKey aMessageKey)
+{
+ m_messageKey = aMessageKey;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_MESSAGE_KEY, m_messageKey);
+}
+
+/* attribute nsMsgKey srcMessageKey; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetSrcMessageKey(nsMsgKey *aMessageKey)
+{
+ NS_ENSURE_ARG(aMessageKey);
+ return m_mdb->GetUint32Property(m_mdbRow, PROP_SRC_MESSAGE_KEY, aMessageKey, nsMsgKey_None);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetSrcMessageKey(nsMsgKey aMessageKey)
+{
+ m_messageKey = aMessageKey;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_SRC_MESSAGE_KEY, m_messageKey);
+}
+
+/* attribute imapMessageFlagsType flagOperation; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetFlagOperation(imapMessageFlagsType *aFlagOperation)
+{
+ NS_ENSURE_ARG(aFlagOperation);
+ *aFlagOperation = m_operationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetFlagOperation(imapMessageFlagsType aFlagOperation)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x setFlagOperation was %x add %x", m_messageKey, m_operationFlags, aFlagOperation));
+ SetOperation(kFlagsChanged);
+ nsresult rv = SetNewFlags(aFlagOperation);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_operationFlags |= aFlagOperation;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_OPERATION_FLAGS, m_operationFlags);
+}
+
+/* attribute imapMessageFlagsType flagOperation; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetNewFlags(imapMessageFlagsType *aNewFlags)
+{
+ NS_ENSURE_ARG(aNewFlags);
+ uint32_t flags;
+ nsresult rv = m_mdb->GetUint32Property(m_mdbRow, PROP_NEW_FLAGS, &flags, 0);
+ *aNewFlags = m_newFlags = (imapMessageFlagsType) flags;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetNewFlags(imapMessageFlagsType aNewFlags)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info) && m_newFlags != aNewFlags)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x SetNewFlags was %x to %x", m_messageKey, m_newFlags, aNewFlags));
+ m_newFlags = aNewFlags;
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_NEW_FLAGS, m_newFlags);
+}
+
+
+/* attribute string destinationFolderURI; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetDestinationFolderURI(char * *aDestinationFolderURI)
+{
+ NS_ENSURE_ARG(aDestinationFolderURI);
+ (void) m_mdb->GetProperty(m_mdbRow, PROP_MOVE_DEST_FOLDER_URI, getter_Copies(m_moveDestination));
+ *aDestinationFolderURI = ToNewCString(m_moveDestination);
+ return (*aDestinationFolderURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetDestinationFolderURI(const char * aDestinationFolderURI)
+{
+ if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x SetDestinationFolderURI to %s", m_messageKey, aDestinationFolderURI));
+ m_moveDestination = aDestinationFolderURI ? aDestinationFolderURI : 0;
+ return m_mdb->SetProperty(m_mdbRow, PROP_MOVE_DEST_FOLDER_URI, aDestinationFolderURI);
+}
+
+/* attribute string sourceFolderURI; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetSourceFolderURI(char * *aSourceFolderURI)
+{
+ NS_ENSURE_ARG(aSourceFolderURI);
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_SRC_FOLDER_URI, getter_Copies(m_sourceFolder));
+ *aSourceFolderURI = ToNewCString(m_sourceFolder);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetSourceFolderURI(const char * aSourceFolderURI)
+{
+ m_sourceFolder = aSourceFolderURI ? aSourceFolderURI : 0;
+ SetOperation(kMoveResult);
+
+ return m_mdb->SetProperty(m_mdbRow, PROP_SRC_FOLDER_URI, aSourceFolderURI);
+}
+
+/* attribute string keyword; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetKeywordsToAdd(char * *aKeywords)
+{
+ NS_ENSURE_ARG(aKeywords);
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_KEYWORD_ADD, getter_Copies(m_keywordsToAdd));
+ *aKeywords = ToNewCString(m_keywordsToAdd);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::AddKeywordToAdd(const char * aKeyword)
+{
+ SetOperation(kAddKeywords);
+ return AddKeyword(aKeyword, m_keywordsToAdd, PROP_KEYWORD_ADD, m_keywordsToRemove, PROP_KEYWORD_REMOVE);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetKeywordsToRemove(char * *aKeywords)
+{
+ NS_ENSURE_ARG(aKeywords);
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_KEYWORD_REMOVE, getter_Copies(m_keywordsToRemove));
+ *aKeywords = ToNewCString(m_keywordsToRemove);
+ return rv;
+}
+
+nsresult nsMsgOfflineImapOperation::AddKeyword(const char *aKeyword, nsCString &addList, const char *addProp,
+ nsCString &removeList, const char *removeProp)
+{
+ int32_t startOffset, keywordLength;
+ if (!MsgFindKeyword(nsDependentCString(aKeyword), addList, &startOffset, &keywordLength))
+ {
+ if (!addList.IsEmpty())
+ addList.Append(' ');
+ addList.Append(aKeyword);
+ }
+ // if the keyword we're removing was in the list of keywords to add,
+ // cut it from that list.
+ if (MsgFindKeyword(nsDependentCString(aKeyword), removeList, &startOffset, &keywordLength))
+ {
+ removeList.Cut(startOffset, keywordLength);
+ m_mdb->SetProperty(m_mdbRow, removeProp, removeList.get());
+ }
+ return m_mdb->SetProperty(m_mdbRow, addProp, addList.get());
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::AddKeywordToRemove(const char * aKeyword)
+{
+ SetOperation(kRemoveKeywords);
+ return AddKeyword(aKeyword, m_keywordsToRemove, PROP_KEYWORD_REMOVE, m_keywordsToAdd, PROP_KEYWORD_ADD);
+}
+
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::AddMessageCopyOperation(const char *destinationBox)
+{
+ SetOperation(kMsgCopy);
+ nsAutoCString newDest(destinationBox);
+ nsresult rv = GetCopiesFromDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_copyDestinations.AppendElement(newDest);
+ return SetCopiesToDB();
+}
+
+// we write out the folders as one string, separated by 0x1.
+#define FOLDER_SEP_CHAR '\001'
+
+nsresult nsMsgOfflineImapOperation::GetCopiesFromDB()
+{
+ nsCString copyDests;
+ m_copyDestinations.Clear();
+ nsresult rv = m_mdb->GetProperty(m_mdbRow, PROP_COPY_DESTS, getter_Copies(copyDests));
+ // use 0x1 as the delimiter between folder names since it's not a legal character
+ if (NS_SUCCEEDED(rv) && !copyDests.IsEmpty())
+ {
+ int32_t curCopyDestStart = 0;
+ int32_t nextCopyDestPos = 0;
+
+ while (nextCopyDestPos != -1)
+ {
+ nsCString curDest;
+ nextCopyDestPos = copyDests.FindChar(FOLDER_SEP_CHAR, curCopyDestStart);
+ if (nextCopyDestPos > 0)
+ curDest = Substring(copyDests, curCopyDestStart, nextCopyDestPos - curCopyDestStart);
+ else
+ curDest = Substring(copyDests, curCopyDestStart, copyDests.Length() - curCopyDestStart);
+ curCopyDestStart = nextCopyDestPos + 1;
+ m_copyDestinations.AppendElement(curDest);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgOfflineImapOperation::SetCopiesToDB()
+{
+ nsAutoCString copyDests;
+
+ // use 0x1 as the delimiter between folders
+ for (uint32_t i = 0; i < m_copyDestinations.Length(); i++)
+ {
+ if (i > 0)
+ copyDests.Append(FOLDER_SEP_CHAR);
+ copyDests.Append(m_copyDestinations.ElementAt(i));
+ }
+ return m_mdb->SetProperty(m_mdbRow, PROP_COPY_DESTS, copyDests.get());
+}
+
+/* attribute long numberOfCopies; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetNumberOfCopies(int32_t *aNumberOfCopies)
+{
+ NS_ENSURE_ARG(aNumberOfCopies);
+ nsresult rv = GetCopiesFromDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aNumberOfCopies = m_copyDestinations.Length();
+ return NS_OK;
+}
+
+/* string getCopyDestination (in long copyIndex); */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetCopyDestination(int32_t copyIndex, char **retval)
+{
+ NS_ENSURE_ARG(retval);
+ nsresult rv = GetCopiesFromDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (copyIndex >= (int32_t)m_copyDestinations.Length())
+ return NS_ERROR_ILLEGAL_VALUE;
+ *retval = ToNewCString(m_copyDestinations.ElementAt(copyIndex));
+ return (*retval) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+/* attribute unsigned log msgSize; */
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetMsgSize(uint32_t *aMsgSize)
+{
+ NS_ENSURE_ARG(aMsgSize);
+ return m_mdb->GetUint32Property(m_mdbRow, PROP_MSG_SIZE, aMsgSize, 0);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetMsgSize(uint32_t aMsgSize)
+{
+ return m_mdb->SetUint32Property(m_mdbRow, PROP_MSG_SIZE, aMsgSize);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::SetPlayingBack(bool aPlayingBack)
+{
+ return m_mdb->SetBooleanProperty(m_mdbRow, PROP_PLAYINGBACK, aPlayingBack);
+}
+
+NS_IMETHODIMP nsMsgOfflineImapOperation::GetPlayingBack(bool *aPlayingBack)
+{
+ NS_ENSURE_ARG(aPlayingBack);
+ return m_mdb->GetBooleanProperty(m_mdbRow, PROP_PLAYINGBACK, aPlayingBack);
+}
+
+
+void nsMsgOfflineImapOperation::Log(PRLogModuleInfo *logFile)
+{
+ if (!IMAPOffline)
+ IMAPOffline = PR_NewLogModule("IMAPOFFLINE");
+ if (!MOZ_LOG_TEST(IMAPOffline, LogLevel::Info))
+ return;
+ // const long kMoveResult = 0x8;
+ // const long kAppendDraft = 0x10;
+ // const long kAddedHeader = 0x20;
+ // const long kDeletedMsg = 0x40;
+ // const long kMsgMarkedDeleted = 0x80;
+ // const long kAppendTemplate = 0x100;
+ // const long kDeleteAllMsgs = 0x200;
+ if (m_operation & nsIMsgOfflineImapOperation::kFlagsChanged)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x changeFlag:%x", m_messageKey, m_newFlags));
+ if (m_operation & nsIMsgOfflineImapOperation::kMsgMoved)
+ {
+ nsCString moveDestFolder;
+ GetDestinationFolderURI(getter_Copies(moveDestFolder));
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x moveTo:%s", m_messageKey, moveDestFolder.get()));
+ }
+ if (m_operation & nsIMsgOfflineImapOperation::kMsgCopy)
+ {
+ nsCString copyDests;
+ m_mdb->GetProperty(m_mdbRow, PROP_COPY_DESTS, getter_Copies(copyDests));
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x moveTo:%s", m_messageKey, copyDests.get()));
+ }
+ if (m_operation & nsIMsgOfflineImapOperation::kAppendDraft)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x append draft", m_messageKey));
+ if (m_operation & nsIMsgOfflineImapOperation::kAddKeywords)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x add keyword:%s", m_messageKey, m_keywordsToAdd.get()));
+ if (m_operation & nsIMsgOfflineImapOperation::kRemoveKeywords)
+ MOZ_LOG(IMAPOffline, LogLevel::Info, ("msg id %x remove keyword:%s", m_messageKey, m_keywordsToRemove.get()));
+}
diff --git a/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h
new file mode 100644
index 000000000..e892a253a
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgOfflineImapOperation.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+#ifndef _nsMsgOfflineImapOperation_H_
+
+#include "nsIMsgOfflineImapOperation.h"
+#include "mdb.h"
+#include "nsMsgDatabase.h"
+#include "prlog.h"
+
+class nsMsgOfflineImapOperation : public nsIMsgOfflineImapOperation
+{
+public:
+ /** Instance Methods **/
+ nsMsgOfflineImapOperation(nsMsgDatabase *db, nsIMdbRow *row);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGOFFLINEIMAPOPERATION
+
+
+ nsIMdbRow *GetMDBRow() {return m_mdbRow;}
+ nsresult GetCopiesFromDB();
+ nsresult SetCopiesToDB();
+ void Log(PRLogModuleInfo *logFile);
+protected:
+ virtual ~nsMsgOfflineImapOperation();
+ nsresult AddKeyword(const char *aKeyword, nsCString &addList, const char *addProp,
+ nsCString &removeList, const char *removeProp);
+
+ nsOfflineImapOperationType m_operation;
+ nsMsgKey m_messageKey;
+ nsMsgKey m_sourceMessageKey;
+ uint32_t m_operationFlags; // what to do on sync
+ imapMessageFlagsType m_newFlags; // used for kFlagsChanged
+
+ // these are URI's, and are escaped. Thus, we can use a delimter like ' '
+ // because the real spaces should be escaped.
+ nsCString m_sourceFolder;
+ nsCString m_moveDestination;
+ nsTArray<nsCString> m_copyDestinations;
+
+ nsCString m_keywordsToAdd;
+ nsCString m_keywordsToRemove;
+
+ // nsMsgOfflineImapOperation will have to know what db and row they belong to, since they are really
+ // just a wrapper around the offline operation row in the mdb.
+ // though I hope not.
+ nsMsgDatabase *m_mdb;
+ nsCOMPtr <nsIMdbRow> m_mdbRow;
+};
+
+
+
+#endif /* _nsMsgOfflineImapOperation_H_ */
+
diff --git a/mailnews/db/msgdb/src/nsMsgThread.cpp b/mailnews/db/msgdb/src/nsMsgThread.cpp
new file mode 100644
index 000000000..0d8ad3d85
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsMsgThread.cpp
@@ -0,0 +1,1180 @@
+/* -*- 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 "nsMsgDatabase.h"
+#include "nsCOMPtr.h"
+#include "nsMsgThread.h"
+#include "MailNewsTypes2.h"
+#include "mozilla/DebugOnly.h"
+
+NS_IMPL_ISUPPORTS(nsMsgThread, nsIMsgThread)
+
+nsMsgThread::nsMsgThread()
+{
+ MOZ_COUNT_CTOR(nsMsgThread);
+ Init();
+}
+nsMsgThread::nsMsgThread(nsMsgDatabase *db, nsIMdbTable *table)
+{
+ MOZ_COUNT_CTOR(nsMsgThread);
+ Init();
+ m_mdbTable = table;
+ m_mdbDB = db;
+ if (db)
+ db->m_threads.AppendElement(this);
+ else
+ NS_ERROR("no db for thread");
+#ifdef DEBUG_David_Bienvenu
+ if (m_mdbDB->m_threads.Length() > 5)
+ printf("more than five outstanding threads\n");
+#endif
+ if (table && db)
+ {
+ table->GetMetaRow(db->GetEnv(), nullptr, nullptr, getter_AddRefs(m_metaRow));
+ InitCachedValues();
+ }
+}
+
+void nsMsgThread::Init()
+{
+ m_threadKey = nsMsgKey_None;
+ m_threadRootKey = nsMsgKey_None;
+ m_numChildren = 0;
+ m_numUnreadChildren = 0;
+ m_flags = 0;
+ m_newestMsgDate = 0;
+ m_cachedValuesInitialized = false;
+}
+
+nsMsgThread::~nsMsgThread()
+{
+ MOZ_COUNT_DTOR(nsMsgThread);
+ if (m_mdbDB)
+ {
+ mozilla::DebugOnly<bool> found = m_mdbDB->m_threads.RemoveElement(this);
+ NS_ASSERTION(found, "removing thread not in threads array");
+ }
+ else // This can happen if db is forced closed
+ NS_WARNING("null db in thread");
+ Clear();
+}
+
+void nsMsgThread::Clear()
+{
+ m_mdbTable = nullptr;
+ m_metaRow = nullptr;
+ m_mdbDB = nullptr;
+}
+
+nsresult nsMsgThread::InitCachedValues()
+{
+ nsresult err = NS_OK;
+
+ NS_ENSURE_TRUE(m_mdbDB && m_metaRow, NS_ERROR_INVALID_POINTER);
+
+ if (!m_cachedValuesInitialized)
+ {
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadFlagsColumnToken, &m_flags);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, &m_numChildren);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadIdColumnToken, &m_threadKey, nsMsgKey_None);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, &m_numUnreadChildren);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadRootKeyColumnToken, &m_threadRootKey, nsMsgKey_None);
+ err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadNewestMsgDateColumnToken, &m_newestMsgDate, 0);
+ // fix num children if it's wrong. this doesn't work - some DB's have a bogus thread table
+ // that is full of bogus headers - don't know why.
+ uint32_t rowCount = 0;
+ m_mdbTable->GetCount(m_mdbDB->GetEnv(), &rowCount);
+ // NS_ASSERTION(m_numChildren <= rowCount, "num children wrong - fixing");
+ if (m_numChildren > rowCount)
+ ChangeChildCount((int32_t) rowCount - (int32_t) m_numChildren);
+ if ((int32_t) m_numUnreadChildren < 0)
+ ChangeUnreadChildCount(- (int32_t) m_numUnreadChildren);
+ if (NS_SUCCEEDED(err))
+ m_cachedValuesInitialized = true;
+ }
+ return err;
+}
+
+NS_IMETHODIMP nsMsgThread::SetThreadKey(nsMsgKey threadKey)
+{
+ NS_ASSERTION(m_threadKey == nsMsgKey_None || m_threadKey == threadKey,
+ "shouldn't be changing thread key");
+ m_threadKey = threadKey;
+ // by definition, the initial thread key is also the thread root key.
+ SetThreadRootKey(threadKey);
+ // gotta set column in meta row here.
+ return m_mdbDB->UInt32ToRowCellColumn(
+ m_metaRow, m_mdbDB->m_threadIdColumnToken, threadKey);
+}
+
+NS_IMETHODIMP nsMsgThread::GetThreadKey(nsMsgKey *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult res = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadIdColumnToken, &m_threadKey);
+ *result = m_threadKey;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgThread::GetFlags(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsresult res = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadFlagsColumnToken, &m_flags);
+ *result = m_flags;
+ return res;
+}
+
+NS_IMETHODIMP nsMsgThread::SetFlags(uint32_t flags)
+{
+ m_flags = flags;
+ return m_mdbDB->UInt32ToRowCellColumn(
+ m_metaRow, m_mdbDB->m_threadFlagsColumnToken, m_flags);
+}
+
+NS_IMETHODIMP nsMsgThread::SetSubject(const nsACString& aSubject)
+{
+ return m_mdbDB->CharPtrToRowCellColumn(m_metaRow, m_mdbDB->m_threadSubjectColumnToken, nsCString(aSubject).get());
+}
+
+NS_IMETHODIMP nsMsgThread::GetSubject(nsACString& aSubject)
+{
+ nsCString subjectStr;
+ nsresult rv = m_mdbDB->RowCellColumnToCharPtr(m_metaRow, m_mdbDB->m_threadSubjectColumnToken,
+ getter_Copies(subjectStr));
+
+ aSubject.Assign(subjectStr);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetNumChildren(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_numChildren;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgThread::GetNumUnreadChildren (uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_numUnreadChildren;
+ return NS_OK;
+}
+
+nsresult nsMsgThread::RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+ mdb_pos outPos;
+ nsMsgKey newHdrAncestor;
+ nsCOMPtr <nsIMsgDBHdr> ancestorHdr = newParentOfOldRoot;
+ nsMsgKey newRoot;
+
+ ancestorHdr->GetMessageKey(&newRoot);
+ // loop trying to find the oldest ancestor of this msg
+ // that is a parent of the root. The oldest ancestor will
+ // become the root of the thread.
+ do
+ {
+ ancestorHdr->GetThreadParent(&newHdrAncestor);
+ if (newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey && newHdrAncestor != newRoot)
+ {
+ newRoot = newHdrAncestor;
+ rv = m_mdbDB->GetMsgHdrForKey(newRoot, getter_AddRefs(ancestorHdr));
+ }
+ }
+ while (NS_SUCCEEDED(rv) && ancestorHdr && newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey
+ && newHdrAncestor != newRoot);
+ SetThreadRootKey(newRoot);
+ ReparentNonReferenceChildrenOf(oldRoot, newRoot, announcer);
+ if (ancestorHdr)
+ {
+ nsIMsgDBHdr *msgHdr = ancestorHdr;
+ nsMsgHdr* rootMsgHdr = static_cast<nsMsgHdr*>(msgHdr); // closed system, cast ok
+ nsIMdbRow *newRootHdrRow = rootMsgHdr->GetMDBRow();
+ // move the root hdr to pos 0.
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), newRootHdrRow, -1, 0, &outPos);
+ ancestorHdr->SetThreadParent(nsMsgKey_None);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, bool threadInThread,
+ nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+ nsMsgHdr* hdr = static_cast<nsMsgHdr*>(child); // closed system, cast ok
+ uint32_t newHdrFlags = 0;
+ uint32_t msgDate;
+ nsMsgKey newHdrKey = 0;
+ bool parentKeyNeedsSetting = true;
+
+ nsIMdbRow *hdrRow = hdr->GetMDBRow();
+ NS_ENSURE_STATE(hdrRow);
+ hdr->GetRawFlags(&newHdrFlags);
+ hdr->GetMessageKey(&newHdrKey);
+ hdr->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ SetNewestMsgDate(msgDate);
+
+ if (newHdrFlags & nsMsgMessageFlags::Watched)
+ SetFlags(m_flags | nsMsgMessageFlags::Watched);
+
+ child->AndFlags(~(nsMsgMessageFlags::Watched), &newHdrFlags);
+
+ // These are threading flags that the child may have set before being added
+ // to the database.
+ uint32_t protoThreadFlags;
+ child->GetUint32Property("ProtoThreadFlags", &protoThreadFlags);
+ SetFlags(m_flags | protoThreadFlags);
+ // Clear the flag so that it doesn't fudge anywhere else
+ child->SetUint32Property("ProtoThreadFlags", 0);
+
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ // get the num children before we add the new header.
+ GetNumChildren(&numChildren);
+
+ // if this is an empty thread, set the root key to this header's key
+ if (numChildren == 0)
+ SetThreadRootKey(newHdrKey);
+
+ if (m_mdbTable)
+ {
+ m_mdbTable->AddRow(m_mdbDB->GetEnv(), hdrRow);
+ ChangeChildCount(1);
+ if (! (newHdrFlags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(1);
+ }
+ if (inReplyTo)
+ {
+ nsMsgKey parentKey;
+ inReplyTo->GetMessageKey(&parentKey);
+ child->SetThreadParent(parentKey);
+ parentKeyNeedsSetting = false;
+ }
+
+ // check if this header is a parent of one of the messages in this thread
+ bool hdrMoved = false;
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ uint32_t moveIndex = 0;
+
+ PRTime newHdrDate;
+ child->GetDate(&newHdrDate);
+
+ // This is an ugly but simple fix for a difficult problem. Basically, when we add
+ // a message to a thread, we have to run through the thread to see if the new
+ // message is a parent of an existing message in the thread, and adjust things
+ // accordingly. If you thread by subject, and you have a large folder with
+ // messages w/ all the same subject, this code can take a really long time. So the
+ // pragmatic thing is to say that for threads with more than 1000 messages, it's
+ // simply not worth dealing with the case where the parent comes in after the
+ // child. Threads with more than 1000 messages are pretty unwieldy anyway.
+ // See Bug 90452
+
+ if (numChildren < 1000)
+ {
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsMsgKey msgKey = nsMsgKey_None;
+
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ if (hdr->IsParentOf(curHdr))
+ {
+ nsMsgKey oldThreadParent;
+ mdb_pos outPos;
+ // move this hdr before the current header.
+ if (!hdrMoved)
+ {
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, childIndex, &outPos);
+ hdrMoved = true;
+ curHdr->GetThreadParent(&oldThreadParent);
+ curHdr->GetMessageKey(&msgKey);
+ nsCOMPtr <nsIMsgDBHdr> curParent;
+ m_mdbDB->GetMsgHdrForKey(oldThreadParent, getter_AddRefs(curParent));
+ if (curParent && hdr->IsAncestorOf(curParent))
+ {
+ nsMsgKey curParentKey;
+ curParent->GetMessageKey(&curParentKey);
+ if (curParentKey == m_threadRootKey)
+ {
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos);
+ RerootThread(child, curParent, announcer);
+ parentKeyNeedsSetting = false;
+ }
+ }
+ else if (msgKey == m_threadRootKey)
+ {
+ RerootThread(child, curHdr, announcer);
+ parentKeyNeedsSetting = false;
+ }
+ }
+ curHdr->SetThreadParent(newHdrKey);
+ // TODO: what should be msgKey if hdrMoved was true above?
+ if (msgKey == newHdrKey)
+ parentKeyNeedsSetting = false;
+
+ // OK, this is a reparenting - need to send notification
+ if (announcer)
+ announcer->NotifyParentChangedAll(msgKey, oldThreadParent, newHdrKey, nullptr);
+#ifdef DEBUG_bienvenu1
+ if (newHdrKey != m_threadKey)
+ printf("adding second level child\n");
+#endif
+ }
+ // Calculate a position for this child in date order
+ else if (!hdrMoved && childIndex > 0 && moveIndex == 0)
+ {
+ PRTime curHdrDate;
+
+ curHdr->GetDate(&curHdrDate);
+ if (newHdrDate < curHdrDate)
+ moveIndex = childIndex;
+ }
+ }
+ }
+ }
+ // If this header is not a reply to a header in the thread, and isn't a parent
+ // check to see if it starts with Re: - if not, and the first header does start
+ // with re, should we make this header the top level header?
+ // If it's date is less (or it's ID?), then yes.
+ if (numChildren > 0 && !(newHdrFlags & nsMsgMessageFlags::HasRe) && !inReplyTo)
+ {
+ PRTime topLevelHdrDate;
+
+ nsCOMPtr <nsIMsgDBHdr> topLevelHdr;
+ rv = GetRootHdr(nullptr, getter_AddRefs(topLevelHdr));
+ if (NS_SUCCEEDED(rv) && topLevelHdr)
+ {
+ topLevelHdr->GetDate(&topLevelHdrDate);
+ if (newHdrDate < topLevelHdrDate)
+ {
+ RerootThread(child, topLevelHdr, announcer);
+ mdb_pos outPos;
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos);
+ hdrMoved = true;
+ topLevelHdr->SetThreadParent(newHdrKey);
+ parentKeyNeedsSetting = false;
+ // ### need to get ancestor of new hdr here too.
+ SetThreadRootKey(newHdrKey);
+ child->SetThreadParent(nsMsgKey_None);
+ // argh, here we'd need to adjust all the headers that listed
+ // the demoted header as their thread parent, but only because
+ // of subject threading. Adjust them to point to the new parent,
+ // that is.
+ ReparentNonReferenceChildrenOf(topLevelHdr, newHdrKey, announcer);
+ }
+ }
+ }
+ // OK, check to see if we added this header, and didn't parent it.
+
+ if (numChildren > 0 && parentKeyNeedsSetting)
+ child->SetThreadParent(m_threadRootKey);
+
+ // Move child to keep thread sorted in ascending date order
+ if (!hdrMoved && moveIndex > 0)
+ {
+ mdb_pos outPos;
+ m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, moveIndex, &outPos);
+ }
+
+ // do this after we've put the new hdr in the thread
+ bool isKilled;
+ child->GetIsKilled(&isKilled);
+ if ((m_flags & nsMsgMessageFlags::Ignored || isKilled) && m_mdbDB)
+ m_mdbDB->MarkHdrRead(child, true, nullptr);
+#ifdef DEBUG_David_Bienvenu
+ nsMsgKey msgHdrThreadKey;
+ child->GetThreadId(&msgHdrThreadKey);
+ NS_ASSERTION(msgHdrThreadKey == m_threadKey, "adding msg to thread it doesn't belong to");
+#endif
+#ifdef DEBUG_bienvenu1
+ nsMsgDatabase *msgDB = static_cast<nsMsgDatabase*>(m_mdbDB);
+ msgDB->DumpThread(m_threadRootKey);
+#endif
+ return rv;
+}
+
+nsresult nsMsgThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *oldTopLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer *announcer)
+{
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ GetNumChildren(&numChildren);
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsMsgKey oldTopLevelHdrKey;
+
+ oldTopLevelHdr->GetMessageKey(&oldTopLevelHdrKey);
+ nsresult rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey oldThreadParent, curHdrKey;
+ nsMsgHdr* oldTopLevelMsgHdr = static_cast<nsMsgHdr*>(oldTopLevelHdr); // closed system, cast ok
+ curHdr->GetThreadParent(&oldThreadParent);
+ curHdr->GetMessageKey(&curHdrKey);
+ if (oldThreadParent == oldTopLevelHdrKey && curHdrKey != newParentKey && !oldTopLevelMsgHdr->IsParentOf(curHdr))
+ {
+ curHdr->GetThreadParent(&oldThreadParent);
+ curHdr->SetThreadParent(newParentKey);
+ // OK, this is a reparenting - need to send notification
+ if (announcer)
+ announcer->NotifyParentChangedAll(curHdrKey, oldThreadParent, newParentKey, nullptr);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsresult rv;
+
+ if (aIndex >= m_numChildren)
+ {
+ *aResult = nsMsgKey_None;
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ mdbOid oid;
+ rv = m_mdbTable->PosToOid( m_mdbDB->GetEnv(), aIndex, &oid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = oid.mOid_Id;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **result)
+{
+ // mork doesn't seem to handle this correctly, so deal with going off
+ // the end here.
+ if (aIndex >= m_numChildren)
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ mdbOid oid;
+ nsresult rv = m_mdbTable->PosToOid( m_mdbDB->GetEnv(), aIndex, &oid);
+ NS_ENSURE_SUCCESS(rv, NS_MSG_MESSAGE_NOT_FOUND);
+ nsIMdbRow *hdrRow = nullptr;
+ rv = m_mdbTable->PosToRow(m_mdbDB->GetEnv(), aIndex, &hdrRow);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hdrRow, NS_ERROR_FAILURE);
+ // CreateMsgHdr takes ownership of the hdrRow reference.
+ rv = m_mdbDB->CreateMsgHdr(hdrRow, oid.mOid_Id , result);
+ return (NS_SUCCEEDED(rv)) ? NS_OK : NS_MSG_MESSAGE_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsMsgThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **result)
+{
+ nsresult rv;
+
+ mdb_bool hasOid;
+ mdbOid rowObjectId;
+
+ NS_ENSURE_ARG_POINTER(result);
+ NS_ENSURE_TRUE(m_mdbTable, NS_ERROR_INVALID_POINTER);
+
+ *result = NULL;
+ rowObjectId.mOid_Id = msgKey;
+ rowObjectId.mOid_Scope = m_mdbDB->m_hdrRowScopeToken;
+ rv = m_mdbTable->HasOid(m_mdbDB->GetEnv(), &rowObjectId, &hasOid);
+
+ if (NS_SUCCEEDED(rv) && hasOid && m_mdbDB && m_mdbDB->m_mdbStore)
+ {
+ nsIMdbRow *hdrRow = nullptr;
+ rv = m_mdbDB->m_mdbStore->GetRow(m_mdbDB->GetEnv(), &rowObjectId, &hdrRow);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hdrRow, NS_ERROR_FAILURE);
+ rv = m_mdbDB->CreateMsgHdr(hdrRow, msgKey, result);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::RemoveChildAt(uint32_t aIndex)
+{
+ return NS_OK;
+}
+
+nsresult nsMsgThread::RemoveChild(nsMsgKey msgKey)
+{
+ nsresult rv;
+
+ mdbOid rowObjectId;
+ rowObjectId.mOid_Id = msgKey;
+ rowObjectId.mOid_Scope = m_mdbDB->m_hdrRowScopeToken;
+ rv = m_mdbTable->CutOid(m_mdbDB->GetEnv(), &rowObjectId);
+ // if this thread is empty, remove it from the all threads table.
+ if (m_numChildren == 0 && m_mdbDB->m_mdbAllThreadsTable)
+ {
+ mdbOid rowID;
+ rowID.mOid_Id = m_threadKey;
+ rowID.mOid_Scope = m_mdbDB->m_threadRowScopeToken;
+
+ m_mdbDB->m_mdbAllThreadsTable->CutOid(m_mdbDB->GetEnv(), &rowID);
+ }
+#if 0 // this seems to cause problems
+ if (m_numChildren == 0 && m_metaRow && m_mdbDB)
+ m_metaRow->CutAllColumns(m_mdbDB->GetEnv());
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer)
+{
+ uint32_t flags;
+ nsMsgKey key;
+ nsMsgKey threadParent;
+
+ NS_ENSURE_ARG_POINTER(child);
+
+ child->GetFlags(&flags);
+ child->GetMessageKey(&key);
+
+ child->GetThreadParent(&threadParent);
+ ReparentChildrenOf(key, threadParent, announcer);
+
+ // if this was the newest msg, clear the newest msg date so we'll recalc.
+ uint32_t date;
+ child->GetDateInSeconds(&date);
+ if (date == m_newestMsgDate)
+ SetNewestMsgDate(0);
+
+ if (!(flags & nsMsgMessageFlags::Read))
+ ChangeUnreadChildCount(-1);
+ ChangeChildCount(-1);
+ return RemoveChild(key);
+}
+
+nsresult nsMsgThread::ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer)
+{
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+
+ GetNumChildren(&numChildren);
+
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ if (numChildren > 0)
+ {
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey threadParent;
+
+ curHdr->GetThreadParent(&threadParent);
+ if (threadParent == oldParent)
+ {
+ nsMsgKey curKey;
+
+ curHdr->SetThreadParent(newParent);
+ curHdr->GetMessageKey(&curKey);
+ if (announcer)
+ announcer->NotifyParentChangedAll(curKey, oldParent, newParent, nullptr);
+ // if the old parent was the root of the thread, then only the first child gets
+ // promoted to root, and other children become children of the new root.
+ if (newParent == nsMsgKey_None)
+ {
+ SetThreadRootKey(curKey);
+ newParent = curKey;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::MarkChildRead(bool bRead)
+{
+ ChangeUnreadChildCount(bRead ? -1 : 1);
+ return NS_OK;
+}
+
+class nsMsgThreadEnumerator : public nsISimpleEnumerator {
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsMsgThreadEnumerator methods:
+ typedef nsresult (*nsMsgThreadEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
+
+ nsMsgThreadEnumerator(nsMsgThread *thread, nsMsgKey startKey,
+ nsMsgThreadEnumeratorFilter filter, void* closure);
+ int32_t MsgKeyFirstChildIndex(nsMsgKey inMsgKey);
+
+protected:
+ virtual ~nsMsgThreadEnumerator();
+
+ nsresult Prefetch();
+
+ nsIMdbTableRowCursor* mRowCursor;
+ nsCOMPtr <nsIMsgDBHdr> mResultHdr;
+ nsMsgThread* mThread;
+ nsMsgKey mThreadParentKey;
+ nsMsgKey mFirstMsgKey;
+ int32_t mChildIndex;
+ bool mDone;
+ bool mNeedToPrefetch;
+ nsMsgThreadEnumeratorFilter mFilter;
+ void* mClosure;
+ bool mFoundChildren;
+};
+
+nsMsgThreadEnumerator::nsMsgThreadEnumerator(nsMsgThread *thread, nsMsgKey startKey,
+ nsMsgThreadEnumeratorFilter filter, void* closure)
+ : mRowCursor(nullptr), mDone(false),
+ mFilter(filter), mClosure(closure), mFoundChildren(false)
+{
+ mThreadParentKey = startKey;
+ mChildIndex = 0;
+ mThread = thread;
+ mNeedToPrefetch = true;
+ mFirstMsgKey = nsMsgKey_None;
+
+ nsresult rv = mThread->GetRootHdr(nullptr, getter_AddRefs(mResultHdr));
+
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ mResultHdr->GetMessageKey(&mFirstMsgKey);
+
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ if (mThreadParentKey != nsMsgKey_None)
+ {
+ nsMsgKey msgKey = nsMsgKey_None;
+ uint32_t childIndex = 0;
+
+
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ mResultHdr->GetMessageKey(&msgKey);
+
+ if (msgKey == startKey)
+ {
+ mChildIndex = MsgKeyFirstChildIndex(msgKey);
+ mDone = (mChildIndex < 0);
+ break;
+ }
+
+ if (mDone)
+ break;
+
+ }
+ else
+ NS_ASSERTION(false, "couldn't get child from thread");
+ }
+ }
+
+#ifdef DEBUG_bienvenu1
+ nsCOMPtr <nsIMsgDBHdr> child;
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey threadParent;
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ child->GetMessageKey(&msgKey);
+ child->GetThreadParent(&threadParent);
+
+ printf("index = %ld key = %ld parent = %lx\n", childIndex, msgKey, threadParent);
+ }
+ }
+#endif
+ NS_ADDREF(thread);
+}
+
+nsMsgThreadEnumerator::~nsMsgThreadEnumerator()
+{
+ NS_RELEASE(mThread);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgThreadEnumerator, nsISimpleEnumerator)
+
+
+int32_t nsMsgThreadEnumerator::MsgKeyFirstChildIndex(nsMsgKey inMsgKey)
+{
+ // if (msgKey != mThreadParentKey)
+ // mDone = true;
+ // look through rest of thread looking for a child of this message.
+ // If the inMsgKey is the first message in the thread, then all children
+ // without parents are considered to be children of inMsgKey.
+ // Otherwise, only true children qualify.
+ uint32_t numChildren;
+ nsCOMPtr <nsIMsgDBHdr> curHdr;
+ int32_t firstChildIndex = -1;
+
+ mThread->GetNumChildren(&numChildren);
+
+ // if this is the first message in the thread, just check if there's more than
+ // one message in the thread.
+ // if (inMsgKey == mThread->m_threadRootKey)
+ // return (numChildren > 1) ? 1 : -1;
+
+ for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++)
+ {
+ nsresult rv = mThread->GetChildHdrAt(curChildIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr)
+ {
+ nsMsgKey parentKey;
+
+ curHdr->GetThreadParent(&parentKey);
+ if (parentKey == inMsgKey)
+ {
+ firstChildIndex = curChildIndex;
+ break;
+ }
+ }
+ }
+#ifdef DEBUG_bienvenu1
+ printf("first child index of %ld = %ld\n", inMsgKey, firstChildIndex);
+#endif
+ return firstChildIndex;
+}
+
+NS_IMETHODIMP nsMsgThreadEnumerator::GetNext(nsISupports **aItem)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+ nsresult rv;
+
+ if (mNeedToPrefetch)
+ {
+ rv = Prefetch();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mResultHdr)
+ {
+ *aItem = mResultHdr;
+ NS_ADDREF(*aItem);
+ mNeedToPrefetch = true;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgThreadEnumerator::Prefetch()
+{
+ nsresult rv=NS_OK; // XXX or should this default to an error?
+ mResultHdr = nullptr;
+ if (mThreadParentKey == nsMsgKey_None)
+ {
+ rv = mThread->GetRootHdr(&mChildIndex, getter_AddRefs(mResultHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && mResultHdr, "better be able to get root hdr");
+ mChildIndex = 0; // since root can be anywhere, set mChildIndex to 0.
+ }
+ else if (!mDone)
+ {
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ while (mChildIndex < (int32_t) numChildren)
+ {
+ rv = mThread->GetChildHdrAt(mChildIndex++, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr)
+ {
+ nsMsgKey parentKey;
+ nsMsgKey curKey;
+
+ if (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure))) {
+ mResultHdr = nullptr;
+ continue;
+ }
+
+ mResultHdr->GetThreadParent(&parentKey);
+ mResultHdr->GetMessageKey(&curKey);
+ // if the parent is the same as the msg we're enumerating over,
+ // or the parentKey isn't set, and we're iterating over the top
+ // level message in the thread, then leave mResultHdr set to cur msg.
+ if (parentKey == mThreadParentKey ||
+ (parentKey == nsMsgKey_None
+ && mThreadParentKey == mFirstMsgKey && curKey != mThreadParentKey))
+ break;
+ mResultHdr = nullptr;
+ }
+ else
+ NS_ASSERTION(false, "better be able to get child");
+ }
+ if (!mResultHdr && mThreadParentKey == mFirstMsgKey && !mFoundChildren && numChildren > 1)
+ mThread->ReparentMsgsWithInvalidParent(numChildren, mThreadParentKey);
+ }
+ if (!mResultHdr)
+ {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv))
+ {
+ mDone = true;
+ return rv;
+ }
+ else
+ mNeedToPrefetch = false;
+ mFoundChildren = true;
+
+#ifdef DEBUG_bienvenu1
+ nsMsgKey debugMsgKey;
+ mResultHdr->GetMessageKey(&debugMsgKey);
+ printf("next for %ld = %ld\n", mThreadParentKey, debugMsgKey);
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThreadEnumerator::HasMoreElements(bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (mNeedToPrefetch)
+ Prefetch();
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThread::EnumerateMessages(nsMsgKey parentKey, nsISimpleEnumerator* *result)
+{
+ nsMsgThreadEnumerator* e = new nsMsgThreadEnumerator(this, parentKey, nullptr, nullptr);
+ NS_ENSURE_TRUE(e, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*result = e);
+ return NS_OK;
+}
+
+nsresult nsMsgThread::ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey)
+{
+ nsresult rv = NS_OK;
+ // run through looking for messages that don't have a correct parent,
+ // i.e., a parent that's in the thread!
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> curChild;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(rv) && curChild)
+ {
+ nsMsgKey parentKey;
+ nsCOMPtr <nsIMsgDBHdr> parent;
+
+ curChild->GetThreadParent(&parentKey);
+
+ if (parentKey != nsMsgKey_None)
+ {
+ GetChild(parentKey, getter_AddRefs(parent));
+ if (!parent)
+ curChild->SetThreadParent(threadParentKey);
+ else
+ {
+ nsMsgKey childKey;
+ curChild->GetMessageKey(&childKey);
+ // can't be your own parent; set parent to thread parent,
+ // or make ourselves the root if we are the root.
+ if (childKey == parentKey)
+ curChild->SetThreadParent(m_threadRootKey == childKey ?
+ nsMsgKey_None : m_threadRootKey);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetRootHdr(int32_t *resultIndex, nsIMsgDBHdr **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+ nsresult rv = NS_OK;
+
+ if (m_threadRootKey != nsMsgKey_None)
+ {
+ rv = GetChildHdrForKey(m_threadRootKey, result, resultIndex);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ // check that we're really the root key.
+ nsMsgKey parentKey;
+ (*result)->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None)
+ return rv;
+ NS_RELEASE(*result);
+ }
+#ifdef DEBUG_David_Bienvenu
+ printf("need to reset thread root key\n");
+#endif
+ uint32_t numChildren;
+ nsMsgKey threadParentKey = nsMsgKey_None;
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> curChild;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(rv) && curChild)
+ {
+ nsMsgKey parentKey;
+
+ curChild->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None)
+ {
+ curChild->GetMessageKey(&threadParentKey);
+ if (*result)
+ {
+ NS_WARNING("two top level msgs, not good");
+ continue;
+ }
+ SetThreadRootKey(threadParentKey);
+ if (resultIndex)
+ *resultIndex = childIndex;
+ NS_ADDREF(*result = curChild);
+ ReparentMsgsWithInvalidParent(numChildren, threadParentKey);
+ // return NS_OK;
+ }
+ }
+ }
+ }
+ if (!*result)
+ {
+ // if we can't get the thread root key, we'll just get the first hdr.
+ // there's a bug where sometimes we weren't resetting the thread root key
+ // when removing the thread root key.
+ if (resultIndex)
+ *resultIndex = 0;
+ rv = GetChildHdrAt(0, result);
+ }
+ if (!*result)
+ return rv;
+ // Check that the thread id of the message is this thread.
+ nsMsgKey threadId = nsMsgKey_None;
+ (void)(*result)->GetThreadId(&threadId);
+ if (threadId != m_threadKey)
+ (*result)->SetThreadId(m_threadKey);
+ return rv;
+}
+
+nsresult nsMsgThread::ChangeChildCount(int32_t delta)
+{
+ nsresult rv;
+
+ uint32_t childCount = 0;
+ m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, childCount);
+
+ NS_WARNING_ASSERTION(childCount != 0 || delta > 0, "child count gone negative");
+ childCount += delta;
+
+ NS_WARNING_ASSERTION((int32_t) childCount >= 0, "child count gone to 0 or below");
+ if ((int32_t) childCount < 0) // force child count to >= 0
+ childCount = 0;
+
+ rv = m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, childCount);
+ m_numChildren = childCount;
+ return rv;
+}
+
+nsresult nsMsgThread::ChangeUnreadChildCount(int32_t delta)
+{
+ nsresult rv;
+
+ uint32_t childCount = 0;
+ m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, childCount);
+ childCount += delta;
+ if ((int32_t) childCount < 0)
+ {
+#ifdef DEBUG_bienvenu1
+ NS_ASSERTION(false, "negative unread child count");
+#endif
+ childCount = 0;
+ }
+ rv = m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, childCount);
+ m_numUnreadChildren = childCount;
+ return rv;
+}
+
+nsresult nsMsgThread::SetThreadRootKey(nsMsgKey threadRootKey)
+{
+ m_threadRootKey = threadRootKey;
+ return m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadRootKeyColumnToken, threadRootKey);
+}
+
+nsresult nsMsgThread::GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex)
+{
+ uint32_t numChildren;
+ uint32_t childIndex = 0;
+ nsresult rv = NS_OK; // XXX or should this default to an error?
+
+ NS_ENSURE_ARG_POINTER(result);
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ rv = GetChildHdrAt(childIndex, result);
+ if (NS_SUCCEEDED(rv) && *result)
+ {
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ (*result)->GetMessageKey(&msgKey);
+
+ if (msgKey == desiredKey)
+ {
+ nsMsgKey threadKey;
+ (*result)->GetThreadId(&threadKey);
+ if (threadKey != m_threadKey) // this msg isn't in this thread
+ {
+ NS_WARNING("msg in wrong thread - this shouldn't happen");
+ uint32_t msgSize;
+ (*result)->GetMessageSize(&msgSize);
+ if (msgSize == 0) // this is a phantom message - let's get rid of it.
+ {
+ RemoveChild(msgKey);
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ else
+ {
+ // otherwise, let's try to figure out which thread
+ // this message really belongs to.
+ nsCOMPtr<nsIMsgThread> threadKeyThread =
+ dont_AddRef(m_mdbDB->GetThreadForThreadId(threadKey));
+ if (threadKeyThread)
+ {
+ nsCOMPtr<nsIMsgDBHdr> otherThreadHdr;
+ threadKeyThread->GetChild(msgKey, getter_AddRefs(otherThreadHdr));
+ if (otherThreadHdr)
+ {
+ // Message is in one thread but has a different thread id.
+ // Remove it from the thread and then rethread it.
+ RemoveChild(msgKey);
+ threadKeyThread->RemoveChildHdr(otherThreadHdr, nullptr);
+ bool newThread;
+ nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(otherThreadHdr.get());
+ m_mdbDB->ThreadNewHdr(msgHdr, newThread);
+ }
+ else
+ {
+ (*result)->SetThreadId(m_threadKey);
+ }
+ }
+ }
+ }
+ break;
+ }
+ NS_RELEASE(*result);
+ }
+ }
+ if (resultIndex)
+ *resultIndex = (int32_t) childIndex;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetFirstUnreadChild(nsIMsgDBHdr **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+ uint8_t minLevel = 0xff;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ nsCOMPtr <nsIMsgDBHdr> retHdr;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ bool isRead;
+ rv = m_mdbDB->IsRead(msgKey, &isRead);
+ if (NS_SUCCEEDED(rv) && !isRead)
+ {
+ // this is the root, so it's the best we're going to do.
+ if (msgKey == m_threadRootKey)
+ {
+ retHdr = child;
+ break;
+ }
+ uint8_t level = 0;
+ nsMsgKey parentId;
+ child->GetThreadParent(&parentId);
+ nsCOMPtr <nsIMsgDBHdr> parent;
+ // count number of ancestors - that's our level
+ while (parentId != nsMsgKey_None)
+ {
+ rv = m_mdbDB->GetMsgHdrForKey(parentId, getter_AddRefs(parent));
+ if (parent)
+ {
+ parent->GetThreadParent(&parentId);
+ level++;
+ }
+ }
+ if (level < minLevel)
+ {
+ minLevel = level;
+ retHdr = child;
+ }
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*result = retHdr);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgThread::GetNewestMsgDate(uint32_t *aResult)
+{
+ // if this hasn't been set, figure it out by enumerating the msgs in the thread.
+ if (!m_newestMsgDate)
+ {
+ uint32_t numChildren;
+ nsresult rv;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t) numChildren < 0)
+ numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t msgDate;
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate)
+ m_newestMsgDate = msgDate;
+ }
+ }
+
+ }
+ *aResult = m_newestMsgDate;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsMsgThread::SetNewestMsgDate(uint32_t aNewestMsgDate)
+{
+ m_newestMsgDate = aNewestMsgDate;
+ return m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadNewestMsgDateColumnToken, aNewestMsgDate);
+}
diff --git a/mailnews/db/msgdb/src/nsNewsDatabase.cpp b/mailnews/db/msgdb/src/nsNewsDatabase.cpp
new file mode 100644
index 000000000..3fb3dbb2b
--- /dev/null
+++ b/mailnews/db/msgdb/src/nsNewsDatabase.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsIMsgDBView.h"
+#include "nsIMsgThread.h"
+#include "nsNewsDatabase.h"
+#include "nsMsgKeySet.h"
+#include "nsCOMPtr.h"
+#include "prlog.h"
+
+#if defined(DEBUG_sspitzer_) || defined(DEBUG_seth_)
+#define DEBUG_NEWS_DATABASE 1
+#endif
+
+nsNewsDatabase::nsNewsDatabase()
+{
+ m_readSet = nullptr;
+}
+
+nsNewsDatabase::~nsNewsDatabase()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(nsNewsDatabase, nsMsgDatabase)
+NS_IMPL_RELEASE_INHERITED(nsNewsDatabase, nsMsgDatabase)
+
+NS_IMETHODIMP nsNewsDatabase::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ if (!aInstancePtr) return NS_ERROR_NULL_POINTER;
+ *aInstancePtr = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsINewsDatabase)))
+ {
+ *aInstancePtr = static_cast<nsINewsDatabase *>(this);
+ }
+
+ if(*aInstancePtr)
+ {
+ AddRef();
+ return NS_OK;
+ }
+
+ return nsMsgDatabase::QueryInterface(aIID, aInstancePtr);
+}
+
+nsresult nsNewsDatabase::Close(bool forceCommit)
+{
+ return nsMsgDatabase::Close(forceCommit);
+}
+
+nsresult nsNewsDatabase::ForceClosed()
+{
+ return nsMsgDatabase::ForceClosed();
+}
+
+nsresult nsNewsDatabase::Commit(nsMsgDBCommit commitType)
+{
+ if (m_dbFolderInfo && m_readSet)
+ {
+ // let's write out our idea of the read set so we can compare it with that of
+ // the .rc file next time we start up.
+ nsCString readSet;
+ m_readSet->Output(getter_Copies(readSet));
+ m_dbFolderInfo->SetCharProperty("readSet", readSet);
+ }
+ return nsMsgDatabase::Commit(commitType);
+}
+
+
+uint32_t nsNewsDatabase::GetCurVersion()
+{
+ return kMsgDBVersion;
+}
+
+NS_IMETHODIMP nsNewsDatabase::IsRead(nsMsgKey key, bool *pRead)
+{
+ NS_ASSERTION(pRead, "null out param in IsRead");
+ if (!pRead) return NS_ERROR_NULL_POINTER;
+
+ if (!m_readSet) return NS_ERROR_FAILURE;
+
+ *pRead = m_readSet->IsMember(key);
+ return NS_OK;
+}
+
+nsresult nsNewsDatabase::IsHeaderRead(nsIMsgDBHdr *msgHdr, bool *pRead)
+{
+ nsresult rv;
+ nsMsgKey messageKey;
+
+ if (!msgHdr || !pRead) return NS_ERROR_NULL_POINTER;
+
+ rv = msgHdr->GetMessageKey(&messageKey);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = IsRead(messageKey,pRead);
+ return rv;
+}
+
+// return highest article number we've seen.
+NS_IMETHODIMP nsNewsDatabase::GetHighWaterArticleNum(nsMsgKey *key)
+{
+ NS_ASSERTION(m_dbFolderInfo, "null db folder info");
+ if (!m_dbFolderInfo)
+ return NS_ERROR_FAILURE;
+ return m_dbFolderInfo->GetHighWater(key);
+}
+
+// return the key of the first article number we know about.
+// Since the iterator iterates in id order, we can just grab the
+// messagekey of the first header it returns.
+// ### dmb
+// This will not deal with the situation where we get holes in
+// the headers we know about. Need to figure out how and when
+// to solve that. This could happen if a transfer is interrupted.
+// Do we need to keep track of known arts permanently?
+NS_IMETHODIMP nsNewsDatabase::GetLowWaterArticleNum(nsMsgKey *key)
+{
+ nsresult rv;
+ nsMsgHdr *pHeader;
+
+ nsCOMPtr<nsISimpleEnumerator> hdrs;
+ rv = EnumerateMessages(getter_AddRefs(hdrs));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = hdrs->GetNext((nsISupports**)&pHeader);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
+ if (NS_FAILED(rv))
+ return rv;
+
+ return pHeader->GetMessageKey(key);
+}
+
+nsresult nsNewsDatabase::ExpireUpTo(nsMsgKey expireKey)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+nsresult nsNewsDatabase::ExpireRange(nsMsgKey startRange, nsMsgKey endRange)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP nsNewsDatabase::GetReadSet(nsMsgKeySet **pSet)
+{
+ if (!pSet) return NS_ERROR_NULL_POINTER;
+ *pSet = m_readSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNewsDatabase::SetReadSet(nsMsgKeySet *pSet)
+{
+ m_readSet = pSet;
+
+ if (m_readSet)
+ {
+ // compare this read set with the one in the db folder info.
+ // If not equivalent, sync with this one.
+ nsCString dbReadSet;
+ if (m_dbFolderInfo)
+ m_dbFolderInfo->GetCharProperty("readSet", dbReadSet);
+ nsCString newsrcReadSet;
+ m_readSet->Output(getter_Copies(newsrcReadSet));
+ if (!dbReadSet.Equals(newsrcReadSet))
+ SyncWithReadSet();
+ }
+ return NS_OK;
+}
+
+
+bool nsNewsDatabase::SetHdrReadFlag(nsIMsgDBHdr *msgHdr, bool bRead)
+{
+ nsresult rv;
+ bool isRead;
+ rv = IsHeaderRead(msgHdr, &isRead);
+
+ if (isRead == bRead)
+ {
+ // give the base class a chance to update m_flags.
+ nsMsgDatabase::SetHdrReadFlag(msgHdr, bRead);
+ return false;
+ }
+ else {
+ nsMsgKey messageKey;
+
+ // give the base class a chance to update m_flags.
+ nsMsgDatabase::SetHdrReadFlag(msgHdr, bRead);
+ rv = msgHdr->GetMessageKey(&messageKey);
+ if (NS_FAILED(rv)) return false;
+
+ NS_ASSERTION(m_readSet, "m_readSet is null");
+ if (!m_readSet) return false;
+
+ if (!bRead) {
+#ifdef DEBUG_NEWS_DATABASE
+ printf("remove %d from the set\n",messageKey);
+#endif
+
+ m_readSet->Remove(messageKey);
+
+ rv = NotifyReadChanged(nullptr);
+ if (NS_FAILED(rv)) return false;
+ }
+ else {
+#ifdef DEBUG_NEWS_DATABASE
+ printf("add %d to the set\n",messageKey);
+#endif
+
+ if (m_readSet->Add(messageKey) < 0) return false;
+
+ rv = NotifyReadChanged(nullptr);
+ if (NS_FAILED(rv)) return false;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsNewsDatabase::MarkAllRead(uint32_t *aNumMarked,
+ nsMsgKey **aThoseMarked)
+{
+ nsMsgKey lowWater = nsMsgKey_None, highWater;
+ nsCString knownArts;
+ if (m_dbFolderInfo)
+ {
+ m_dbFolderInfo->GetKnownArtsSet(getter_Copies(knownArts));
+ nsMsgKeySet *knownKeys = nsMsgKeySet::Create(knownArts.get());
+ if (knownKeys)
+ lowWater = knownKeys->GetFirstMember();
+
+ delete knownKeys;
+ }
+ if (lowWater == nsMsgKey_None)
+ GetLowWaterArticleNum(&lowWater);
+ GetHighWaterArticleNum(&highWater);
+ if (lowWater > 2)
+ m_readSet->AddRange(1, lowWater - 1);
+ nsresult err = nsMsgDatabase::MarkAllRead(aNumMarked, aThoseMarked);
+ if (NS_SUCCEEDED(err) && 1 <= highWater)
+ m_readSet->AddRange(1, highWater); // mark everything read in newsrc.
+
+ return err;
+}
+
+nsresult nsNewsDatabase::SyncWithReadSet()
+{
+
+ // The code below attempts to update the underlying nsMsgDatabase's idea
+ // of read/unread flags to match the read set in the .newsrc file. It should
+ // only be called when they don't match, e.g., we crashed after committing the
+ // db but before writing out the .newsrc
+ nsCOMPtr <nsISimpleEnumerator> hdrs;
+ nsresult rv = EnumerateMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false, readInNewsrc, isReadInDB, changed = false;
+ int32_t numMessages = 0, numUnreadMessages = 0;
+ nsMsgKey messageKey;
+ nsCOMPtr <nsIMsgThread> threadHdr;
+
+ // Scan all messages in DB
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIMsgDBHdr> header = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsMsgDatabase::IsHeaderRead(header, &isReadInDB);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ header->GetMessageKey(&messageKey);
+ IsRead(messageKey,&readInNewsrc);
+
+ numMessages++;
+ if (!readInNewsrc)
+ numUnreadMessages++;
+
+ // If DB and readSet disagree on Read/Unread, fix DB
+ if (readInNewsrc!=isReadInDB)
+ {
+ MarkHdrRead(header, readInNewsrc, nullptr);
+ changed = true;
+ }
+ }
+
+ // Update FolderInfo Counters
+ int32_t oldMessages, oldUnreadMessages;
+ rv = m_dbFolderInfo->GetNumMessages(&oldMessages);
+ if (NS_SUCCEEDED(rv) && oldMessages!=numMessages)
+ {
+ changed = true;
+ m_dbFolderInfo->ChangeNumMessages(numMessages-oldMessages);
+ }
+ rv = m_dbFolderInfo->GetNumUnreadMessages(&oldUnreadMessages);
+ if (NS_SUCCEEDED(rv) && oldUnreadMessages!=numUnreadMessages)
+ {
+ changed = true;
+ m_dbFolderInfo->ChangeNumUnreadMessages(numUnreadMessages-oldUnreadMessages);
+ }
+
+ if (changed)
+ Commit(nsMsgDBCommitType::kLargeCommit);
+
+ return rv;
+}
+
+nsresult nsNewsDatabase::AdjustExpungedBytesOnDelete(nsIMsgDBHdr *msgHdr)
+{
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline && m_dbFolderInfo)
+ {
+ uint32_t size = 0;
+ (void)msgHdr->GetOfflineMessageSize(&size);
+ return m_dbFolderInfo->ChangeExpungedBytes (size);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNewsDatabase::GetDefaultViewFlags(nsMsgViewFlagsTypeValue *aDefaultViewFlags)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultViewFlags);
+ GetIntPref("mailnews.default_news_view_flags", aDefaultViewFlags);
+ if (*aDefaultViewFlags < nsMsgViewFlagsType::kNone ||
+ *aDefaultViewFlags > (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kShowIgnored |
+ nsMsgViewFlagsType::kUnreadOnly |
+ nsMsgViewFlagsType::kExpandAll |
+ nsMsgViewFlagsType::kGroupBySort))
+ *aDefaultViewFlags = nsMsgViewFlagsType::kThreadedDisplay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNewsDatabase::GetDefaultSortType(nsMsgViewSortTypeValue *aDefaultSortType)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortType);
+ GetIntPref("mailnews.default_news_sort_type", aDefaultSortType);
+ if (*aDefaultSortType < nsMsgViewSortType::byDate ||
+ *aDefaultSortType > nsMsgViewSortType::byAccount)
+ *aDefaultSortType = nsMsgViewSortType::byThread;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNewsDatabase::GetDefaultSortOrder(nsMsgViewSortOrderValue *aDefaultSortOrder)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultSortOrder);
+ GetIntPref("mailnews.default_news_sort_order", aDefaultSortOrder);
+ if (*aDefaultSortOrder != nsMsgViewSortOrder::descending)
+ *aDefaultSortOrder = nsMsgViewSortOrder::ascending;
+ return NS_OK;
+}