/* -*- 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 "nsMsgXFViewThread.h" #include "nsMsgSearchDBView.h" #include "nsMsgMessageFlags.h" NS_IMPL_ISUPPORTS(nsMsgXFViewThread, nsIMsgThread) nsMsgXFViewThread::nsMsgXFViewThread(nsMsgSearchDBView *view, nsMsgKey threadId) { m_numUnreadChildren = 0; m_numChildren = 0; m_flags = 0; m_newestMsgDate = 0; m_view = view; m_threadId = threadId; } nsMsgXFViewThread::~nsMsgXFViewThread() { } NS_IMETHODIMP nsMsgXFViewThread::SetThreadKey(nsMsgKey threadKey) { m_threadId = threadKey; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::GetThreadKey(nsMsgKey *aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = m_threadId; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::GetFlags(uint32_t *aFlags) { NS_ENSURE_ARG_POINTER(aFlags); *aFlags = m_flags; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::SetFlags(uint32_t aFlags) { m_flags = aFlags; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::SetSubject(const nsACString& aSubject) { NS_ASSERTION(false, "shouldn't call this"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgXFViewThread::GetSubject(nsACString& result) { NS_ASSERTION(false, "shouldn't call this"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgXFViewThread::GetNumChildren(uint32_t *aNumChildren) { NS_ENSURE_ARG_POINTER(aNumChildren); *aNumChildren = m_keys.Length(); return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::GetNumUnreadChildren (uint32_t *aNumUnreadChildren) { NS_ENSURE_ARG_POINTER(aNumUnreadChildren); *aNumUnreadChildren = m_numUnreadChildren; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::AddChild(nsIMsgDBHdr *aNewHdr, nsIMsgDBHdr *aInReplyTo, bool aThreadInThread, nsIDBChangeAnnouncer *aAnnouncer) { uint32_t whereInserted; return AddHdr(aNewHdr, false, whereInserted, nullptr); } // 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, uint32_t &whereInserted, nsIMsgDBHdr **outParent) { nsCOMPtr newHdrFolder; newHdr->GetFolder(getter_AddRefs(newHdrFolder)); uint32_t newHdrFlags = 0; uint32_t 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 = nullptr; 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. uint16_t numReferences; newHdr->GetNumReferences(&numReferences); nsCOMPtr parent; int32_t parentIndex = -1; for (int32_t i = numReferences - 1; i >= 0; i--) { nsAutoCString 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 = nullptr; } break; } } if (parent) { if (outParent) NS_ADDREF(*outParent = parent); uint32_t parentLevel = m_levels[parentIndex]; nsMsgKey parentKey; parent->GetMessageKey(&parentKey); nsCOMPtr 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. uint32_t msgDate, childDate; newHdr->GetDateInSeconds(&msgDate); nsCOMPtr child; nsMsgViewIndex i; nsMsgViewIndex insertIndex = m_keys.Length(); uint32_t 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; uint8_t 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 = nullptr; nsCOMPtr 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 topLevelHdr; // rv = GetRootHdr(nullptr, getter_AddRefs(topLevelHdr)); // if (NS_SUCCEEDED(rv) && topLevelHdr) // { // topLevelHdr->GetDate(&topLevelHdrDate); // if (newHdrDate < topLevelHdrDate) // } // } return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult) { if (aIndex >= m_keys.Length()) return NS_MSG_MESSAGE_NOT_FOUND; nsCOMPtr db; nsresult rv = m_folders[aIndex]->GetMsgDatabase(getter_AddRefs(db)); NS_ENSURE_SUCCESS(rv, rv); return db->GetMsgHdrForKey(m_keys[aIndex], aResult); } NS_IMETHODIMP nsMsgXFViewThread::RemoveChildAt(uint32_t aIndex) { m_keys.RemoveElementAt(aIndex); m_levels.RemoveElementAt(aIndex); m_folders.RemoveObjectAt(aIndex); return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) { NS_ENSURE_ARG_POINTER(child); nsMsgKey msgKey; uint32_t msgFlags; child->GetMessageKey(&msgKey); child->GetFlags(&msgFlags); nsCOMPtr msgFolder; child->GetFolder(getter_AddRefs(msgFolder)); // 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); for (uint32_t childIndex = 0; childIndex < m_keys.Length(); childIndex++) { if (m_keys[childIndex] == msgKey && m_folders[childIndex] == msgFolder) { uint8_t 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::GetRootHdr(int32_t *aResultIndex, nsIMsgDBHdr **aResult) { NS_ENSURE_ARG_POINTER(aResult); if (aResultIndex) *aResultIndex = 0; return GetChildHdrAt(0, aResult); } NS_IMETHODIMP nsMsgXFViewThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult) { NS_ASSERTION(false, "shouldn't call this"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgXFViewThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult) { NS_ASSERTION(false, "shouldn't call this"); return NS_ERROR_NOT_IMPLEMENTED; } int32_t nsMsgXFViewThread::HdrIndex(nsIMsgDBHdr *hdr) { nsMsgKey msgKey; nsCOMPtr folder; hdr->GetMessageKey(&msgKey); hdr->GetFolder(getter_AddRefs(folder)); for (uint32_t i = 0; i < m_keys.Length(); i++) { if (m_keys[i] == msgKey && m_folders[i] == folder) return i; } return -1; } void nsMsgXFViewThread::ChangeUnreadChildCount(int32_t delta) { m_numUnreadChildren += delta; } void nsMsgXFViewThread::ChangeChildCount(int32_t delta) { m_numChildren += delta; } bool nsMsgXFViewThread::IsHdrParentOf(nsIMsgDBHdr *possibleParent, nsIMsgDBHdr *possibleChild) { uint16_t referenceToCheck = 0; possibleChild->GetNumReferences(&referenceToCheck); nsAutoCString reference; nsCString messageId; possibleParent->GetMessageId(getter_Copies(messageId)); while (referenceToCheck > 0) { possibleChild->GetStringReference(referenceToCheck - 1, reference); if (reference.Equals(messageId)) return true; // if reference didn't match, check if this ref is for a non-existent // header. If it is, continue looking at ancestors. nsCOMPtr refHdr; m_view->GetMsgHdrFromHash(reference, getter_AddRefs(refHdr)); if (refHdr) break; referenceToCheck--; } return false; } NS_IMETHODIMP nsMsgXFViewThread::GetNewestMsgDate(uint32_t *aResult) { // if this hasn't been set, figure it out by enumerating the msgs in the thread. if (!m_newestMsgDate) { uint32_t numChildren; nsresult rv = NS_OK; GetNumChildren(&numChildren); if ((int32_t) numChildren < 0) numChildren = 0; for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) { nsCOMPtr child; rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { uint32_t msgDate; child->GetDateInSeconds(&msgDate); if (msgDate > m_newestMsgDate) m_newestMsgDate = msgDate; } } } *aResult = m_newestMsgDate; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::SetNewestMsgDate(uint32_t aNewestMsgDate) { m_newestMsgDate = aNewestMsgDate; return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::MarkChildRead(bool aRead) { ChangeUnreadChildCount(aRead ? -1 : 1); return NS_OK; } NS_IMETHODIMP nsMsgXFViewThread::GetFirstUnreadChild(nsIMsgDBHdr **aResult) { NS_ENSURE_ARG(aResult); 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 child; rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); if (NS_SUCCEEDED(rv) && child) { nsMsgKey msgKey; child->GetMessageKey(&msgKey); bool isRead; nsCOMPtr 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; } NS_IMETHODIMP nsMsgXFViewThread::EnumerateMessages(nsMsgKey aParentKey, nsISimpleEnumerator **aResult) { NS_ERROR("shouldn't call this"); return NS_ERROR_NOT_IMPLEMENTED; }