Back to index

lightning-sunbird  0.9+nobinonly
nsMsgThread.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1999
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "msgCore.h"
00040 #include "nsMsgThread.h"
00041 #include "nsMsgDatabase.h"
00042 #include "nsCOMPtr.h"
00043 #include "MailNewsTypes2.h"
00044 
00045 NS_IMPL_ISUPPORTS1(nsMsgThread, nsMsgThread)
00046 
00047 
00048 nsMsgThread::nsMsgThread()
00049 {
00050   
00051   MOZ_COUNT_CTOR(nsMsgThread);
00052   Init();
00053 }
00054 nsMsgThread::nsMsgThread(nsMsgDatabase *db, nsIMdbTable *table)
00055 {
00056   MOZ_COUNT_CTOR(nsMsgThread);
00057   Init();
00058   m_mdbTable = table;
00059   m_mdbDB = db;
00060   if (db)
00061     db->AddRef();
00062   
00063   if (table && db)
00064   {
00065     table->GetMetaRow(db->GetEnv(), nsnull, nsnull, &m_metaRow);
00066     InitCachedValues();
00067   }
00068 }
00069 
00070 void nsMsgThread::Init()
00071 {
00072   m_threadKey = nsMsgKey_None; 
00073   m_threadRootKey = nsMsgKey_None;
00074   m_numChildren = 0;        
00075   m_numUnreadChildren = 0;  
00076   m_flags = 0;
00077   m_mdbTable = nsnull;
00078   m_mdbDB = nsnull;
00079   m_metaRow = nsnull;
00080   m_newestMsgDate = 0;
00081   m_cachedValuesInitialized = PR_FALSE;
00082 }
00083 
00084 
00085 nsMsgThread::~nsMsgThread()
00086 {
00087   MOZ_COUNT_DTOR(nsMsgThread);
00088   if (m_mdbTable)
00089     m_mdbTable->Release();
00090   if (m_mdbDB)
00091     m_mdbDB->Release();
00092   if (m_metaRow)
00093     m_metaRow->Release();
00094 }
00095 
00096 nsresult nsMsgThread::InitCachedValues()
00097 {
00098   nsresult err = NS_OK;
00099   
00100   if (!m_mdbDB || !m_metaRow)
00101     return NS_ERROR_NULL_POINTER;
00102   
00103   if (!m_cachedValuesInitialized)
00104   {
00105     err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadFlagsColumnToken, &m_flags);
00106     err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, &m_numChildren);
00107     err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadIdColumnToken, &m_threadKey);
00108     err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, &m_numUnreadChildren);
00109     err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadRootKeyColumnToken, &m_threadRootKey, nsMsgKey_None);
00110     err = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadNewestMsgDateColumnToken, &m_newestMsgDate, 0);
00111     // fix num children if it's wrong. this doesn't work - some DB's have a bogus thread table
00112     // that is full of bogus headers - don't know why.
00113     PRUint32 rowCount = 0;
00114     m_mdbTable->GetCount(m_mdbDB->GetEnv(), &rowCount);
00115     //    NS_ASSERTION(m_numChildren <= rowCount, "num children wrong - fixing");
00116     if (m_numChildren > rowCount)
00117       ChangeChildCount((PRInt32) rowCount - (PRInt32) m_numChildren);
00118     if ((PRInt32) m_numUnreadChildren < 0)
00119       ChangeUnreadChildCount(- (PRInt32) m_numUnreadChildren);
00120     if (NS_SUCCEEDED(err))
00121       m_cachedValuesInitialized = PR_TRUE;
00122   }
00123   return err;
00124 }
00125 
00126 NS_IMETHODIMP        nsMsgThread::SetThreadKey(nsMsgKey threadKey)
00127 {
00128   m_threadKey = threadKey;
00129   // by definition, the initial thread key is also the thread root key.
00130   SetThreadRootKey(threadKey);
00131   // gotta set column in meta row here.
00132   return m_mdbDB->UInt32ToRowCellColumn(
00133                     m_metaRow, m_mdbDB->m_threadIdColumnToken, threadKey);
00134 }
00135 
00136 NS_IMETHODIMP nsMsgThread::GetThreadKey(nsMsgKey *result)
00137 {
00138   if (result)
00139   {
00140     nsresult res = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadIdColumnToken, &m_threadKey);
00141     *result = m_threadKey;
00142     return res;
00143   }
00144   else
00145     return NS_ERROR_NULL_POINTER;
00146 }
00147 
00148 NS_IMETHODIMP nsMsgThread::GetFlags(PRUint32 *result)
00149 {
00150   if (result)
00151   {
00152     nsresult res = m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadFlagsColumnToken, &m_flags);
00153     *result = m_flags;
00154     return res;
00155   }
00156   else
00157     return NS_ERROR_NULL_POINTER;
00158 }
00159 
00160 NS_IMETHODIMP nsMsgThread::SetFlags(PRUint32 flags)
00161 {
00162   m_flags = flags;
00163   return m_mdbDB->UInt32ToRowCellColumn(
00164                     m_metaRow, m_mdbDB->m_threadFlagsColumnToken, m_flags);
00165 }
00166 
00167 NS_IMETHODIMP nsMsgThread::SetSubject(const char *subject)
00168 {
00169   return m_mdbDB->CharPtrToRowCellColumn(m_metaRow, m_mdbDB->m_threadSubjectColumnToken, subject);
00170 }
00171 
00172 NS_IMETHODIMP nsMsgThread::GetSubject(char **result)
00173 {
00174   if (!result)
00175     return NS_ERROR_NULL_POINTER;
00176   return m_mdbDB->RowCellColumnToCharPtr(
00177                     m_metaRow, m_mdbDB->m_threadSubjectColumnToken, result);
00178 }
00179 
00180 NS_IMETHODIMP nsMsgThread::GetNumChildren(PRUint32 *result)
00181 {
00182   if (result)
00183   {
00184     *result = m_numChildren;
00185     return NS_OK;
00186   }
00187   else
00188     return NS_ERROR_NULL_POINTER;
00189 }
00190 
00191 
00192 NS_IMETHODIMP nsMsgThread::GetNumUnreadChildren (PRUint32 *result)
00193 {
00194   if (result)
00195   {
00196     *result = m_numUnreadChildren;
00197     return NS_OK;
00198   }
00199   else
00200     return NS_ERROR_NULL_POINTER;
00201 }
00202 
00203 nsresult nsMsgThread::RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer)
00204 {
00205   nsCOMPtr <nsIMsgDBHdr> ancestorHdr = newParentOfOldRoot;
00206   nsMsgKey newRoot;
00207   newParentOfOldRoot->GetMessageKey(&newRoot);
00208   mdb_pos outPos;
00209 
00210   nsMsgKey newHdrAncestor;
00211   ancestorHdr->GetMessageKey(&newRoot);
00212   nsresult rv = NS_OK;
00213   // loop trying to find the oldest ancestor of this msg
00214   // that is a parent of the root. The oldest ancestor will
00215   // become the root of the thread.
00216   do 
00217   {
00218     ancestorHdr->GetThreadParent(&newHdrAncestor);
00219     if (newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey && newHdrAncestor != newRoot)
00220     {
00221       newRoot = newHdrAncestor;
00222       rv = m_mdbDB->GetMsgHdrForKey(newRoot, getter_AddRefs(ancestorHdr));
00223     }
00224   }
00225   while (NS_SUCCEEDED(rv) && ancestorHdr && newHdrAncestor != nsMsgKey_None && newHdrAncestor != m_threadRootKey
00226     && newHdrAncestor != newRoot);
00227   SetThreadRootKey(newRoot);
00228   ReparentNonReferenceChildrenOf(oldRoot, newRoot, announcer);
00229   if (ancestorHdr)
00230   {
00231     nsIMsgDBHdr *msgHdr = ancestorHdr;
00232     nsMsgHdr* rootMsgHdr = NS_STATIC_CAST(nsMsgHdr*, msgHdr);          // closed system, cast ok
00233     nsIMdbRow *newRootHdrRow = rootMsgHdr->GetMDBRow();
00234     // move the  root hdr to pos 0.
00235     m_mdbTable->MoveRow(m_mdbDB->GetEnv(), newRootHdrRow, -1, 0, &outPos);
00236     ancestorHdr->SetThreadParent(nsMsgKey_None);
00237   }
00238   return rv;
00239 }
00240 
00241 NS_IMETHODIMP nsMsgThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, PRBool threadInThread, 
00242                                     nsIDBChangeAnnouncer *announcer)
00243 {
00244   nsresult ret = NS_OK;
00245   nsMsgHdr* hdr = NS_STATIC_CAST(nsMsgHdr*, child);          // closed system, cast ok
00246   PRUint32 newHdrFlags = 0;
00247   PRUint32 msgDate;
00248   nsMsgKey newHdrKey = 0;
00249   PRBool parentKeyNeedsSetting = PR_TRUE;
00250   
00251   nsIMdbRow *hdrRow = hdr->GetMDBRow();
00252   hdr->GetRawFlags(&newHdrFlags);
00253   hdr->GetMessageKey(&newHdrKey);
00254   hdr->GetDateInSeconds(&msgDate);
00255   if (msgDate > m_newestMsgDate)
00256     SetNewestMsgDate(msgDate);
00257 
00258   if (newHdrFlags & MSG_FLAG_IGNORED)
00259     SetFlags(m_flags | MSG_FLAG_IGNORED);
00260 
00261   if (newHdrFlags & MSG_FLAG_WATCHED)
00262     SetFlags(m_flags | MSG_FLAG_WATCHED);
00263 
00264   child->AndFlags(~(MSG_FLAG_WATCHED | MSG_FLAG_IGNORED), &newHdrFlags);
00265   PRUint32 numChildren;
00266   PRUint32 childIndex = 0;
00267   
00268   // get the num children before we add the new header.
00269   GetNumChildren(&numChildren);
00270   
00271   // if this is an empty thread, set the root key to this header's key
00272   if (numChildren == 0)
00273     SetThreadRootKey(newHdrKey);
00274   
00275   if (m_mdbTable)
00276   {
00277     m_mdbTable->AddRow(m_mdbDB->GetEnv(), hdrRow);
00278     ChangeChildCount(1);
00279     if (! (newHdrFlags & MSG_FLAG_READ))
00280       ChangeUnreadChildCount(1);
00281   }
00282   if (inReplyTo)
00283   {
00284     nsMsgKey parentKey;
00285     inReplyTo->GetMessageKey(&parentKey);
00286     child->SetThreadParent(parentKey);
00287     parentKeyNeedsSetting = PR_FALSE;
00288   }
00289   // check if this header is a parent of one of the messages in this thread
00290   
00291   PRBool hdrMoved = PR_FALSE;
00292   nsCOMPtr <nsIMsgDBHdr> curHdr;
00293 
00294   // This is an ugly but simple fix for a difficult problem. Basically, when we add
00295   // a message to a thread, we have to run through the thread to see if the new
00296   // message is a parent of an existing message in the thread, and adjust things
00297   // accordingly. If you thread by subject, and you have a large folder with
00298   // messages w/ all the same subject, this code can take a really long time. So the
00299   // pragmatic thing is to say that for threads with more than 1000 messages, it's
00300   // simply not worth dealing with the case where the parent comes in after the
00301   // child. Threads with more than 1000 messages are pretty unwieldy anyway.
00302   // See Bug 90452
00303 
00304   if (numChildren < 1000)
00305   {
00306     for (childIndex = 0; childIndex < numChildren; childIndex++)
00307     {
00308       nsMsgKey msgKey;
00309     
00310       ret = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
00311       if (NS_SUCCEEDED(ret) && curHdr)
00312       {
00313         if (hdr->IsParentOf(curHdr))
00314         {
00315           nsMsgKey oldThreadParent;
00316           mdb_pos outPos;
00317           // move this hdr before the current header.
00318           if (!hdrMoved)
00319           {
00320             m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, childIndex, &outPos);
00321             hdrMoved = PR_TRUE;
00322             curHdr->GetThreadParent(&oldThreadParent);
00323             curHdr->GetMessageKey(&msgKey);
00324             nsCOMPtr <nsIMsgDBHdr> curParent;
00325             m_mdbDB->GetMsgHdrForKey(oldThreadParent, getter_AddRefs(curParent));
00326             if (curParent && hdr->IsAncestorOf(curParent))
00327             {
00328               nsMsgKey curParentKey;
00329               curParent->GetMessageKey(&curParentKey);
00330               if (curParentKey == m_threadRootKey)
00331               {
00332                 m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos);
00333                 RerootThread(child, curParent, announcer);
00334                 parentKeyNeedsSetting = PR_FALSE;
00335               }
00336             }
00337             else if (msgKey == m_threadRootKey)
00338             {
00339               RerootThread(child, curHdr, announcer);
00340               parentKeyNeedsSetting = PR_FALSE;
00341             }
00342           }
00343           curHdr->SetThreadParent(newHdrKey);
00344           if (msgKey == newHdrKey)
00345             parentKeyNeedsSetting = PR_FALSE;
00346         
00347           // OK, this is a reparenting - need to send notification
00348           if (announcer)
00349             announcer->NotifyParentChangedAll(msgKey, oldThreadParent, newHdrKey, nsnull);
00350   #ifdef DEBUG_bienvenu1
00351           if (newHdrKey != m_threadKey)
00352             printf("adding second level child\n");
00353   #endif
00354         }
00355       }
00356     }
00357   }
00358   // If this header is not a reply to a header in the thread, and isn't a parent
00359   // check to see if it starts with Re: - if not, and the first header does start
00360   // with re, should we make this header the top level header?
00361   // If it's date is less (or it's ID?), then yes.
00362   if (numChildren > 0 && !(newHdrFlags & MSG_FLAG_HAS_RE) && !inReplyTo)
00363   {
00364     PRTime newHdrDate;
00365     PRTime topLevelHdrDate;
00366     
00367     nsCOMPtr <nsIMsgDBHdr> topLevelHdr;
00368     ret = GetRootHdr(nsnull, getter_AddRefs(topLevelHdr));
00369     if (NS_SUCCEEDED(ret) && topLevelHdr)
00370     {
00371       child->GetDate(&newHdrDate);
00372       topLevelHdr->GetDate(&topLevelHdrDate);
00373       if (LL_CMP(newHdrDate, <, topLevelHdrDate))
00374       {
00375         RerootThread(child, topLevelHdr, announcer);
00376         mdb_pos outPos;
00377         m_mdbTable->MoveRow(m_mdbDB->GetEnv(), hdrRow, -1, 0, &outPos);
00378         topLevelHdr->SetThreadParent(newHdrKey);
00379         parentKeyNeedsSetting = PR_FALSE;
00380         // ### need to get ancestor of new hdr here too.
00381         SetThreadRootKey(newHdrKey);
00382         child->SetThreadParent(nsMsgKey_None);
00383         // argh, here we'd need to adjust all the headers that listed 
00384         // the demoted header as their thread parent, but only because
00385         // of subject threading. Adjust them to point to the new parent,
00386         // that is.
00387         ReparentNonReferenceChildrenOf(topLevelHdr, newHdrKey, announcer);
00388       }
00389     }
00390   }
00391   // OK, check to see if we added this header, and didn't parent it.
00392   
00393   if (numChildren > 0 && parentKeyNeedsSetting)
00394     child->SetThreadParent(m_threadRootKey);
00395   
00396   // do this after we've put the new hdr in the thread
00397   if (m_flags & MSG_FLAG_IGNORED && m_mdbDB)
00398     m_mdbDB->MarkHdrRead(child, PR_TRUE, nsnull);
00399   
00400 #ifdef DEBUG_bienvenu1
00401   nsMsgDatabase *msgDB = NS_STATIC_CAST(nsMsgDatabase*, m_mdbDB);
00402   msgDB->DumpThread(m_threadRootKey);
00403 #endif
00404   return ret;
00405 }
00406 
00407 nsresult nsMsgThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *oldTopLevelHdr, nsMsgKey newParentKey,
00408                                                             nsIDBChangeAnnouncer *announcer)
00409 {
00410   nsCOMPtr <nsIMsgDBHdr> curHdr;
00411   PRUint32 numChildren;
00412   PRUint32 childIndex = 0;
00413   
00414   GetNumChildren(&numChildren);
00415   for (childIndex = 0; childIndex < numChildren; childIndex++)
00416   {
00417     nsMsgKey oldTopLevelHdrKey;
00418     
00419     oldTopLevelHdr->GetMessageKey(&oldTopLevelHdrKey);
00420     nsresult ret = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
00421     if (NS_SUCCEEDED(ret) && curHdr)
00422     {
00423       nsMsgKey oldThreadParent, curHdrKey;
00424       nsMsgHdr* oldTopLevelMsgHdr = NS_STATIC_CAST(nsMsgHdr*, oldTopLevelHdr);      // closed system, cast ok
00425       curHdr->GetThreadParent(&oldThreadParent);
00426       curHdr->GetMessageKey(&curHdrKey);
00427       if (oldThreadParent == oldTopLevelHdrKey && curHdrKey != newParentKey && !oldTopLevelMsgHdr->IsParentOf(curHdr))
00428       {
00429         curHdr->GetThreadParent(&oldThreadParent);
00430         curHdr->SetThreadParent(newParentKey);
00431         // OK, this is a reparenting - need to send notification
00432         if (announcer)
00433           announcer->NotifyParentChangedAll(curHdrKey, oldThreadParent, newParentKey, nsnull);
00434       }
00435     }
00436   }
00437   return NS_OK;
00438 }
00439 
00440 NS_IMETHODIMP nsMsgThread::GetChildKeyAt(PRInt32 aIndex, nsMsgKey *result)
00441 {
00442   nsresult ret;
00443 
00444   mdbOid oid;
00445   ret = m_mdbTable->PosToOid( m_mdbDB->GetEnv(), aIndex, &oid);
00446   if (NS_SUCCEEDED(ret))
00447     *result = oid.mOid_Id;
00448 
00449   return ret;
00450 }
00451 
00452 NS_IMETHODIMP nsMsgThread::GetChildAt(PRInt32 aIndex, nsIMsgDBHdr **result)
00453 {
00454   nsresult ret;
00455 
00456   mdbOid oid;
00457   ret = m_mdbTable->PosToOid( m_mdbDB->GetEnv(), aIndex, &oid);
00458   if (NS_SUCCEEDED(ret))
00459   {
00460     nsIMdbRow *hdrRow = nsnull;
00461     //do I have to release hdrRow?
00462     ret = m_mdbTable->PosToRow(m_mdbDB->GetEnv(), aIndex, &hdrRow); 
00463     if(NS_SUCCEEDED(ret) && hdrRow)
00464     {
00465       ret = m_mdbDB->CreateMsgHdr(hdrRow,  oid.mOid_Id , result);
00466     }
00467   }
00468   
00469   return (NS_SUCCEEDED(ret)) ? NS_OK : NS_MSG_MESSAGE_NOT_FOUND;
00470 }
00471 
00472 
00473 NS_IMETHODIMP nsMsgThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **result)
00474 {
00475   nsresult ret;
00476 
00477   mdb_bool    hasOid;
00478   mdbOid             rowObjectId;
00479   
00480   if (!result || !m_mdbTable)
00481     return NS_ERROR_NULL_POINTER;
00482   
00483   *result = NULL;
00484   rowObjectId.mOid_Id = msgKey;
00485   rowObjectId.mOid_Scope = m_mdbDB->m_hdrRowScopeToken;
00486   ret = m_mdbTable->HasOid(m_mdbDB->GetEnv(), &rowObjectId, &hasOid);
00487   if (NS_SUCCEEDED(ret) && hasOid && m_mdbDB && m_mdbDB->m_mdbStore)
00488   {
00489     nsIMdbRow *hdrRow = nsnull;
00490     ret = m_mdbDB->m_mdbStore->GetRow(m_mdbDB->GetEnv(), &rowObjectId,  &hdrRow);
00491     if (NS_SUCCEEDED(ret) && hdrRow && m_mdbDB)
00492     {
00493       ret = m_mdbDB->CreateMsgHdr(hdrRow,  msgKey, result);
00494     }
00495   }
00496 
00497   return ret;
00498 }
00499 
00500 
00501 NS_IMETHODIMP nsMsgThread::GetChildHdrAt(PRInt32 aIndex, nsIMsgDBHdr **result)
00502 {
00503   nsresult ret;
00504 
00505   nsIMdbRow* resultRow;
00506   mdb_pos pos = aIndex - 1;
00507   
00508   if (!result)
00509     return NS_ERROR_NULL_POINTER;
00510   
00511   *result = nsnull;
00512   // mork doesn't seem to handle this correctly, so deal with going off
00513   // the end here.
00514   if (aIndex > (PRInt32) m_numChildren)
00515     return NS_OK;
00516   
00517   nsIMdbTableRowCursor *rowCursor;
00518   ret = m_mdbTable->GetTableRowCursor(m_mdbDB->GetEnv(), pos, &rowCursor);
00519   if (NS_FAILED(ret))
00520     return ret;
00521   
00522   ret = rowCursor->NextRow(m_mdbDB->GetEnv(), &resultRow, &pos);
00523   NS_RELEASE(rowCursor);
00524   if (NS_FAILED(ret) || !resultRow) 
00525     return ret;
00526   
00527   //Get key from row
00528   mdbOid outOid;
00529   nsMsgKey key=0;
00530   if (resultRow->GetOid(m_mdbDB->GetEnv(), &outOid) == NS_OK)
00531     key = outOid.mOid_Id;
00532   
00533   return m_mdbDB->CreateMsgHdr(resultRow, key, result);
00534 }
00535 
00536 
00537 NS_IMETHODIMP nsMsgThread::RemoveChildAt(PRInt32 aIndex)
00538 {
00539   return NS_OK;
00540 }
00541 
00542 
00543 nsresult nsMsgThread::RemoveChild(nsMsgKey msgKey)
00544 {
00545   nsresult ret;
00546 
00547   mdbOid             rowObjectId;
00548   rowObjectId.mOid_Id = msgKey;
00549   rowObjectId.mOid_Scope = m_mdbDB->m_hdrRowScopeToken;
00550   ret = m_mdbTable->CutOid(m_mdbDB->GetEnv(), &rowObjectId);
00551   // if this thread is empty, remove it from the all threads table.
00552   if (m_numChildren == 0 && m_mdbDB->m_mdbAllThreadsTable)
00553   {
00554     mdbOid rowID;
00555     rowID.mOid_Id = m_threadKey;
00556     rowID.mOid_Scope = m_mdbDB->m_threadRowScopeToken;
00557     
00558     m_mdbDB->m_mdbAllThreadsTable->CutOid(m_mdbDB->GetEnv(), &rowID);
00559   }
00560 #if 0 // this seems to cause problems
00561   if (m_numChildren == 0 && m_metaRow && m_mdbDB)
00562     m_metaRow->CutAllColumns(m_mdbDB->GetEnv());
00563 #endif
00564   
00565   return ret;
00566 }
00567 
00568 NS_IMETHODIMP nsMsgThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer)
00569 {
00570   PRUint32 flags;
00571   nsMsgKey key;
00572   nsMsgKey threadParent;
00573   
00574   if (!child)
00575     return NS_ERROR_NULL_POINTER;
00576   
00577   child->GetFlags(&flags);
00578   child->GetMessageKey(&key);
00579   
00580   child->GetThreadParent(&threadParent);
00581   ReparentChildrenOf(key, threadParent, announcer);
00582   
00583   // if this was the newest msg, clear the newest msg date so we'll recalc.
00584   PRUint32 date;
00585   child->GetDateInSeconds(&date);
00586   if (date == m_newestMsgDate)
00587     SetNewestMsgDate(0);
00588 
00589  if (!(flags & MSG_FLAG_READ))
00590     ChangeUnreadChildCount(-1);
00591   ChangeChildCount(-1);
00592   return RemoveChild(key);
00593 }
00594 
00595 nsresult nsMsgThread::ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer)
00596 {
00597   nsresult rv = NS_OK;
00598   
00599   PRUint32 numChildren;
00600   PRUint32 childIndex = 0;
00601   
00602   GetNumChildren(&numChildren);
00603   
00604   nsCOMPtr <nsIMsgDBHdr> curHdr;
00605   if (numChildren > 0)
00606   {
00607     for (childIndex = 0; childIndex < numChildren; childIndex++)
00608     {
00609       rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
00610       if (NS_SUCCEEDED(rv) && curHdr)
00611       {
00612         nsMsgKey threadParent;
00613         
00614         curHdr->GetThreadParent(&threadParent);
00615         if (threadParent == oldParent)
00616         {
00617           nsMsgKey curKey;
00618           
00619           curHdr->SetThreadParent(newParent);
00620           curHdr->GetMessageKey(&curKey);
00621           if (announcer)
00622             announcer->NotifyParentChangedAll(curKey, oldParent, newParent, nsnull);
00623           // if the old parent was the root of the thread, then only the first child gets 
00624           // promoted to root, and other children become children of the new root.
00625           if (newParent == nsMsgKey_None)
00626           {
00627             SetThreadRootKey(curKey);
00628             newParent = curKey;
00629           }
00630         }
00631       }
00632     }
00633   }
00634   return rv;
00635 }
00636 
00637 NS_IMETHODIMP nsMsgThread::MarkChildRead(PRBool bRead)
00638 {
00639   ChangeUnreadChildCount(bRead ? -1 : 1);
00640   return NS_OK;
00641 }
00642 
00643 class nsMsgThreadEnumerator : public nsISimpleEnumerator {
00644 public:
00645   NS_DECL_ISUPPORTS
00646     
00647   // nsISimpleEnumerator methods:
00648   NS_DECL_NSISIMPLEENUMERATOR
00649     
00650   // nsMsgThreadEnumerator methods:
00651   typedef nsresult (*nsMsgThreadEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
00652   
00653   nsMsgThreadEnumerator(nsMsgThread *thread, nsMsgKey startKey,
00654   nsMsgThreadEnumeratorFilter filter, void* closure);
00655   PRInt32 MsgKeyFirstChildIndex(nsMsgKey inMsgKey);
00656   virtual ~nsMsgThreadEnumerator();
00657   
00658 protected:
00659   
00660   nsresult                Prefetch();
00661   
00662   nsIMdbTableRowCursor*   mRowCursor;
00663   nsCOMPtr <nsIMsgDBHdr>  mResultHdr;
00664   nsMsgThread*                 mThread;
00665   nsMsgKey                mThreadParentKey;
00666   nsMsgKey                mFirstMsgKey;
00667   PRInt32                 mChildIndex;
00668   PRBool                  mDone;
00669   PRBool                  mNeedToPrefetch;
00670   nsMsgThreadEnumeratorFilter     mFilter;
00671   void*                   mClosure;
00672   PRBool                  mFoundChildren;
00673 };
00674 
00675 nsMsgThreadEnumerator::nsMsgThreadEnumerator(nsMsgThread *thread, nsMsgKey startKey,
00676                                              nsMsgThreadEnumeratorFilter filter, void* closure)
00677                                              : mRowCursor(nsnull), mDone(PR_FALSE),
00678                                              mFilter(filter), mClosure(closure), mFoundChildren(PR_FALSE)
00679 {
00680   mThreadParentKey = startKey;
00681   mChildIndex = 0;
00682   mThread = thread;
00683   mNeedToPrefetch = PR_TRUE;
00684   mFirstMsgKey = nsMsgKey_None;
00685   
00686   nsresult rv = mThread->GetRootHdr(nsnull, getter_AddRefs(mResultHdr));
00687   
00688   if (NS_SUCCEEDED(rv) && mResultHdr)
00689     mResultHdr->GetMessageKey(&mFirstMsgKey);
00690   
00691   PRUint32 numChildren;
00692   mThread->GetNumChildren(&numChildren);
00693   
00694   if (mThreadParentKey != nsMsgKey_None)
00695   {
00696     nsMsgKey msgKey = nsMsgKey_None;
00697     PRUint32 childIndex = 0;
00698     
00699     
00700     for (childIndex = 0; childIndex < numChildren; childIndex++)
00701     {
00702       rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(mResultHdr));
00703       if (NS_SUCCEEDED(rv) && mResultHdr)
00704       {
00705         mResultHdr->GetMessageKey(&msgKey);
00706         
00707         if (msgKey == startKey)
00708         {
00709           mChildIndex = MsgKeyFirstChildIndex(msgKey);
00710           mDone = (mChildIndex < 0);
00711           break;
00712         }
00713         
00714         if (mDone)
00715           break;
00716         
00717       }
00718       else
00719         NS_ASSERTION(PR_FALSE, "couldn't get child from thread");
00720     }
00721   }
00722   
00723 #ifdef DEBUG_bienvenu1
00724   nsCOMPtr <nsIMsgDBHdr> child;
00725   for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++)
00726   {
00727     rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(child));
00728     if (NS_SUCCEEDED(rv) && child)
00729     {
00730       nsMsgKey threadParent;
00731       nsMsgKey msgKey;
00732       // we're only doing one level of threading, so check if caller is
00733       // asking for children of the first message in the thread or not.
00734       // if not, we will tell him there are no children.
00735       child->GetMessageKey(&msgKey);
00736       child->GetThreadParent(&threadParent);
00737       
00738       printf("index = %ld key = %ld parent = %lx\n", childIndex, msgKey, threadParent);
00739     }
00740   }
00741 #endif
00742   NS_ADDREF(thread);
00743 }
00744 
00745 nsMsgThreadEnumerator::~nsMsgThreadEnumerator()
00746 {
00747     NS_RELEASE(mThread);
00748 }
00749 
00750 NS_IMPL_ISUPPORTS1(nsMsgThreadEnumerator, nsISimpleEnumerator)
00751 
00752 
00753 PRInt32 nsMsgThreadEnumerator::MsgKeyFirstChildIndex(nsMsgKey inMsgKey)
00754 {
00755   //   if (msgKey != mThreadParentKey)
00756   //          mDone = PR_TRUE;
00757   // look through rest of thread looking for a child of this message.
00758   // If the inMsgKey is the first message in the thread, then all children
00759   // without parents are considered to be children of inMsgKey.
00760   // Otherwise, only true children qualify.
00761   PRUint32 numChildren;
00762   nsCOMPtr <nsIMsgDBHdr> curHdr;
00763   PRInt32 firstChildIndex = -1;
00764   
00765   mThread->GetNumChildren(&numChildren);
00766   
00767   // if this is the first message in the thread, just check if there's more than
00768   // one message in the thread.
00769   //   if (inMsgKey == mThread->m_threadRootKey)
00770   //          return (numChildren > 1) ? 1 : -1;
00771   
00772   for (PRUint32 curChildIndex = 0; curChildIndex < numChildren; curChildIndex++)
00773   {
00774     nsresult rv = mThread->GetChildHdrAt(curChildIndex, getter_AddRefs(curHdr));
00775     if (NS_SUCCEEDED(rv) && curHdr)
00776     {
00777       nsMsgKey parentKey;
00778       
00779       curHdr->GetThreadParent(&parentKey);
00780       if (parentKey == inMsgKey)
00781       {
00782         firstChildIndex = curChildIndex;
00783         break;
00784       }
00785     }
00786   }
00787 #ifdef DEBUG_bienvenu1
00788   printf("first child index of %ld = %ld\n", inMsgKey, firstChildIndex);
00789 #endif
00790   return firstChildIndex;
00791 }
00792 
00793 NS_IMETHODIMP nsMsgThreadEnumerator::GetNext(nsISupports **aItem)
00794 {
00795   if (!aItem)
00796     return NS_ERROR_NULL_POINTER;
00797   nsresult rv = NS_OK;
00798   
00799   if (mNeedToPrefetch)
00800     rv = Prefetch();
00801   
00802   if (NS_SUCCEEDED(rv) && mResultHdr) 
00803   {
00804     *aItem = mResultHdr;
00805     NS_ADDREF(*aItem);
00806     mNeedToPrefetch = PR_TRUE;
00807   }
00808   return rv;
00809 }
00810 
00811 nsresult nsMsgThreadEnumerator::Prefetch()
00812 {
00813   nsresult rv=NS_OK;          // XXX or should this default to an error?
00814   mResultHdr = nsnull;
00815   if (mThreadParentKey == nsMsgKey_None)
00816   {
00817     rv = mThread->GetRootHdr(&mChildIndex, getter_AddRefs(mResultHdr));
00818     NS_ASSERTION(NS_SUCCEEDED(rv) && mResultHdr, "better be able to get root hdr");
00819     mChildIndex = 0; // since root can be anywhere, set mChildIndex to 0.
00820   }
00821   else if (!mDone)
00822   {
00823     PRUint32 numChildren;
00824     mThread->GetNumChildren(&numChildren);
00825     
00826     while (mChildIndex < (PRInt32) numChildren)
00827     {
00828       rv  = mThread->GetChildHdrAt(mChildIndex++, getter_AddRefs(mResultHdr));
00829       if (NS_SUCCEEDED(rv) && mResultHdr)
00830       {
00831         nsMsgKey parentKey;
00832         nsMsgKey curKey;
00833         
00834         if (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure))) {
00835           mResultHdr = nsnull;
00836           continue;
00837         }
00838         
00839         mResultHdr->GetThreadParent(&parentKey);
00840         mResultHdr->GetMessageKey(&curKey);
00841         // if the parent is the same as the msg we're enumerating over,
00842         // or the parentKey isn't set, and we're iterating over the top
00843         // level message in the thread, then leave mResultHdr set to cur msg.
00844         if (parentKey == mThreadParentKey || 
00845           (parentKey == nsMsgKey_None 
00846           && mThreadParentKey == mFirstMsgKey && curKey != mThreadParentKey))
00847           break;
00848         mResultHdr = nsnull;
00849       }
00850       else
00851         NS_ASSERTION(PR_FALSE, "better be able to get child");
00852     }
00853     if (!mResultHdr && mThreadParentKey == mFirstMsgKey && !mFoundChildren && numChildren > 1)
00854     {
00855       mThread->ReparentMsgsWithInvalidParent(numChildren, mThreadParentKey);
00856     }
00857   }
00858   if (!mResultHdr) 
00859   {
00860     mDone = PR_TRUE;
00861     return NS_ERROR_FAILURE;
00862   }
00863   if (NS_FAILED(rv)) 
00864   {
00865     mDone = PR_TRUE;
00866     return rv;
00867   }
00868   else
00869     mNeedToPrefetch = PR_FALSE;
00870   mFoundChildren = PR_TRUE;
00871 
00872 #ifdef DEBUG_bienvenu1
00873        nsMsgKey debugMsgKey;
00874        mResultHdr->GetMessageKey(&debugMsgKey);
00875        printf("next for %ld = %ld\n", mThreadParentKey, debugMsgKey);
00876 #endif
00877 
00878     return rv;
00879 }
00880 
00881 NS_IMETHODIMP nsMsgThreadEnumerator::HasMoreElements(PRBool *aResult)
00882 {
00883   if (!aResult)
00884     return NS_ERROR_NULL_POINTER;
00885   if (mNeedToPrefetch)
00886     Prefetch();
00887   *aResult = !mDone;
00888   return NS_OK;
00889 }
00890 
00891 NS_IMETHODIMP nsMsgThread::EnumerateMessages(nsMsgKey parentKey, nsISimpleEnumerator* *result)
00892 {
00893     nsMsgThreadEnumerator* e = new nsMsgThreadEnumerator(this, parentKey, nsnull, nsnull);
00894     if (e == nsnull)
00895         return NS_ERROR_OUT_OF_MEMORY;
00896     NS_ADDREF(e);
00897     *result = e;
00898 
00899     return NS_OK;
00900 }
00901 
00902 nsresult nsMsgThread::ReparentMsgsWithInvalidParent(PRUint32 numChildren, nsMsgKey threadParentKey)
00903 {
00904   nsresult ret = NS_OK;
00905   // run through looking for messages that don't have a correct parent, 
00906   // i.e., a parent that's in the thread!
00907   for (PRInt32 childIndex = 0; childIndex < (PRInt32) numChildren; childIndex++)
00908   {
00909     nsCOMPtr <nsIMsgDBHdr> curChild;
00910     ret  = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
00911     if (NS_SUCCEEDED(ret) && curChild)
00912     {
00913       nsMsgKey parentKey;
00914       nsCOMPtr <nsIMsgDBHdr> parent;
00915       
00916       curChild->GetThreadParent(&parentKey);
00917       
00918       if (parentKey != nsMsgKey_None)
00919       {
00920         GetChild(parentKey, getter_AddRefs(parent));
00921         if (!parent)
00922           curChild->SetThreadParent(threadParentKey);
00923       }
00924     }
00925   }
00926   return ret;
00927 }
00928 
00929 NS_IMETHODIMP nsMsgThread::GetRootHdr(PRInt32 *resultIndex, nsIMsgDBHdr **result)
00930 {
00931   if (!result)
00932     return NS_ERROR_NULL_POINTER;
00933   
00934   *result = nsnull;
00935   
00936   if (m_threadRootKey != nsMsgKey_None)
00937   {
00938     nsresult ret = GetChildHdrForKey(m_threadRootKey, result, resultIndex);
00939     if (NS_SUCCEEDED(ret) && *result)
00940       return ret;
00941     else
00942     {
00943 #ifdef DEBUG_bienvenu
00944       printf("need to reset thread root key\n");
00945 #endif
00946       PRUint32 numChildren;
00947       nsMsgKey threadParentKey = nsMsgKey_None;
00948       GetNumChildren(&numChildren);
00949       
00950       for (PRInt32 childIndex = 0; childIndex < (PRInt32) numChildren; childIndex++)
00951       {
00952         nsCOMPtr <nsIMsgDBHdr> curChild;
00953         ret  = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
00954         if (NS_SUCCEEDED(ret) && curChild)
00955         {
00956           nsMsgKey parentKey;
00957           
00958           curChild->GetThreadParent(&parentKey);
00959           if (parentKey == nsMsgKey_None)
00960           {
00961             NS_ASSERTION(!(*result), "two top level msgs, not good");
00962             curChild->GetMessageKey(&threadParentKey);
00963             SetThreadRootKey(threadParentKey);
00964             if (resultIndex)
00965               *resultIndex = childIndex;
00966             *result = curChild;
00967             NS_ADDREF(*result);
00968             ReparentMsgsWithInvalidParent(numChildren, threadParentKey);
00969             //            return NS_OK;
00970           }
00971         }
00972       }
00973       if (*result)
00974       {
00975         return NS_OK;
00976       }
00977     }
00978     // if we can't get the thread root key, we'll just get the first hdr.
00979     // there's a bug where sometimes we weren't resetting the thread root key 
00980     // when removing the thread root key.
00981   }
00982   if (resultIndex)
00983     *resultIndex = 0;
00984   return GetChildHdrAt(0, result);
00985 }
00986 
00987 nsresult nsMsgThread::ChangeChildCount(PRInt32 delta)
00988 {
00989   nsresult ret;
00990 
00991   PRUint32 childCount = 0;
00992   m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, childCount);
00993   
00994   NS_ASSERTION(childCount != 0 || delta > 0, "child count gone negative");
00995   childCount += delta;
00996   
00997   NS_ASSERTION((PRInt32) childCount >= 0, "child count gone to 0 or below");
00998   if ((PRInt32) childCount < 0)    // force child count to >= 0
00999     childCount = 0;
01000   
01001   ret = m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadChildrenColumnToken, childCount);
01002   m_numChildren = childCount;
01003   return ret;
01004 }
01005 
01006 nsresult nsMsgThread::ChangeUnreadChildCount(PRInt32 delta)
01007 {
01008   nsresult ret;
01009 
01010   PRUint32 childCount = 0;
01011   m_mdbDB->RowCellColumnToUInt32(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, childCount);
01012   childCount += delta;
01013   if ((PRInt32) childCount < 0)
01014   {
01015 #ifdef DEBUG_bienvenu1
01016     NS_ASSERTION(PR_FALSE, "negative unread child count");
01017 #endif
01018     childCount = 0;
01019   }
01020   ret = m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadUnreadChildrenColumnToken, childCount);
01021   m_numUnreadChildren = childCount;
01022   return ret;
01023 }
01024 
01025 nsresult nsMsgThread::SetThreadRootKey(nsMsgKey threadRootKey)
01026 {
01027   m_threadRootKey = threadRootKey;
01028   return m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadRootKeyColumnToken, threadRootKey);
01029 }
01030 
01031 nsresult nsMsgThread::GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, PRInt32 *resultIndex)
01032 {
01033   PRUint32 numChildren;
01034   PRUint32 childIndex = 0;
01035   nsresult rv = NS_OK;        // XXX or should this default to an error?
01036   
01037   if (!result)
01038     return NS_ERROR_NULL_POINTER;
01039   
01040   GetNumChildren(&numChildren);
01041   
01042   if ((PRInt32) numChildren < 0)
01043     numChildren = 0;
01044   
01045   for (childIndex = 0; childIndex < numChildren; childIndex++)
01046   {
01047     rv = GetChildHdrAt(childIndex, result);
01048     if (NS_SUCCEEDED(rv) && *result)
01049     {
01050       nsMsgKey msgKey;
01051       // we're only doing one level of threading, so check if caller is
01052       // asking for children of the first message in the thread or not.
01053       // if not, we will tell him there are no children.
01054       (*result)->GetMessageKey(&msgKey);
01055       
01056       if (msgKey == desiredKey)
01057       {
01058         nsMsgKey threadKey;
01059         (*result)->GetThreadId(&threadKey);
01060         if (threadKey != m_threadKey) // this msg isn't in this thread
01061         {
01062           PRUint32 msgSize;
01063           (*result)->GetMessageSize(&msgSize);
01064           if (msgSize == 0) // this is a phantom message - let's get rid of it.
01065             RemoveChild(msgKey);
01066           rv = NS_ERROR_UNEXPECTED;
01067         }
01068         break;
01069       }
01070       NS_RELEASE(*result);
01071     }
01072   }
01073   if (resultIndex)
01074     *resultIndex = childIndex;
01075   
01076   return rv;
01077 }
01078 
01079 NS_IMETHODIMP nsMsgThread::GetFirstUnreadChild(nsIMsgDBHdr **result)
01080 {
01081   NS_ENSURE_ARG(result);
01082   PRUint32 numChildren;
01083   nsresult rv = NS_OK;
01084   
01085   GetNumChildren(&numChildren);
01086   
01087   if ((PRInt32) numChildren < 0)
01088     numChildren = 0;
01089   
01090   for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++)
01091   {
01092     nsCOMPtr <nsIMsgDBHdr> child;
01093     rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
01094     if (NS_SUCCEEDED(rv) && child)
01095     {
01096       nsMsgKey msgKey;
01097       child->GetMessageKey(&msgKey);
01098       
01099       PRBool isRead;
01100       rv = m_mdbDB->IsRead(msgKey, &isRead);
01101       if (NS_SUCCEEDED(rv) && !isRead)
01102       {
01103         *result = child;
01104         NS_ADDREF(*result);
01105         break;
01106       }
01107     }
01108   }
01109   
01110   return rv;
01111 }
01112 
01113 NS_IMETHODIMP nsMsgThread::GetNewestMsgDate(PRUint32 *aResult) 
01114 {
01115   // if this hasn't been set, figure it out by enumerating the msgs in the thread.
01116   if (!m_newestMsgDate)
01117   {
01118     PRUint32 numChildren;
01119     nsresult rv = NS_OK;
01120   
01121     GetNumChildren(&numChildren);
01122   
01123     if ((PRInt32) numChildren < 0)
01124       numChildren = 0;
01125   
01126     for (PRUint32 childIndex = 0; childIndex < numChildren; childIndex++)
01127     {
01128       nsCOMPtr <nsIMsgDBHdr> child;
01129       rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
01130       if (NS_SUCCEEDED(rv) && child)
01131       {
01132         PRUint32 msgDate;
01133         child->GetDateInSeconds(&msgDate);
01134         if (msgDate > m_newestMsgDate)
01135           m_newestMsgDate = msgDate;
01136       }
01137     }
01138   
01139   }
01140   *aResult = m_newestMsgDate;
01141   return NS_OK;
01142 }
01143 
01144 
01145 NS_IMETHODIMP nsMsgThread::SetNewestMsgDate(PRUint32 aNewestMsgDate) 
01146 {
01147   m_newestMsgDate = aNewestMsgDate;
01148   return m_mdbDB->UInt32ToRowCellColumn(m_metaRow, m_mdbDB->m_threadNewestMsgDateColumnToken, aNewestMsgDate);
01149 }
01150