diff options
Diffstat (limited to 'mailnews/db/msgdb/src/nsMsgThread.cpp')
-rw-r--r-- | mailnews/db/msgdb/src/nsMsgThread.cpp | 1180 |
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); +} |