NS_IMETHODIMP nsMsgGroupThread::GetRootHdr(int32_t *resultIndex, nsIMsgDBHdr **result) { if (!result) return NS_ERROR_NULL_POINTER; *result = nullptr; if (m_threadRootKey != nsMsgKey_None) { nsresult ret = GetChildHdrForKey(m_threadRootKey, result, resultIndex); if (NS_SUCCEEDED(ret) && *result) return ret; else { printf("need to reset thread root key\n"); uint32_t numChildren; nsMsgKey threadParentKey = nsMsgKey_None; GetNumChildren(&numChildren); for (int32_t childIndex = 0; childIndex < (int32_t) numChildren; childIndex++) { nsCOMPtr <nsIMsgDBHdr> curChild; ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild)); if (NS_SUCCEEDED(ret) && curChild) { nsMsgKey parentKey; curChild->GetThreadParent(&parentKey); if (parentKey == nsMsgKey_None) { NS_ASSERTION(!(*result), "two top level msgs, not good"); curChild->GetMessageKey(&threadParentKey); m_threadRootKey = threadParentKey; if (resultIndex) *resultIndex = childIndex; *result = curChild; NS_ADDREF(*result); // ReparentMsgsWithInvalidParent(numChildren, threadParentKey); // return NS_OK; } } } if (*result) { return NS_OK; } } // 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; return GetChildHdrAt(0, result); }
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 nsMsgXFViewThread::GetFirstUnreadChild(nsIMsgDBHdr **aResult) { NS_ENSURE_ARG(aResult); PRUint32 numChildren; nsresult rv = NS_OK; GetNumChildren(&numChildren); if ((PRInt32) numChildren < 0) numChildren = 0; for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++) { nsCOMPtr<nsIMsgDBHdr> child; rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { nsMsgKey msgKey; child->GetMessageKey(&msgKey); bool isRead; nsCOMPtr<nsIMsgDatabase> db; nsresult rv = m_folders[childIndex]->GetMsgDatabase(getter_AddRefs(db)); if (NS_SUCCEEDED(rv)) rv = db->IsRead(msgKey, &isRead); if (NS_SUCCEEDED(rv) && !isRead) { NS_ADDREF(*aResult = child); break; } } } return rv; }
nsresult nsMsgGroupThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey, nsIDBChangeAnnouncer *announcer) { #if 0 nsCOMPtr <nsIMsgDBHdr> curHdr; PRUint32 numChildren; PRUint32 childIndex = 0; GetNumChildren(&numChildren); for (childIndex = 0; childIndex < numChildren; childIndex++) { nsMsgKey msgKey; topLevelHdr->GetMessageKey(&msgKey); nsresult ret = GetChildHdrAt(childIndex, getter_AddRefs(curHdr)); if (NS_SUCCEEDED(ret) && curHdr) { nsMsgKey oldThreadParent, curHdrKey; nsIMsgDBHdr *curMsgHdr = curHdr; curHdr->GetThreadParent(&oldThreadParent); curHdr->GetMessageKey(&curHdrKey); if (oldThreadParent == msgKey && curHdrKey != newParentKey && topLevelMsgHdr->IsParentOf(curHdr)) { curHdr->GetThreadParent(&oldThreadParent); curHdr->SetThreadParent(newParentKey); // OK, this is a reparenting - need to send notification if (announcer) announcer->NotifyParentChangedAll(curHdrKey, oldThreadParent, newParentKey, nsnull); } } } #endif return NS_OK; }
NS_IMETHODIMP nsMsgGroupThread::GetFirstUnreadChild(nsIMsgDBHdr **result) { NS_ENSURE_ARG(result); uint32_t numChildren; nsresult rv = NS_OK; GetNumChildren(&numChildren); if ((int32_t) numChildren < 0) numChildren = 0; for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) { nsCOMPtr <nsIMsgDBHdr> child; rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { nsMsgKey msgKey; child->GetMessageKey(&msgKey); bool isRead; rv = m_db->IsRead(msgKey, &isRead); if (NS_SUCCEEDED(rv) && !isRead) { *result = child; NS_ADDREF(*result); break; } } } return rv; }
nsresult nsMsgGroupThread::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? if (!result) return NS_ERROR_NULL_POINTER; 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) break; NS_RELEASE(*result); } } if (resultIndex) *resultIndex = childIndex; return rv; }
NS_IMETHODIMP nsMsgXFViewThread::GetRootHdr(PRInt32 *aResultIndex, nsIMsgDBHdr **aResult) { NS_ENSURE_ARG_POINTER(aResult); if (aResultIndex) *aResultIndex = 0; return GetChildHdrAt(0, aResult); }
NS_IMETHODIMP nsMsgXFViewThread::GetNewestMsgDate(PRUint32 *aResult) { // if this hasn't been set, figure it out by enumerating the msgs in the thread. if (!m_newestMsgDate) { PRUint32 numChildren; nsresult rv = NS_OK; GetNumChildren(&numChildren); if ((PRInt32) numChildren < 0) numChildren = 0; for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++) { nsCOMPtr<nsIMsgDBHdr> child; rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { PRUint32 msgDate; child->GetDateInSeconds(&msgDate); if (msgDate > m_newestMsgDate) m_newestMsgDate = msgDate; } } } *aResult = m_newestMsgDate; return NS_OK; }
nsresult nsMsgThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *oldTopLevelHdr, nsMsgKey newParentKey, nsIDBChangeAnnouncer *announcer) { nsCOMPtr <nsIMsgDBHdr> curHdr; uint32_t numChildren; uint32_t childIndex = 0; GetNumChildren(&numChildren); for (childIndex = 0; childIndex < numChildren; childIndex++) { nsMsgKey oldTopLevelHdrKey; oldTopLevelHdr->GetMessageKey(&oldTopLevelHdrKey); nsresult rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr)); if (NS_SUCCEEDED(rv) && curHdr) { nsMsgKey oldThreadParent, curHdrKey; nsMsgHdr* oldTopLevelMsgHdr = static_cast<nsMsgHdr*>(oldTopLevelHdr); // closed system, cast ok curHdr->GetThreadParent(&oldThreadParent); curHdr->GetMessageKey(&curHdrKey); if (oldThreadParent == oldTopLevelHdrKey && curHdrKey != newParentKey && !oldTopLevelMsgHdr->IsParentOf(curHdr)) { curHdr->GetThreadParent(&oldThreadParent); curHdr->SetThreadParent(newParentKey); // OK, this is a reparenting - need to send notification if (announcer) announcer->NotifyParentChangedAll(curHdrKey, oldThreadParent, newParentKey, nullptr); } } } return NS_OK; }
nsresult nsMsgGroupThread::ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey) { nsresult ret = NS_OK; // run through looking for messages that don't have a correct parent, // i.e., a parent that's in the thread! for (int32_t childIndex = 0; childIndex < (int32_t) numChildren; childIndex++) { nsCOMPtr <nsIMsgDBHdr> curChild; ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild)); if (NS_SUCCEEDED(ret) && curChild) { nsMsgKey parentKey; nsCOMPtr <nsIMsgDBHdr> parent; curChild->GetThreadParent(&parentKey); if (parentKey != nsMsgKey_None) { GetChild(parentKey, getter_AddRefs(parent)); if (!parent) curChild->SetThreadParent(threadParentKey); } } } return ret; }
NS_IMETHODIMP nsMsgThread::GetFirstUnreadChild(nsIMsgDBHdr **result) { NS_ENSURE_ARG_POINTER(result); uint32_t numChildren; nsresult rv = NS_OK; uint8_t minLevel = 0xff; GetNumChildren(&numChildren); if ((int32_t) numChildren < 0) numChildren = 0; nsCOMPtr <nsIMsgDBHdr> retHdr; for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) { nsCOMPtr <nsIMsgDBHdr> child; rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { nsMsgKey msgKey; child->GetMessageKey(&msgKey); bool isRead; rv = m_mdbDB->IsRead(msgKey, &isRead); if (NS_SUCCEEDED(rv) && !isRead) { // this is the root, so it's the best we're going to do. if (msgKey == m_threadRootKey) { retHdr = child; break; } uint8_t level = 0; nsMsgKey parentId; child->GetThreadParent(&parentId); nsCOMPtr <nsIMsgDBHdr> parent; // count number of ancestors - that's our level while (parentId != nsMsgKey_None) { rv = m_mdbDB->GetMsgHdrForKey(parentId, getter_AddRefs(parent)); if (parent) { parent->GetThreadParent(&parentId); level++; } } if (level < minLevel) { minLevel = level; retHdr = child; } } } } NS_IF_ADDREF(*result = retHdr); return rv; }
nsresult nsMsgGroupThread::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) { m_threadRootKey = curKey; newParent = curKey; } } } } } return rv; }
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::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); } }
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; }
// 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 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 nsMsgGroupThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult) { return GetChildHdrAt((int32_t) m_keys.IndexOf(msgKey), aResult); }
nsresult nsMsgThread::GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex) { uint32_t numChildren; uint32_t childIndex = 0; nsresult rv = NS_OK; // XXX or should this default to an error? NS_ENSURE_ARG_POINTER(result); GetNumChildren(&numChildren); if ((int32_t) numChildren < 0) numChildren = 0; for (childIndex = 0; childIndex < numChildren; childIndex++) { rv = GetChildHdrAt(childIndex, result); if (NS_SUCCEEDED(rv) && *result) { nsMsgKey msgKey; // we're only doing one level of threading, so check if caller is // asking for children of the first message in the thread or not. // if not, we will tell him there are no children. (*result)->GetMessageKey(&msgKey); if (msgKey == desiredKey) { nsMsgKey threadKey; (*result)->GetThreadId(&threadKey); if (threadKey != m_threadKey) // this msg isn't in this thread { NS_WARNING("msg in wrong thread - this shouldn't happen"); uint32_t msgSize; (*result)->GetMessageSize(&msgSize); if (msgSize == 0) // this is a phantom message - let's get rid of it. { RemoveChild(msgKey); rv = NS_ERROR_UNEXPECTED; } else { // otherwise, let's try to figure out which thread // this message really belongs to. nsCOMPtr<nsIMsgThread> threadKeyThread = dont_AddRef(m_mdbDB->GetThreadForThreadId(threadKey)); if (threadKeyThread) { nsCOMPtr<nsIMsgDBHdr> otherThreadHdr; threadKeyThread->GetChild(msgKey, getter_AddRefs(otherThreadHdr)); if (otherThreadHdr) { // Message is in one thread but has a different thread id. // Remove it from the thread and then rethread it. RemoveChild(msgKey); threadKeyThread->RemoveChildHdr(otherThreadHdr, nullptr); bool newThread; nsMsgHdr* msgHdr = static_cast<nsMsgHdr*>(otherThreadHdr.get()); m_mdbDB->ThreadNewHdr(msgHdr, newThread); } else { (*result)->SetThreadId(m_threadKey); } } } } break; } NS_RELEASE(*result); } } if (resultIndex) *resultIndex = (int32_t) childIndex; return rv; }
NS_IMETHODIMP nsMsgThread::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; }