/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * David Bienvenu. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * David Bienvenu * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "msgCore.h" #include "nsMsgXFVirtualFolderDBView.h" #include "nsIMsgHdr.h" #include "nsIMsgThread.h" #include "nsQuickSort.h" #include "nsIDBFolderInfo.h" #include "nsXPIDLString.h" #include "nsMsgBaseCID.h" #include "nsIMsgCopyService.h" #include "nsICopyMsgStreamListener.h" #include "nsMsgUtils.h" #include "nsITreeColumns.h" #include "nsIMsgSearchSession.h" #include "nsMsgDBCID.h" nsMsgXFVirtualFolderDBView::nsMsgXFVirtualFolderDBView() { mSuppressMsgDisplay = PR_FALSE; m_doingSearch = PR_FALSE; } nsMsgXFVirtualFolderDBView::~nsMsgXFVirtualFolderDBView() { } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount) { m_viewFolder = folder; return nsMsgSearchDBView::Open(folder, sortType, sortOrder, viewFlags, pCount); } void nsMsgXFVirtualFolderDBView::RemovePendingDBListeners() { nsresult rv; nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); // UnregisterPendingListener will return an error when there are no more instances // of this object registered as pending listeners. while (NS_SUCCEEDED(rv)) rv = msgDBService->UnregisterPendingListener(this); } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Close() { RemovePendingDBListeners(); return NS_OK; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) { nsMsgXFVirtualFolderDBView* newMsgDBView; NS_NEWXPCOM(newMsgDBView, nsMsgXFVirtualFolderDBView); if (!newMsgDBView) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); NS_ENSURE_SUCCESS(rv,rv); NS_IF_ADDREF(*_retval = newMsgDBView); return NS_OK; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) { nsMsgSearchDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); nsMsgXFVirtualFolderDBView* newMsgDBView = (nsMsgXFVirtualFolderDBView *) aNewMsgDBView; newMsgDBView->m_viewFolder = m_viewFolder; newMsgDBView->m_searchSession = m_searchSession; PRInt32 scopeCount; nsCOMPtr searchSession = do_QueryReferent(m_searchSession); nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID); searchSession->CountSearchScopes(&scopeCount); for (PRInt32 i = 0; i < scopeCount; i++) { nsMsgSearchScopeValue scopeId; nsCOMPtr searchFolder; searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder)); if (searchFolder) { nsCOMPtr searchDB; nsXPIDLCString searchUri; m_viewFolder->GetURI(getter_Copies(searchUri)); nsresult rv = searchFolder->GetMsgDatabase(nsnull, getter_AddRefs(searchDB)); if (NS_SUCCEEDED(rv) && searchDB) { if (msgDBService) msgDBService->RegisterPendingListener(searchFolder, newMsgDBView); } } } return NS_OK; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue *aViewType) { NS_ENSURE_ARG_POINTER(aViewType); *aViewType = nsMsgViewType::eShowVirtualFolderResults; return NS_OK; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::SetSearchSession(nsIMsgSearchSession *aSession) { m_searchSession = do_GetWeakReference(aSession); return NS_OK; } nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/) { if (newHdr) { PRBool match=PR_FALSE; nsCOMPtr searchSession = do_QueryReferent(m_searchSession); if (searchSession) searchSession->MatchHdr(newHdr, m_db, &match); if (match) { nsCOMPtr folder; newHdr->GetFolder(getter_AddRefs(folder)); PRBool saveDoingSearch = m_doingSearch; m_doingSearch = PR_FALSE; OnSearchHit(newHdr, folder); m_doingSearch = saveDoingSearch; } } return NS_OK; } nsresult nsMsgXFVirtualFolderDBView::InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsISupports *folder) { nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr); if (insertIndex == nsMsgViewIndex_None) return AddHdrFromFolder(msgHdr, folder); nsMsgKey msgKey; PRUint32 msgFlags; msgHdr->GetMessageKey(&msgKey); msgHdr->GetFlags(&msgFlags); m_keys.InsertAt(insertIndex, msgKey); m_flags.InsertAt(insertIndex, msgFlags); m_folders->InsertElementAt(folder, insertIndex); m_levels.InsertAt((PRInt32) insertIndex, (PRUint8) 0); // the call to NoteChange() has to happen after we add the key // as NoteChange() will call RowCountChanged() which will call our GetRowCount() NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete); return NS_OK; } void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, PRUint32 numNewHits) { nsCOMPtr db; nsresult rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(db)); if (NS_SUCCEEDED(rv) && db) { nsXPIDLCString searchUri; m_viewFolder->GetURI(getter_Copies(searchUri)); PRUint32 numBadHits; nsMsgKey *badHits; rv = db->RefreshCache(searchUri, numNewHits, newHits, &numBadHits, &badHits); if (NS_SUCCEEDED(rv)) { for (PRUint32 badHitIndex = 0; badHitIndex < numBadHits; badHitIndex++) { // of course, this isn't quite right nsMsgViewIndex staleHitIndex = FindKey(badHits[badHitIndex], PR_TRUE); if (staleHitIndex != nsMsgViewIndex_None) RemoveByIndex(staleHitIndex); } delete [] badHits; } } } void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder) { // Handle the most recent folder with hits, if any. #ifdef DEBUG if (curSearchFolder) { nsXPIDLCString folderUri; curSearchFolder->GetURI(getter_Copies(folderUri)); printf("UpdateCacheAndViewForPrevSearchedFolders curSearchFolder - %s\n", folderUri.get()); } #endif if (m_curFolderGettingHits) { PRUint32 count = m_hdrHits.Count(); nsMsgKeyArray newHits; for (PRUint32 i = 0; i < count; i++) { nsMsgKey key; m_hdrHits[i]->GetMessageKey(&key); newHits.Add(key); } newHits.QuickSort(); UpdateCacheAndViewForFolder(m_curFolderGettingHits, newHits.GetArray(), newHits.GetSize()); } while (m_foldersSearchingOver.Count() > 0) { // this new folder has cached hits. if (m_foldersSearchingOver[0] == curSearchFolder) { m_curFolderHasCachedHits = PR_TRUE; m_foldersSearchingOver.RemoveObjectAt(0); break; } else if (m_foldersSearchingOver[0] != m_curFolderGettingHits) { // this must be a folder that had no hits with the current search. // So all cached hits, if any, need to be removed. #ifdef DEBUG nsXPIDLCString folderUri; m_foldersSearchingOver[0]->GetURI(getter_Copies(folderUri)); printf("UpdateCacheAndViewForPrevSearchedFolders 0 hits in - %s\n", folderUri.get()); #endif UpdateCacheAndViewForFolder(m_foldersSearchingOver[0], 0, nsnull); #ifdef DEBUG m_foldersSearchingOver[0]->GetURI(getter_Copies(folderUri)); printf("UpdateCacheAndViewForPrevSearchedFolders removing %s\n", folderUri.get()); #endif m_foldersSearchingOver.RemoveObjectAt(0); } } } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder) { NS_ENSURE_ARG(aMsgHdr); NS_ENSURE_ARG(folder); nsCOMPtr supports = do_QueryInterface(folder); nsCOMPtr dbToUse; nsCOMPtr folderInfo; folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse)); if (m_curFolderGettingHits != folder && m_doingSearch) { #ifdef DEBUG nsXPIDLCString folderUri; folder->GetURI(getter_Copies(folderUri)); printf("first hit for folder - %s\n", folderUri.get()); #endif m_curFolderHasCachedHits = PR_FALSE; // since we've gotten a hit for a new folder, the searches for // any previous folders are done, so deal with stale cached hits // for those folders now. UpdateCacheAndViewForPrevSearchedFolders(folder); m_curFolderGettingHits = folder; m_hdrHits.Clear(); m_curFolderStartKeyIndex = m_keys.GetSize(); } PRBool hdrInCache = PR_FALSE; nsXPIDLCString searchUri; m_viewFolder->GetURI(getter_Copies(searchUri)); dbToUse->HdrIsInCache(searchUri, aMsgHdr, &hdrInCache); if (!m_doingSearch || !m_curFolderHasCachedHits || !hdrInCache) { if (m_sortValid) InsertHdrFromFolder(aMsgHdr, supports); else AddHdrFromFolder(aMsgHdr, supports); } m_hdrHits.AppendObject(aMsgHdr); return NS_OK; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status) { // handle any non verified hits we haven't handled yet. UpdateCacheAndViewForPrevSearchedFolders(nsnull); m_doingSearch = PR_FALSE; //we want to set imap delete model once the search is over because setting next //message after deletion will happen before deleting the message and search scope //can change with every search. mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; //set to default in case it is non-imap folder nsCOMPtr curFolder = do_QueryElementAt(m_folders, 0); if (curFolder) GetImapDeleteModel(curFolder); nsCOMPtr virtDatabase; nsCOMPtr dbFolderInfo; nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); NS_ENSURE_SUCCESS(rv, rv); // count up the number of unread and total messages from the view, and set those in the // folder - easier than trying to keep the count up to date in the face of // search hits coming in while the user is reading/deleting messages. PRInt32 numUnread = 0; for (PRInt32 i = 0; i < m_flags.GetSize(); i++) if (!(m_flags[i] & MSG_FLAG_READ)) numUnread++; dbFolderInfo->SetNumUnreadMessages(numUnread); dbFolderInfo->SetNumMessages(GetSize()); m_viewFolder->UpdateSummaryTotals(true); // force update from db. virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit); if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread) { m_sortValid = PR_FALSE; //sort the results Sort(m_sortType, m_sortOrder); } m_foldersSearchingOver.Clear(); m_curFolderGettingHits = nsnull; return rv; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::OnNewSearch() { PRInt32 oldSize = GetSize(); RemovePendingDBListeners(); m_doingSearch = PR_TRUE; m_folders->Clear(); m_keys.RemoveAll(); m_levels.RemoveAll(); m_flags.RemoveAll(); // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount() if (mTree) mTree->RowCountChanged(0, -oldSize); // to use the search results cache, we'll need to iterate over the scopes in the // search session, calling getNthSearchScope for i = 0; i < searchSession.countSearchScopes; i++ // and for each folder, then open the db and pull out the cached hits, add them to the view. // For each hit in a new folder, we'll then clean up the stale hits from the previous folder(s). PRInt32 scopeCount; nsCOMPtr searchSession = do_QueryReferent(m_searchSession); nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID); searchSession->CountSearchScopes(&scopeCount); for (PRInt32 i = 0; i < scopeCount; i++) { nsMsgSearchScopeValue scopeId; nsCOMPtr searchFolder; searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder)); if (searchFolder) { nsCOMPtr cachedHits; nsCOMPtr searchDB; nsXPIDLCString searchUri; m_viewFolder->GetURI(getter_Copies(searchUri)); nsresult rv = searchFolder->GetMsgDatabase(nsnull, getter_AddRefs(searchDB)); if (NS_SUCCEEDED(rv) && searchDB) { if (msgDBService) msgDBService->RegisterPendingListener(searchFolder, this); m_foldersSearchingOver.AppendObject(searchFolder); #ifdef DEBUG nsXPIDLCString folderUri; searchFolder->GetURI(getter_Copies(folderUri)); printf("adding to m_foldersSearchingOver - %s\n", folderUri.get()); #endif searchDB->GetCachedHits(searchUri, getter_AddRefs(cachedHits)); PRBool hasMore; if (cachedHits) { cachedHits->HasMoreElements(&hasMore); if (hasMore) { nsMsgKey prevKey = nsMsgKey_None; while (hasMore) { nsCOMPtr pHeader; nsresult rv = cachedHits->GetNext(getter_AddRefs(pHeader)); NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); if (pHeader && NS_SUCCEEDED(rv)) { nsMsgKey msgKey; pHeader->GetMessageKey(&msgKey); NS_ASSERTION(prevKey == nsMsgKey_None || msgKey > prevKey, "cached Hits not sorted"); prevKey = msgKey; AddHdrFromFolder(pHeader, searchFolder); // need to QI to nsISupports? } else break; cachedHits->HasMoreElements(&hasMore); } } } } } } m_curFolderStartKeyIndex = 0; m_curFolderGettingHits = nsnull; m_curFolderHasCachedHits = PR_FALSE; // if we have cached hits, sort them. if (GetSize() > 0) { if (m_sortType != nsMsgViewSortType::byThread) { m_sortValid = PR_FALSE; //sort the results Sort(m_sortType, m_sortOrder); } } // mSearchResults->Clear(); return NS_OK; } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::DoCommand(nsMsgViewCommandTypeValue command) { return nsMsgSearchDBView::DoCommand(command); } NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder) { NS_ENSURE_ARG_POINTER(aMsgFolder); NS_IF_ADDREF(*aMsgFolder = m_viewFolder); return NS_OK; }