nsMsgViewIndex nsMsgGroupThread::AddChildFromGroupView(nsIMsgDBHdr *child, nsMsgDBView *view) { uint32_t newHdrFlags = 0; uint32_t msgDate; nsMsgKey newHdrKey = 0; child->GetFlags(&newHdrFlags); child->GetMessageKey(&newHdrKey); child->GetDateInSeconds(&msgDate); if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate); child->AndFlags(~(nsMsgMessageFlags::Watched), &newHdrFlags); uint32_t numChildren; // 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) m_threadRootKey = newHdrKey; if (! (newHdrFlags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(1); return AddMsgHdrInDateOrder(child, view); }
NS_IMETHODIMP nsMsgGroupThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) { uint32_t flags; nsMsgKey key; if (!child) return NS_ERROR_NULL_POINTER; child->GetFlags(&flags); child->GetMessageKey(&key); // 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); nsMsgViewIndex threadIndex = FindMsgHdr(child); bool wasFirstChild = threadIndex == 0; nsresult rv = RemoveChildAt(threadIndex); // if we're deleting the root of a dummy thread, need to update the threadKey // and the dummy header at position 0 if (m_dummy && wasFirstChild && m_keys.Length() > 1) { nsIMsgDBHdr *newRootChild; GetChildHdrAt(1, &newRootChild); SetMsgHdrAt(0, newRootChild); } 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::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); 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. PRUint32 rowCount = 0; m_mdbTable->GetCount(m_mdbDB->GetEnv(), &rowCount); // NS_ASSERTION(m_numChildren <= rowCount, "num children wrong - fixing"); if (m_numChildren > rowCount) ChangeChildCount((PRInt32) rowCount - (PRInt32) m_numChildren); if ((PRInt32) m_numUnreadChildren < 0) ChangeUnreadChildCount(- (PRInt32) m_numUnreadChildren); if (NS_SUCCEEDED(err)) m_cachedValuesInitialized = PR_TRUE; } return err; }
NS_IMETHODIMP nsMsgGroupThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) { PRUint32 flags; nsMsgKey key; nsMsgKey threadParent; if (!child) return NS_ERROR_NULL_POINTER; 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. PRUint32 date; child->GetDateInSeconds(&date); if (date == m_newestMsgDate) SetNewestMsgDate(0); if (!(flags & MSG_FLAG_READ)) ChangeUnreadChildCount(-1); PRBool keyWasFirstKey = (m_keys[0] == key); nsresult rv = RemoveChild(key); // if we're deleting the root of a dummy thread, need to update the threadKey // and the dummy header at position 0 if (m_dummy && keyWasFirstKey && m_keys.Length() > 1) m_keys[0] = m_keys[1]; return rv; }
nsresult nsMsgGroupThread::AddChildFromGroupView(nsIMsgDBHdr *child, nsMsgDBView *view) { PRUint32 newHdrFlags = 0; PRUint32 msgDate; nsMsgKey newHdrKey = 0; child->GetFlags(&newHdrFlags); child->GetMessageKey(&newHdrKey); child->GetDateInSeconds(&msgDate); if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate); child->AndFlags(~(MSG_FLAG_WATCHED), &newHdrFlags); PRUint32 numChildren; // 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) m_threadRootKey = newHdrKey; if (! (newHdrFlags & MSG_FLAG_READ)) ChangeUnreadChildCount(1); return AddMsgHdrInDateOrder(child, view); }
NS_IMETHODIMP nsMsgXFViewThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) { NS_ENSURE_ARG_POINTER(child); nsMsgKey msgKey; PRUint32 msgFlags; child->GetMessageKey(&msgKey); child->GetFlags(&msgFlags); nsCOMPtr<nsIMsgFolder> msgFolder; child->GetFolder(getter_AddRefs(msgFolder)); // if this was the newest msg, clear the newest msg date so we'll recalc. PRUint32 date; child->GetDateInSeconds(&date); if (date == m_newestMsgDate) SetNewestMsgDate(0); for (PRUint32 childIndex = 0; childIndex < m_keys.Length(); childIndex++) { if (m_keys[childIndex] == msgKey && m_folders[childIndex] == msgFolder) { PRUint8 levelRemoved = m_keys[childIndex]; // Adjust the levels of all the children of this header nsMsgViewIndex i; for (i = childIndex + 1; i < m_keys.Length() && m_levels[i] > levelRemoved; i++) m_levels[i] = m_levels[i] - 1; m_view->NoteChange(childIndex + 1, i - childIndex + 1, nsMsgViewNotificationCode::changed); m_keys.RemoveElementAt(childIndex); m_levels.RemoveElementAt(childIndex); m_folders.RemoveObjectAt(childIndex); if (!(msgFlags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(-1); ChangeChildCount(-1); return NS_OK; } } return NS_ERROR_FAILURE; }
NS_IMETHODIMP nsMsgXFViewThread::MarkChildRead(bool aRead) { ChangeUnreadChildCount(aRead ? -1 : 1); return NS_OK; }
// Returns the parent of the newly added header. If reparentChildren // is true, we believe that the new header is a parent of an existing // header, and we should find it, and reparent it. nsresult nsMsgXFViewThread::AddHdr(nsIMsgDBHdr *newHdr, bool reparentChildren, PRUint32 &whereInserted, nsIMsgDBHdr **outParent) { nsCOMPtr<nsIMsgFolder> newHdrFolder; newHdr->GetFolder(getter_AddRefs(newHdrFolder)); PRUint32 newHdrFlags = 0; PRUint32 msgDate; nsMsgKey newHdrKey = 0; newHdr->GetMessageKey(&newHdrKey); newHdr->GetDateInSeconds(&msgDate); newHdr->GetFlags(&newHdrFlags); if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate); if (newHdrFlags & nsMsgMessageFlags::Watched) SetFlags(m_flags | nsMsgMessageFlags::Watched); ChangeChildCount(1); if (! (newHdrFlags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(1); if (m_numChildren == 1) { m_keys.InsertElementAt(0, newHdrKey); m_levels.InsertElementAt(0, 0); m_folders.InsertObjectAt(newHdrFolder, 0); if (outParent) *outParent = nsnull; whereInserted = 0; return NS_OK; } // Find our parent, if any, in the thread. Starting at the newest // reference, and working our way back, see if we've mapped that reference // to this thread. PRUint16 numReferences; newHdr->GetNumReferences(&numReferences); nsCOMPtr<nsIMsgDBHdr> parent; PRInt32 parentIndex; for (PRInt32 i = numReferences - 1; i >= 0; i--) { nsCAutoString reference; newHdr->GetStringReference(i, reference); if (reference.IsEmpty()) break; // I could look for the thread from the reference, but getting // the header directly should be fine. If it's not, that means // that the parent isn't in this thread, though it should be. m_view->GetMsgHdrFromHash(reference, getter_AddRefs(parent)); if (parent) { parentIndex = HdrIndex(parent); if (parentIndex == -1) { NS_ERROR("how did we get in the wrong thread?"); parent = nsnull; } break; } } if (parent) { if (outParent) NS_ADDREF(*outParent = parent); PRUint32 parentLevel = m_levels[parentIndex]; nsMsgKey parentKey; parent->GetMessageKey(&parentKey); nsCOMPtr<nsIMsgFolder> parentFolder; parent->GetFolder(getter_AddRefs(parentFolder)); // iterate over our parents' children until we find one we're older than, // and insert ourselves before it, or as the last child. In other words, // insert, sorted by date. PRUint32 msgDate, childDate; newHdr->GetDateInSeconds(&msgDate); nsCOMPtr<nsIMsgDBHdr> child; nsMsgViewIndex i; nsMsgViewIndex insertIndex = m_keys.Length(); PRUint32 insertLevel = parentLevel + 1; for (i = parentIndex; i < m_keys.Length() && (i == (nsMsgViewIndex)parentIndex || m_levels[i] >= parentLevel); i++) { GetChildHdrAt(i, getter_AddRefs(child)); if (child) { if (reparentChildren && IsHdrParentOf(newHdr, child)) { insertIndex = i; // bump all the children of the current child, and the child nsMsgViewIndex j = insertIndex; PRUint8 childLevel = m_levels[insertIndex]; do { m_levels[j] = m_levels[j] + 1; j++; } while (j < m_keys.Length() && m_levels[j] > childLevel); break; } else if (m_levels[i] == parentLevel + 1) // possible sibling { child->GetDateInSeconds(&childDate); if (msgDate < childDate) { // if we think we need to reparent, remember this // insert index, but keep looking for children. insertIndex = i; insertLevel = m_levels[i]; // if the sibling we're inserting after has children, we need // to go after the children. while (insertIndex + 1 < m_keys.Length() && m_levels[insertIndex + 1] > insertLevel) insertIndex++; if (!reparentChildren) break; } } } } m_keys.InsertElementAt(insertIndex, newHdrKey); m_levels.InsertElementAt(insertIndex, insertLevel); m_folders.InsertObjectAt(newHdrFolder, insertIndex); whereInserted = insertIndex; } else { if (outParent) *outParent = nsnull; nsCOMPtr<nsIMsgDBHdr> rootHdr; GetChildHdrAt(0, getter_AddRefs(rootHdr)); // If the new header is a parent of the root then it should be promoted. if (rootHdr && IsHdrParentOf(newHdr, rootHdr)) { m_keys.InsertElementAt(0, newHdrKey); m_levels.InsertElementAt(0, 0); m_folders.InsertObjectAt(newHdrFolder, 0); whereInserted = 0; // Adjust level of old root hdr and its children for (nsMsgViewIndex i = 1; i < m_keys.Length(); i++) m_levels[i] = m_levels[1] + 1; } else { m_keys.AppendElement(newHdrKey); m_levels.AppendElement(1); m_folders.AppendObject(newHdrFolder); if (outParent) NS_IF_ADDREF(*outParent = rootHdr); whereInserted = m_keys.Length() -1; } } // ### TODO handle the case where the root header starts // with Re, and the new one doesn't, and is earlier. In that // case, we want to promote the new header to root. // PRTime newHdrDate; // newHdr->GetDate(&newHdrDate); // if (numChildren > 0 && !(newHdrFlags & nsMsgMessageFlags::HasRe)) // { // PRTime topLevelHdrDate; // nsCOMPtr<nsIMsgDBHdr> topLevelHdr; // rv = GetRootHdr(nsnull, getter_AddRefs(topLevelHdr)); // if (NS_SUCCEEDED(rv) && topLevelHdr) // { // topLevelHdr->GetDate(&topLevelHdrDate); // if (LL_CMP(newHdrDate, <, topLevelHdrDate)) // } // } return NS_OK; }
NS_IMETHODIMP nsMsgGroupThread::MarkChildRead(bool bRead) { ChangeUnreadChildCount(bRead ? -1 : 1); return NS_OK; }
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::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); } }