NS_IMETHODIMP nsMsgThread::SetThreadKey(nsMsgKey threadKey) { 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::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); }
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; }
nsresult nsMsgThread::RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer) { nsCOMPtr <nsIMsgDBHdr> ancestorHdr = newParentOfOldRoot; nsMsgKey newRoot; newParentOfOldRoot->GetMessageKey(&newRoot); mdb_pos outPos; nsMsgKey newHdrAncestor; ancestorHdr->GetMessageKey(&newRoot); nsresult rv = NS_OK; // 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, PRBool threadInThread, nsIDBChangeAnnouncer *announcer) { nsresult rv = NS_OK; nsMsgHdr* hdr = static_cast<nsMsgHdr*>(child); // closed system, cast ok PRUint32 newHdrFlags = 0; PRUint32 msgDate; nsMsgKey newHdrKey = 0; PRBool parentKeyNeedsSetting = PR_TRUE; nsIMdbRow *hdrRow = hdr->GetMDBRow(); hdr->GetRawFlags(&newHdrFlags); hdr->GetMessageKey(&newHdrKey); hdr->GetDateInSeconds(&msgDate); if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate); if (newHdrFlags & MSG_FLAG_WATCHED) SetFlags(m_flags | MSG_FLAG_WATCHED); child->AndFlags(~(MSG_FLAG_WATCHED), &newHdrFlags); // These are threading flags that the child may have set before being added // to the database. PRUint32 protoThreadFlags; child->GetUint32Property("ProtoThreadFlags", &protoThreadFlags); SetFlags(m_flags | protoThreadFlags); // Clear the flag so that it doesn't fudge anywhere else child->SetUint32Property("ProtoThreadFlags", 0); PRUint32 numChildren; PRUint32 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 & MSG_FLAG_READ)) ChangeUnreadChildCount(1); } if (inReplyTo) { nsMsgKey parentKey; inReplyTo->GetMessageKey(&parentKey); child->SetThreadParent(parentKey); parentKeyNeedsSetting = PR_FALSE; } // check if this header is a parent of one of the messages in this thread PRBool hdrMoved = PR_FALSE; nsCOMPtr <nsIMsgDBHdr> curHdr; PRUint32 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; 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 = PR_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 = PR_FALSE; } } else if (msgKey == m_threadRootKey) { RerootThread(child, curHdr, announcer); parentKeyNeedsSetting = PR_FALSE; } } curHdr->SetThreadParent(newHdrKey); if (msgKey == newHdrKey) parentKeyNeedsSetting = PR_FALSE; // OK, this is a reparenting - need to send notification if (announcer) announcer->NotifyParentChangedAll(msgKey, oldThreadParent, newHdrKey, nsnull); #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 (LL_CMP(newHdrDate, <, curHdrDate)) moveIndex = childIndex; } } } }
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; }
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(); 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; 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); 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; }
NS_IMETHODIMP nsMsgThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, PRBool threadInThread, nsIDBChangeAnnouncer *announcer) { nsresult ret = NS_OK; nsMsgHdr* hdr = NS_STATIC_CAST(nsMsgHdr*, child); // closed system, cast ok PRUint32 newHdrFlags = 0; PRUint32 msgDate; nsMsgKey newHdrKey = 0; PRBool parentKeyNeedsSetting = PR_TRUE; nsIMdbRow *hdrRow = hdr->GetMDBRow(); hdr->GetRawFlags(&newHdrFlags); hdr->GetMessageKey(&newHdrKey); hdr->GetDateInSeconds(&msgDate); if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate); if (newHdrFlags & MSG_FLAG_IGNORED) SetFlags(m_flags | MSG_FLAG_IGNORED); if (newHdrFlags & MSG_FLAG_WATCHED) SetFlags(m_flags | MSG_FLAG_WATCHED); child->AndFlags(~(MSG_FLAG_WATCHED | MSG_FLAG_IGNORED), &newHdrFlags); PRUint32 numChildren; PRUint32 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 & MSG_FLAG_READ)) ChangeUnreadChildCount(1); } if (inReplyTo) { nsMsgKey parentKey; inReplyTo->GetMessageKey(&parentKey); child->SetThreadParent(parentKey); parentKeyNeedsSetting = PR_FALSE; } // check if this header is a parent of one of the messages in this thread PRBool hdrMoved = PR_FALSE; nsCOMPtr <nsIMsgDBHdr> curHdr; // 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; ret = GetChildHdrAt(childIndex, getter_AddRefs(curHdr)); if (NS_SUCCEEDED(ret) && 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 = PR_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 = PR_FALSE; } } else if (msgKey == m_threadRootKey) { RerootThread(child, curHdr, announcer); parentKeyNeedsSetting = PR_FALSE; } } curHdr->SetThreadParent(newHdrKey); if (msgKey == newHdrKey) parentKeyNeedsSetting = PR_FALSE; // OK, this is a reparenting - need to send notification if (announcer) announcer->NotifyParentChangedAll(msgKey, oldThreadParent, newHdrKey, nsnull); #ifdef DEBUG_bienvenu1 if (newHdrKey != m_threadKey) printf("adding second level child\n"); #endif } } } } // 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 & MSG_FLAG_HAS_RE) && !inReplyTo) { PRTime newHdrDate; PRTime topLevelHdrDate; nsCOMPtr <nsIMsgDBHdr> topLevelHdr; ret = GetRootHdr(nsnull, getter_AddRefs(topLevelHdr)); if (NS_SUCCEEDED(ret) && topLevelHdr) { child->GetDate(&newHdrDate); topLevelHdr->GetDate(&topLevelHdrDate); if (LL_CMP(newHdrDate, <, topLevelHdrDate)) { RerootThread(child, topLevelHdr, announcer); mdb_pos outPos; m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos); topLevelHdr->SetThreadParent(newHdrKey); parentKeyNeedsSetting = PR_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); } }