Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsMsgMessageFlags.h"
#include "nsMsgEnumerator.h"
#include "MailNewsTypes2.h"
#include "mozilla/DebugOnly.h"
NS_IMPL_ISUPPORTS(nsMsgThread, nsIMsgThread)
nsMsgThread::nsMsgThread() { Init(); }
nsMsgThread::nsMsgThread(nsMsgDatabase* db, nsIMdbTable* table) {
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_numNewChildren = 0;
m_numUnreadChildren = 0;
m_flags = 0;
m_newestMsgDate = 0;
m_cachedValuesInitialized = false;
}
nsMsgThread::~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);
// Count the number of new messages we started with. This information isn't
// stored in the database, so we have to iterate through the messages.
bool hasNew;
err = m_mdbDB->HasNew(&hasNew);
NS_ENSURE_SUCCESS(err, err);
if (hasNew) {
nsTArray<nsMsgKey> newKeys;
m_mdbDB->GetNewList(newKeys);
newKeys.Sort();
for (uint32_t childIndex = 0; childIndex < m_numChildren; childIndex++) {
nsMsgKey key;
err = GetChildKeyAt(childIndex, &key);
if (NS_SUCCEEDED(err) && newKeys.ContainsSorted(key)) {
MarkChildNew(true);
}
}
}
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,
PromiseFlatCString(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::GetNumNewChildren(uint32_t* aNumNewChildren) {
NS_ENSURE_ARG_POINTER(aNumNewChildren);
*aNumNewChildren = m_numNewChildren;
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);
uint32_t unused;
child->AndFlags(~(nsMsgMessageFlags::Watched), &unused);
// 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 = 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 (newHdrFlags & nsMsgMessageFlags::New) ChangeNewChildCount(1);
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 (uint32_t 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(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);
}
#ifdef DEBUG_David_Bienvenu
nsMsgKey msgHdrThreadKey;
child->GetThreadId(&msgHdrThreadKey);
NS_ASSERTION(msgHdrThreadKey == m_threadKey,
"adding msg to thread it doesn't belong to");
#endif
return rv;
}
nsresult nsMsgThread::ReparentNonReferenceChildrenOf(
nsIMsgDBHdr* oldTopLevelHdr, nsMsgKey newParentKey,
nsIDBChangeAnnouncer* announcer) {
nsCOMPtr<nsIMsgDBHdr> curHdr;
uint32_t numChildren = 0;
GetNumChildren(&numChildren);
for (uint32_t 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::New) ChangeNewChildCount(-1);
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 = 0;
GetNumChildren(&numChildren);
nsCOMPtr<nsIMsgDBHdr> curHdr;
if (numChildren > 0) {
for (uint32_t 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::MarkChildNew(bool bNew) {
ChangeNewChildCount(bNew ? 1 : -1);
return NS_OK;
}
NS_IMETHODIMP nsMsgThread::MarkChildRead(bool bRead) {
ChangeUnreadChildCount(bRead ? -1 : 1);
return NS_OK;
}
/**
* Helper class for enumerating through the messages in a thread.
*/
class nsMsgThreadEnumerator : public nsBaseMsgEnumerator {
public:
// nsIMsgEnumerator support.
NS_IMETHOD GetNext(nsIMsgDBHdr** aItem) override;
NS_IMETHOD HasMoreElements(bool* aResult) override;
// nsMsgThreadEnumerator methods:
typedef nsresult (*nsMsgThreadEnumeratorFilter)(nsIMsgDBHdr* hdr,
void* closure);
nsMsgThreadEnumerator(nsMsgThread* thread, nsMsgKey startKey,
nsMsgThreadEnumeratorFilter filter, void* closure);
int32_t MsgKeyFirstChildIndex(nsMsgKey inMsgKey);
protected:
~nsMsgThreadEnumerator() override = default;
nsresult Prefetch();
nsIMdbTableRowCursor* mRowCursor;
nsCOMPtr<nsIMsgDBHdr> mResultHdr;
RefPtr<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(getter_AddRefs(mResultHdr));
if (NS_SUCCEEDED(rv) && mResultHdr) mResultHdr->GetMessageKey(&mFirstMsgKey);
uint32_t numChildren = 0;
mThread->GetNumChildren(&numChildren);
if (mThreadParentKey != nsMsgKey_None) {
nsMsgKey msgKey = nsMsgKey_None;
for (uint32_t 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
}
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.
int32_t firstChildIndex = -1;
uint32_t numChildren = 0;
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++) {
nsCOMPtr<nsIMsgDBHdr> curHdr;
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(nsIMsgDBHdr** aItem) {
NS_ENSURE_ARG_POINTER(aItem);
nsresult rv;
if (mNeedToPrefetch) {
rv = Prefetch();
NS_ENSURE_SUCCESS(rv, rv);
}
if (mResultHdr) {
NS_ADDREF(*aItem = mResultHdr);
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(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 = 0;
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,
nsIMsgEnumerator** result) {
NS_ADDREF(*result =
new nsMsgThreadEnumerator(this, parentKey, nullptr, nullptr));
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(nsIMsgDBHdr** result) {
NS_ENSURE_ARG_POINTER(result);
*result = nullptr;
int32_t resultIndex = -1;
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;
// XXX Hack: since GetChildHdrForKey() addref'ed result, we need to
// release any unwanted result before continuing.
NS_RELEASE(*result);
}
#ifdef DEBUG_David_Bienvenu
printf("need to reset thread root key\n");
#endif
nsMsgKey threadParentKey = nsMsgKey_None;
uint32_t numChildren = 0;
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);
curChild.forget(result);
ReparentMsgsWithInvalidParent(numChildren, threadParentKey);
}
}
}
}
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.
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::ChangeNewChildCount(int32_t delta) {
m_numNewChildren += delta;
return NS_OK;
}
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) {
NS_ENSURE_ARG_POINTER(result);
nsresult rv = NS_OK; // XXX or should this default to an error?
uint32_t numChildren = 0;
GetNumChildren(&numChildren);
uint32_t childIndex;
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;
}
// XXX Hack: since GetChildHdrAt() addref'ed result, we need to
// release any unwanted result before continuing in the loop.
NS_RELEASE(*result);
}
}
if (resultIndex) *resultIndex = (int32_t)childIndex;
return rv;
}
NS_IMETHODIMP nsMsgThread::GetFirstUnreadChild(nsIMsgDBHdr** result) {
NS_ENSURE_ARG_POINTER(result);
uint8_t minLevel = 0xff;
uint32_t numChildren = 0;
GetNumChildren(&numChildren);
nsCOMPtr<nsIMsgDBHdr> retHdr;
for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
nsCOMPtr<nsIMsgDBHdr> child;
nsresult 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;
}
}
}
}
retHdr.forget(result);
return (*result) ? NS_OK : NS_ERROR_NULL_POINTER;
}
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) {
nsresult rv;
uint32_t numChildren;
GetNumChildren(&numChildren);
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);
}