/* -*- 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 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 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(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(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 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 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 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(m_mdbDB); msgDB->DumpThread(m_threadRootKey); #endif return rv; } nsresult nsMsgThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *oldTopLevelHdr, nsMsgKey newParentKey, nsIDBChangeAnnouncer *announcer) { nsCOMPtr 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(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 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 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 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 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 curChild; rv = GetChildHdrAt(childIndex, getter_AddRefs(curChild)); if (NS_SUCCEEDED(rv) && curChild) { nsMsgKey parentKey; nsCOMPtr 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 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 threadKeyThread = dont_AddRef(m_mdbDB->GetThreadForThreadId(threadKey)); if (threadKeyThread) { nsCOMPtr 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(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 retHdr; for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) { nsCOMPtr 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 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 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); }