Back to index

lightning-sunbird  0.9+nobinonly
nsMsgXFVirtualFolderDBView.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  * David Bienvenu.
00019  * Portions created by the Initial Developer are Copyright (C) 2004
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   David Bienvenu <bienvenu@nventure.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 "nsMsgXFVirtualFolderDBView.h"
00041 #include "nsIMsgHdr.h"
00042 #include "nsIMsgThread.h"
00043 #include "nsQuickSort.h"
00044 #include "nsIDBFolderInfo.h"
00045 #include "nsXPIDLString.h"
00046 #include "nsMsgBaseCID.h"
00047 #include "nsIMsgCopyService.h"
00048 #include "nsICopyMsgStreamListener.h"
00049 #include "nsMsgUtils.h"
00050 #include "nsITreeColumns.h"
00051 #include "nsIMsgSearchSession.h"
00052 #include "nsMsgDBCID.h"
00053 
00054 nsMsgXFVirtualFolderDBView::nsMsgXFVirtualFolderDBView()
00055 {
00056   mSuppressMsgDisplay = PR_FALSE;
00057   m_doingSearch = PR_FALSE;
00058 }
00059 
00060 nsMsgXFVirtualFolderDBView::~nsMsgXFVirtualFolderDBView()
00061 {      
00062 }
00063 
00064 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount)
00065 {
00066   m_viewFolder = folder;
00067   return nsMsgSearchDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
00068 }
00069 
00070 void nsMsgXFVirtualFolderDBView::RemovePendingDBListeners()
00071 {
00072   nsresult rv;
00073   nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
00074   // UnregisterPendingListener will return an error when there are no more instances
00075   // of this object registered as pending listeners.
00076   while (NS_SUCCEEDED(rv))
00077     rv = msgDBService->UnregisterPendingListener(this);
00078 }
00079 
00080 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Close()
00081 {
00082   RemovePendingDBListeners();
00083   return NS_OK;
00084 }
00085 
00086 NS_IMETHODIMP
00087 nsMsgXFVirtualFolderDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, 
00088                                         nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval)
00089 {
00090   nsMsgXFVirtualFolderDBView* newMsgDBView;
00091   NS_NEWXPCOM(newMsgDBView, nsMsgXFVirtualFolderDBView);
00092 
00093   if (!newMsgDBView)
00094     return NS_ERROR_OUT_OF_MEMORY;
00095 
00096   nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
00097   NS_ENSURE_SUCCESS(rv,rv);
00098 
00099   NS_IF_ADDREF(*_retval = newMsgDBView);
00100   return NS_OK;
00101 }
00102 
00103 NS_IMETHODIMP
00104 nsMsgXFVirtualFolderDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, 
00105                                        nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater)
00106 {
00107   nsMsgSearchDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
00108 
00109   nsMsgXFVirtualFolderDBView* newMsgDBView = (nsMsgXFVirtualFolderDBView *) aNewMsgDBView;
00110 
00111   newMsgDBView->m_viewFolder = m_viewFolder;
00112   newMsgDBView->m_searchSession = m_searchSession;
00113 
00114   PRInt32 scopeCount;
00115   nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
00116   nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID);
00117   searchSession->CountSearchScopes(&scopeCount);
00118   for (PRInt32 i = 0; i < scopeCount; i++)
00119   {
00120     nsMsgSearchScopeValue scopeId;
00121     nsCOMPtr<nsIMsgFolder> searchFolder;
00122     searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
00123     if (searchFolder)
00124     {
00125       nsCOMPtr<nsIMsgDatabase> searchDB;
00126       nsXPIDLCString searchUri;
00127       m_viewFolder->GetURI(getter_Copies(searchUri));
00128       nsresult rv = searchFolder->GetMsgDatabase(nsnull, getter_AddRefs(searchDB));
00129       if (NS_SUCCEEDED(rv) && searchDB)
00130       {
00131         if (msgDBService)
00132           msgDBService->RegisterPendingListener(searchFolder, newMsgDBView);
00133       }
00134     }
00135   }
00136 
00137   return NS_OK;
00138 }
00139 
00140 
00141 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue *aViewType)
00142 {
00143     NS_ENSURE_ARG_POINTER(aViewType);
00144     *aViewType = nsMsgViewType::eShowVirtualFolderResults; 
00145     return NS_OK;
00146 }
00147 
00148 NS_IMETHODIMP
00149 nsMsgXFVirtualFolderDBView::SetSearchSession(nsIMsgSearchSession *aSession)
00150 {
00151   m_searchSession = do_GetWeakReference(aSession);
00152   return NS_OK;
00153 }
00154 
00155 nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/)
00156 {
00157   if (newHdr)
00158   {
00159     PRBool match=PR_FALSE;
00160     nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
00161     if (searchSession)
00162       searchSession->MatchHdr(newHdr, m_db, &match);
00163     if (match)
00164     {
00165       nsCOMPtr <nsIMsgFolder> folder;
00166       newHdr->GetFolder(getter_AddRefs(folder));
00167       PRBool saveDoingSearch = m_doingSearch;
00168       m_doingSearch = PR_FALSE;
00169       OnSearchHit(newHdr, folder); 
00170       m_doingSearch = saveDoingSearch;
00171     }
00172   }
00173   return NS_OK;
00174 }
00175 
00176 nsresult nsMsgXFVirtualFolderDBView::InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsISupports *folder)
00177 {
00178   nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
00179   if (insertIndex == nsMsgViewIndex_None)
00180     return AddHdrFromFolder(msgHdr, folder);
00181 
00182   nsMsgKey msgKey;
00183   PRUint32 msgFlags;
00184   msgHdr->GetMessageKey(&msgKey);
00185   msgHdr->GetFlags(&msgFlags);
00186   m_keys.InsertAt(insertIndex, msgKey);
00187   m_flags.InsertAt(insertIndex, msgFlags);
00188   m_folders->InsertElementAt(folder, insertIndex);
00189   m_levels.InsertAt((PRInt32) insertIndex, (PRUint8) 0);
00190     
00191   // the call to NoteChange() has to happen after we add the key
00192   // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
00193   NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
00194   return NS_OK;
00195 }
00196 
00197 void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, PRUint32 numNewHits)
00198 {
00199   nsCOMPtr <nsIMsgDatabase> db;
00200   nsresult rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(db));
00201   if (NS_SUCCEEDED(rv) && db)
00202   {
00203     nsXPIDLCString searchUri;
00204     m_viewFolder->GetURI(getter_Copies(searchUri));
00205     PRUint32 numBadHits;
00206     nsMsgKey *badHits;
00207     rv = db->RefreshCache(searchUri, numNewHits, newHits,
00208                      &numBadHits, &badHits);
00209     if (NS_SUCCEEDED(rv))
00210     {
00211       for (PRUint32 badHitIndex = 0; badHitIndex < numBadHits; badHitIndex++)
00212       {
00213         // of course, this isn't quite right
00214         nsMsgViewIndex staleHitIndex = FindKey(badHits[badHitIndex], PR_TRUE);
00215         if (staleHitIndex != nsMsgViewIndex_None)
00216           RemoveByIndex(staleHitIndex);
00217       }
00218       delete [] badHits;
00219     }
00220   }
00221 }
00222 
00223 void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder)
00224 {
00225   // Handle the most recent folder with hits, if any.
00226 #ifdef DEBUG
00227   if (curSearchFolder)
00228   {
00229     nsXPIDLCString folderUri;
00230     curSearchFolder->GetURI(getter_Copies(folderUri));
00231     printf("UpdateCacheAndViewForPrevSearchedFolders curSearchFolder - %s\n", folderUri.get());
00232   }
00233 #endif
00234   if (m_curFolderGettingHits)
00235   {
00236     PRUint32 count = m_hdrHits.Count();
00237     nsMsgKeyArray newHits;
00238     for (PRUint32 i = 0; i < count; i++)
00239     {
00240       nsMsgKey key;
00241       m_hdrHits[i]->GetMessageKey(&key);
00242       newHits.Add(key);
00243     }
00244     newHits.QuickSort();
00245     UpdateCacheAndViewForFolder(m_curFolderGettingHits, newHits.GetArray(), newHits.GetSize());
00246   }
00247 
00248   while (m_foldersSearchingOver.Count() > 0)
00249   {
00250     // this new folder has cached hits.
00251     if (m_foldersSearchingOver[0] == curSearchFolder)
00252     {
00253       m_curFolderHasCachedHits = PR_TRUE;
00254       m_foldersSearchingOver.RemoveObjectAt(0);
00255       break;
00256     }
00257     else if (m_foldersSearchingOver[0] != m_curFolderGettingHits)
00258     {
00259       // this must be a folder that had no hits with the current search.
00260       // So all cached hits, if any, need to be removed. 
00261 #ifdef DEBUG
00262       nsXPIDLCString folderUri;
00263       m_foldersSearchingOver[0]->GetURI(getter_Copies(folderUri));
00264       printf("UpdateCacheAndViewForPrevSearchedFolders 0 hits in - %s\n", folderUri.get());
00265 #endif
00266       UpdateCacheAndViewForFolder(m_foldersSearchingOver[0], 0, nsnull);
00267 #ifdef DEBUG
00268       m_foldersSearchingOver[0]->GetURI(getter_Copies(folderUri));
00269       printf("UpdateCacheAndViewForPrevSearchedFolders removing %s\n", folderUri.get());
00270 #endif
00271       m_foldersSearchingOver.RemoveObjectAt(0);
00272     }
00273   }
00274 }
00275 NS_IMETHODIMP
00276 nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
00277 {
00278   NS_ENSURE_ARG(aMsgHdr);
00279   NS_ENSURE_ARG(folder);
00280 
00281   nsCOMPtr <nsISupports> supports = do_QueryInterface(folder);
00282   nsCOMPtr<nsIMsgDatabase> dbToUse;
00283   nsCOMPtr<nsIDBFolderInfo> folderInfo;
00284   folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse));
00285   
00286   if (m_curFolderGettingHits != folder && m_doingSearch)
00287   {
00288 #ifdef DEBUG
00289     nsXPIDLCString folderUri;
00290     folder->GetURI(getter_Copies(folderUri));
00291     printf("first hit for folder - %s\n", folderUri.get());
00292 #endif
00293     m_curFolderHasCachedHits = PR_FALSE;
00294     // since we've gotten a hit for a new folder, the searches for 
00295     // any previous folders are done, so deal with stale cached hits
00296     // for those folders now.
00297     UpdateCacheAndViewForPrevSearchedFolders(folder);
00298     m_curFolderGettingHits = folder;
00299     m_hdrHits.Clear();
00300     m_curFolderStartKeyIndex = m_keys.GetSize();
00301   }
00302   PRBool hdrInCache = PR_FALSE;
00303   nsXPIDLCString searchUri;
00304   m_viewFolder->GetURI(getter_Copies(searchUri));
00305   dbToUse->HdrIsInCache(searchUri, aMsgHdr, &hdrInCache);
00306   if (!m_doingSearch || !m_curFolderHasCachedHits || !hdrInCache)
00307   {
00308     if (m_sortValid)
00309       InsertHdrFromFolder(aMsgHdr, supports);
00310     else
00311       AddHdrFromFolder(aMsgHdr, supports);
00312   }
00313   m_hdrHits.AppendObject(aMsgHdr);
00314 
00315   return NS_OK;
00316 }
00317 
00318 NS_IMETHODIMP
00319 nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status)
00320 {
00321   // handle any non verified hits we haven't handled yet.
00322   UpdateCacheAndViewForPrevSearchedFolders(nsnull);
00323 
00324   m_doingSearch = PR_FALSE;
00325   //we want to set imap delete model once the search is over because setting next
00326   //message after deletion will happen before deleting the message and search scope
00327   //can change with every search.
00328   mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;  //set to default in case it is non-imap folder
00329   nsCOMPtr <nsIMsgFolder> curFolder = do_QueryElementAt(m_folders, 0);
00330   if (curFolder)   
00331     GetImapDeleteModel(curFolder);
00332   nsCOMPtr <nsIMsgDatabase> virtDatabase;
00333   nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00334 
00335   nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
00336   NS_ENSURE_SUCCESS(rv, rv);
00337   // count up the number of unread and total messages from the view, and set those in the
00338   // folder - easier than trying to keep the count up to date in the face of
00339   // search hits coming in while the user is reading/deleting messages.
00340   PRInt32 numUnread = 0;
00341   for (PRInt32 i = 0; i < m_flags.GetSize(); i++)
00342     if (!(m_flags[i] & MSG_FLAG_READ))
00343       numUnread++;
00344   dbFolderInfo->SetNumUnreadMessages(numUnread);
00345   dbFolderInfo->SetNumMessages(GetSize());
00346   m_viewFolder->UpdateSummaryTotals(true); // force update from db.
00347   virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
00348   if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread)
00349   {
00350     m_sortValid = PR_FALSE;       //sort the results 
00351     Sort(m_sortType, m_sortOrder);
00352   }
00353   m_foldersSearchingOver.Clear();
00354   m_curFolderGettingHits = nsnull;
00355   return rv;
00356 }
00357 
00358 
00359 NS_IMETHODIMP
00360 nsMsgXFVirtualFolderDBView::OnNewSearch()
00361 {
00362   PRInt32 oldSize = GetSize();
00363 
00364   RemovePendingDBListeners();
00365 
00366   m_doingSearch = PR_TRUE;
00367 
00368   m_folders->Clear();
00369   m_keys.RemoveAll();
00370   m_levels.RemoveAll();
00371   m_flags.RemoveAll();
00372 
00373   // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount()
00374   if (mTree) 
00375     mTree->RowCountChanged(0, -oldSize);
00376 
00377   // to use the search results cache, we'll need to iterate over the scopes in the
00378   // search session, calling getNthSearchScope for i = 0; i < searchSession.countSearchScopes; i++
00379   // and for each folder, then open the db and pull out the cached hits, add them to the view.
00380   // For each hit in a new folder, we'll then clean up the stale hits from the previous folder(s).
00381   
00382   PRInt32 scopeCount;
00383   nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
00384   nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID);
00385   searchSession->CountSearchScopes(&scopeCount);
00386   for (PRInt32 i = 0; i < scopeCount; i++)
00387   {
00388     nsMsgSearchScopeValue scopeId;
00389     nsCOMPtr<nsIMsgFolder> searchFolder;
00390     searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
00391     if (searchFolder)
00392     {
00393       nsCOMPtr<nsISimpleEnumerator> cachedHits;
00394       nsCOMPtr<nsIMsgDatabase> searchDB;
00395       nsXPIDLCString searchUri;
00396       m_viewFolder->GetURI(getter_Copies(searchUri));
00397       nsresult rv = searchFolder->GetMsgDatabase(nsnull, getter_AddRefs(searchDB));
00398       if (NS_SUCCEEDED(rv) && searchDB)
00399       {
00400         if (msgDBService)
00401           msgDBService->RegisterPendingListener(searchFolder, this);
00402 
00403         m_foldersSearchingOver.AppendObject(searchFolder);
00404 #ifdef DEBUG
00405         nsXPIDLCString folderUri;
00406         searchFolder->GetURI(getter_Copies(folderUri));
00407         printf("adding to m_foldersSearchingOver - %s\n", folderUri.get());
00408 #endif
00409         searchDB->GetCachedHits(searchUri, getter_AddRefs(cachedHits));
00410         PRBool hasMore;
00411         if (cachedHits)
00412         {
00413           cachedHits->HasMoreElements(&hasMore);
00414           if (hasMore)
00415           {
00416             nsMsgKey prevKey = nsMsgKey_None;
00417             while (hasMore)
00418             {
00419               nsCOMPtr <nsIMsgDBHdr> pHeader;
00420               nsresult rv = cachedHits->GetNext(getter_AddRefs(pHeader));
00421               NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
00422               if (pHeader && NS_SUCCEEDED(rv))
00423               {
00424                 nsMsgKey msgKey;
00425                 pHeader->GetMessageKey(&msgKey);
00426                 NS_ASSERTION(prevKey == nsMsgKey_None || msgKey > prevKey, "cached Hits not sorted");
00427                 prevKey = msgKey;
00428                 AddHdrFromFolder(pHeader, searchFolder); // need to QI to nsISupports?
00429               }
00430               else
00431                 break;
00432               cachedHits->HasMoreElements(&hasMore);
00433             }
00434           }
00435         }
00436       }
00437     }
00438   }
00439 
00440   m_curFolderStartKeyIndex = 0;
00441   m_curFolderGettingHits = nsnull;
00442   m_curFolderHasCachedHits = PR_FALSE;
00443 
00444   // if we have cached hits, sort them.
00445   if (GetSize() > 0)
00446   {
00447     if (m_sortType != nsMsgViewSortType::byThread)
00448     {
00449       m_sortValid = PR_FALSE;       //sort the results 
00450       Sort(m_sortType, m_sortOrder);
00451     }
00452   }
00453 //    mSearchResults->Clear();
00454     return NS_OK;
00455 }
00456 
00457 
00458 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::DoCommand(nsMsgViewCommandTypeValue command)
00459 {
00460     return nsMsgSearchDBView::DoCommand(command);
00461 }
00462 
00463 
00464 
00465 NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder)
00466 {
00467   NS_ENSURE_ARG_POINTER(aMsgFolder);
00468   NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
00469   return NS_OK;
00470 }