summaryrefslogtreecommitdiffstats
path: root/mailnews/db/msgdb/src/nsMsgThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/db/msgdb/src/nsMsgThread.cpp')
-rw-r--r--mailnews/db/msgdb/src/nsMsgThread.cpp1180
1 files changed, 1180 insertions, 0 deletions
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);
+}