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;
}
Example #8
0
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);
      }
    }