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;
}
Beispiel #13
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);
      }
    }