cache virtual folder search results, sr=mscott 276716

This commit is contained in:
bienvenu%nventure.com 2006-08-02 21:25:36 +00:00
parent 8094ac5af0
commit c5ace2681c
15 changed files with 610 additions and 78 deletions

View File

@ -275,6 +275,7 @@ var folderListener = {
{
gDefaultSearchViewTerms = null;
viewDebug("searching gVirtualFolderTerms\n");
gDBView.viewFolder = gMsgFolderSelected;
loadVirtualFolder();
}
else if (gMsgFolderSelected.flags & MSG_FOLDER_FLAG_VIRTUAL)

View File

@ -616,15 +616,6 @@ nsresult nsMsgSearchOfflineMail::MatchTerms(nsIMsgDBHdr *msgToMatch,
NS_ENSURE_ARG(aExpressionTree);
nsresult err;
// Don't even bother to look at expunged messages awaiting compression
PRUint32 msgFlags;
msgToMatch->GetFlags(&msgFlags);
if (msgFlags & MSG_FLAG_EXPUNGED)
{
*pResult = PR_FALSE;
return NS_OK;
}
if (!*aExpressionTree)
{
PRUint32 initialPos = 0;

View File

@ -171,7 +171,8 @@ nsMsgSearchSession::GetNthSearchTerm(PRInt32 whichTerm,
NS_IMETHODIMP nsMsgSearchSession::CountSearchScopes(PRInt32 *_retval)
{
NS_ENSURE_ARG(_retval);
return NS_ERROR_NOT_IMPLEMENTED;
*_retval = m_scopeList.Count();
return NS_OK;
}
/* void GetNthSearchScope (in long which, out nsMsgSearchScope scopeId, out nsIMsgFolder folder); */

View File

@ -2725,6 +2725,13 @@ NS_IMETHODIMP VirtualFolderChangeListener::OnHdrChange(nsIMsgDBHdr *aHdrChanged,
if (numNewMessages == 1)
m_virtualFolder->SetHasNewMessages(PR_FALSE);
}
if (totalDelta)
{
nsXPIDLCString searchUri;
m_virtualFolder->GetURI(getter_Copies(searchUri));
msgDB->UpdateHdrInCache(searchUri, aHdrChanged, totalDelta == 1);
}
m_virtualFolder->UpdateSummaryTotals(PR_TRUE); // force update from db.
virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
}
@ -2762,6 +2769,10 @@ NS_IMETHODIMP VirtualFolderChangeListener::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted
if (numNewMessages == 1)
m_virtualFolder->SetHasNewMessages(PR_FALSE);
}
nsXPIDLCString searchUri;
m_virtualFolder->GetURI(getter_Copies(searchUri));
msgDB->UpdateHdrInCache(searchUri, aHdrDeleted, PR_FALSE);
m_virtualFolder->UpdateSummaryTotals(PR_TRUE); // force update from db.
virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
}
@ -2797,6 +2808,9 @@ NS_IMETHODIMP VirtualFolderChangeListener::OnHdrAdded(nsIMsgDBHdr *aNewHdr, nsMs
m_virtualFolder->SetHasNewMessages(PR_TRUE);
m_virtualFolder->SetNumNewMessages(numNewMessages + 1);
}
nsXPIDLCString searchUri;
m_virtualFolder->GetURI(getter_Copies(searchUri));
msgDB->UpdateHdrInCache(searchUri, aNewHdr, PR_TRUE);
dbFolderInfo->ChangeNumMessages(1);
m_virtualFolder->UpdateSummaryTotals(true); // force update from db.
virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
@ -2945,20 +2959,8 @@ NS_IMETHODIMP nsMsgAccountManager::LoadVirtualFolders()
// and we have to add a pending listener for each of them.
if (buffer.Length())
{
nsCStringArray folderUris;
dbFolderInfo->SetCharPtrProperty("searchFolderUri", buffer.get());
folderUris.ParseString(buffer.get(), "|");
for (PRInt32 i = 0; i < folderUris.Count(); i++)
{
rdf->GetResource(*(folderUris[i]), getter_AddRefs(resource));
nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource);
VirtualFolderChangeListener *dbListener = new VirtualFolderChangeListener();
m_virtualFolderListeners.AppendObject(dbListener);
dbListener->m_virtualFolder = virtualFolder;
dbListener->m_folderWatching = realFolder;
dbListener->Init();
msgDBService->RegisterPendingListener(realFolder, dbListener);
}
AddVFListenersForVF(virtualFolder, buffer.get(), rdf, msgDBService);
}
else // this folder is useless
{
@ -3067,6 +3069,29 @@ nsresult nsMsgAccountManager::WriteLineToOutputStream(const char *prefix, const
return NS_OK;
}
nsresult nsMsgAccountManager::AddVFListenersForVF(nsIMsgFolder *virtualFolder,
const char *srchFolderUris,
nsIRDFService *rdf,
nsIMsgDBService *msgDBService)
{
nsCStringArray folderUris;
folderUris.ParseString(srchFolderUris, "|");
nsCOMPtr <nsIRDFResource> resource;
for (PRInt32 i = 0; i < folderUris.Count(); i++)
{
rdf->GetResource(*(folderUris[i]), getter_AddRefs(resource));
nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource);
VirtualFolderChangeListener *dbListener = new VirtualFolderChangeListener();
NS_ENSURE_TRUE(dbListener, NS_ERROR_OUT_OF_MEMORY);
m_virtualFolderListeners.AppendObject(dbListener);
dbListener->m_virtualFolder = virtualFolder;
dbListener->m_folderWatching = realFolder;
dbListener->Init();
msgDBService->RegisterPendingListener(realFolder, dbListener);
}
}
NS_IMETHODIMP nsMsgAccountManager::OnItemAdded(nsIRDFResource *parentItem, nsISupports *item)
{
nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item);
@ -3083,23 +3108,14 @@ NS_IMETHODIMP nsMsgAccountManager::OnItemAdded(nsIRDFResource *parentItem, nsISu
nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
if (msgDBService)
{
VirtualFolderChangeListener *dbListener = new VirtualFolderChangeListener();
dbListener->m_virtualFolder = folder;
nsCOMPtr <nsIMsgDatabase> virtDatabase;
nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
m_virtualFolderListeners.AppendObject(dbListener);
rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
NS_ENSURE_SUCCESS(rv, rv);
nsXPIDLCString srchFolderUri;
dbFolderInfo->GetCharPtrProperty("searchFolderUri", getter_Copies(srchFolderUri));
// if we supported cross folder virtual folders, we'd have a list of folders uris,
// and we'd have to add a pending listener for each of them.
rv = GetExistingFolder(srchFolderUri.get(), getter_AddRefs(dbListener->m_folderWatching));
if (dbListener->m_folderWatching)
{
dbListener->Init();
msgDBService->RegisterPendingListener(dbListener->m_folderWatching, dbListener);
}
nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
AddVFListenersForVF(folder, srchFolderUri, rdf, msgDBService);
}
rv = SaveVirtualFolders();
}

View File

@ -56,6 +56,8 @@
#include "nsIUrlListener.h"
#include "nsCOMArray.h"
class nsIRDFService;
class nsMsgAccountManager: public nsIMsgAccountManager,
public nsIObserver,
public nsSupportsWeakReference,
@ -205,6 +207,10 @@ private:
// handle virtual folders
nsresult GetVirtualFoldersFile(nsCOMPtr<nsILocalFile>& file);
nsresult WriteLineToOutputStream(const char *prefix, const char * line, nsIOutputStream *outputStream);
nsresult AddVFListenersForVF(nsIMsgFolder *virtualFolder,
const char *srchFolderUris,
nsIRDFService *rdf,
nsIMsgDBService *msgDBService);
static void getUniqueKey(const char* prefix,
nsHashtable *hashTable,

View File

@ -4178,7 +4178,10 @@ nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsMsgKeyAr
nsMsgViewIndex tryIndex = (lowIndex + highIndex - 1) / 2;
EntryInfo2.id = keys->GetAt(tryIndex);
nsCOMPtr <nsIMsgDBHdr> tryHdr;
rv = m_db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
nsCOMPtr <nsIMsgDatabase> db;
GetDBForViewIndex(tryIndex, getter_AddRefs(db));
if (db)
rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
if (!tryHdr)
break;
if (fieldType == kCollationKey)

View File

@ -48,6 +48,8 @@
nsMsgQuickSearchDBView::nsMsgQuickSearchDBView()
{
m_usingCachedHits = PR_FALSE;
m_cacheEmpty = PR_TRUE;
}
nsMsgQuickSearchDBView::~nsMsgQuickSearchDBView()
@ -219,17 +221,55 @@ nsMsgQuickSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
NS_ENSURE_ARG(aMsgHdr);
if (!m_db)
return NS_ERROR_NULL_POINTER;
// remember search hit and when search is done, reconcile cache
// with new hits;
m_hdrHits.AppendObject(aMsgHdr);
nsMsgKey key;
aMsgHdr->GetMessageKey(&key);
// is FindKey going to be expensive here? A lot of hits could make
// it a little bit slow to search through the view for every hit.
if (m_cacheEmpty || FindKey(key, PR_FALSE) == nsMsgViewIndex_None)
return AddHdr(aMsgHdr);
else
return NS_OK;
}
NS_IMETHODIMP
nsMsgQuickSearchDBView::OnSearchDone(nsresult status)
{
if (m_viewFolder)
{
nsMsgKeyArray keyArray;
nsXPIDLCString searchUri;
m_viewFolder->GetURI(getter_Copies(searchUri));
PRUint32 count = m_hdrHits.Count();
// build up message keys.
PRUint32 i;
for (i = 0; i < count; i++)
{
nsMsgKey key;
m_hdrHits[i]->GetMessageKey(&key);
keyArray.Add(key);
}
nsMsgKey *staleHits;
PRUint32 numBadHits;
m_db->RefreshCache(searchUri, m_hdrHits.Count(), keyArray.GetArray(), &numBadHits, &staleHits);
for (i = 0; i < numBadHits; i++)
{
nsMsgViewIndex staleHitIndex = FindKey(staleHits[i], PR_TRUE);
if (staleHitIndex != nsMsgViewIndex_None)
RemoveByIndex(staleHitIndex);
}
delete [] staleHits;
// we also need to add new hits - should we have RefreshCache calculate these?
// or just look through the view for each hit to see if we're already displaying it?
}
if (m_sortType != nsMsgViewSortType::byThread)//we do not find levels for the results.
{
m_sortValid = PR_FALSE; //sort the results
Sort(m_sortType, m_sortOrder);
}
m_hdrHits.Clear();
return NS_OK;
}
@ -242,11 +282,41 @@ nsMsgQuickSearchDBView::OnNewSearch()
m_keys.RemoveAll();
m_levels.RemoveAll();
m_flags.RemoveAll();
m_hdrHits.Clear();
// this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount()
if (mTree)
mTree->RowCountChanged(0, -oldSize);
PRUint32 folderFlags = 0;
if (m_viewFolder)
m_viewFolder->GetFlags(&folderFlags);
// check if it's a virtual folder - if so, we should get the cached hits
// from the db, and set a flag saying that we're using cached values.
if (folderFlags & MSG_FOLDER_FLAG_VIRTUAL)
{
nsCOMPtr<nsISimpleEnumerator> cachedHits;
nsXPIDLCString searchUri;
m_viewFolder->GetURI(getter_Copies(searchUri));
m_db->GetCachedHits(searchUri, getter_AddRefs(cachedHits));
if (cachedHits)
{
PRBool hasMore;
m_usingCachedHits = PR_TRUE;
cachedHits->HasMoreElements(&hasMore);
m_cacheEmpty = !hasMore;
while (hasMore)
{
nsCOMPtr <nsIMsgDBHdr> pHeader;
nsresult rv = cachedHits->GetNext(getter_AddRefs(pHeader));
NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken");
if (pHeader && NS_SUCCEEDED(rv))
AddHdr(pHeader);
else
break;
cachedHits->HasMoreElements(&hasMore);
}
}
}
return NS_OK;
}

View File

@ -42,6 +42,9 @@
#include "nsMsgThreadedDBView.h"
#include "nsIMsgSearchNotify.h"
#include "nsIMsgSearchSession.h"
#include "nsCOMArray.h"
#include "nsIMsgHdr.h"
class nsMsgQuickSearchDBView : public nsMsgThreadedDBView, public nsIMsgSearchNotify
{
@ -66,6 +69,9 @@ public:
protected:
nsWeakPtr m_searchSession;
nsMsgKeyArray m_origKeys;
PRBool m_usingCachedHits;
PRBool m_cacheEmpty;
nsCOMArray <nsIMsgDBHdr> m_hdrHits;
virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool ensureListed);
virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder);
virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result);

View File

@ -190,6 +190,27 @@ nsresult nsMsgSearchDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDataba
return NS_MSG_INVALID_DBVIEW_INDEX;
}
nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsISupports *folder)
{
m_folders->AppendElement(folder);
nsMsgKey msgKey;
PRUint32 msgFlags;
msgHdr->GetMessageKey(&msgKey);
// nsMsgKey_None means it's not a valid hdr.
if (msgKey != nsMsgKey_None)
{
msgHdr->GetFlags(&msgFlags);
m_keys.Add(msgKey);
m_levels.Add(0);
m_flags.Add(msgFlags);
// this needs to be called after we add the key, since RowCountChanged() will call our GetRowCount()
if (mTree)
mTree->RowCountChanged(GetSize() - 1, 1);
}
return NS_OK;
}
NS_IMETHODIMP
nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
{
@ -209,20 +230,7 @@ nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
}
}
m_folders->AppendElement(supports);
nsMsgKey msgKey;
PRUint32 msgFlags;
aMsgHdr->GetMessageKey(&msgKey);
aMsgHdr->GetFlags(&msgFlags);
m_keys.Add(msgKey);
m_levels.Add(0);
m_flags.Add(msgFlags);
// this needs to be called after we add the key, since RowCountChanged() will call our GetRowCount()
if (mTree)
mTree->RowCountChanged(GetSize() - 1, 1);
return NS_OK;
return AddHdrFromFolder(aMsgHdr, supports);
}
NS_IMETHODIMP
@ -518,7 +526,7 @@ nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow *window)
// called for delete with trash, copy and move
if (mCommand == nsMsgViewCommandType::deleteMsg)
curFolder->DeleteMessages(messageArray, window, PR_FALSE /* delete storage */, PR_FALSE /* is move*/, this, PR_FALSE /*allowUndo*/);
curFolder->DeleteMessages(messageArray, window, PR_FALSE /* delete storage */, PR_FALSE /* is move*/, this, PR_TRUE /*allowUndo*/);
else
{
NS_ASSERTION(!(curFolder == mDestFolder), "The source folder and the destination folder are the same");
@ -528,9 +536,9 @@ nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow *window)
if (NS_SUCCEEDED(rv))
{
if (mCommand == nsMsgViewCommandType::moveMessages)
copyService->CopyMessages(curFolder, messageArray, mDestFolder, PR_TRUE /* isMove */, this, window, PR_FALSE /*allowUndo*/);
copyService->CopyMessages(curFolder, messageArray, mDestFolder, PR_TRUE /* isMove */, this, window, PR_TRUE /*allowUndo*/);
else if (mCommand == nsMsgViewCommandType::copyMessages)
copyService->CopyMessages(curFolder, messageArray, mDestFolder, PR_FALSE /* isMove */, this, window, PR_FALSE /*allowUndo*/);
copyService->CopyMessages(curFolder, messageArray, mDestFolder, PR_FALSE /* isMove */, this, window, PR_TRUE /*allowUndo*/);
}
}
}

View File

@ -77,6 +77,7 @@ public:
protected:
nsresult FetchLocation(PRInt32 aRow, PRUnichar ** aLocationString);
virtual nsresult AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsISupports *folder);
virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db);
virtual nsresult RemoveByIndex(nsMsgViewIndex index);
virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool isMove, nsIMsgFolder *destFolder);

View File

@ -142,6 +142,86 @@ nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey a
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 <nsIMsgDatabase> 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.
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);
}
UpdateCacheAndViewForFolder(m_curFolderGettingHits, newHits.GetArray(), newHits.GetSize());
}
while (m_foldersWithNonVerifiedCachedHits.Count() > 0)
{
// this new folder has cached hits.
if (m_foldersWithNonVerifiedCachedHits[0] == curSearchFolder)
{
m_curFolderHasCachedHits = PR_TRUE;
break;
}
else if (m_foldersWithNonVerifiedCachedHits[0] != m_curFolderGettingHits)
{
// this must be a folder that had cached hits but no hits with
// the current search. So all cached hits need to be removed.
UpdateCacheAndViewForFolder(m_foldersWithNonVerifiedCachedHits[0], 0, nsnull);
}
m_foldersWithNonVerifiedCachedHits.RemoveObjectAt(0);
}
}
NS_IMETHODIMP
nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder)
{
@ -149,32 +229,54 @@ nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *fold
NS_ENSURE_ARG(folder);
nsCOMPtr <nsISupports> supports = do_QueryInterface(folder);
nsCOMPtr<nsIMsgDatabase> dbToUse;
nsCOMPtr<nsIDBFolderInfo> folderInfo;
folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse));
if (m_folders->IndexOf(supports) < 0 ) //do this just for new folder
{
nsCOMPtr<nsIMsgDatabase> dbToUse;
nsCOMPtr<nsIDBFolderInfo> folderInfo;
folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse));
if (dbToUse)
{
dbToUse->AddListener(this);
m_dbToUseList.AppendObject(dbToUse);
}
}
if (m_curFolderGettingHits != folder)
{
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.
m_folders->AppendElement(supports);
nsMsgKey msgKey;
PRUint32 msgFlags;
aMsgHdr->GetMessageKey(&msgKey);
aMsgHdr->GetFlags(&msgFlags);
m_keys.Add(msgKey);
m_levels.Add(0);
m_flags.Add(msgFlags);
// this needs to be called after we add the key, since RowCountChanged() will call our GetRowCount()
if (mTree)
mTree->RowCountChanged(GetSize() - 1, 1);
UpdateCacheAndViewForPrevSearchedFolders(folder);
m_curFolderGettingHits = folder;
m_hdrHits.Clear();
m_curFolderStartKeyIndex = m_keys.GetSize();
}
// calling FindHdr is sub-optimal here since for virtual folders with
// a large number of hits, we'll have an 0(n2) algorithm
// - we could add a way to check if a hdr is in the cached hits for
// this folder, or we could make FindHdr do a binary search when
// the view is sorted. Checking the cached hits is probably best
// since they're in id order, so we can do a binary search.
// Actually, I think Mork has a hash table for uid lookups in a
// table, so we should definitely use the db.
PRBool hdrInCache = PR_FALSE;
nsXPIDLCString searchUri;
m_viewFolder->GetURI(getter_Copies(searchUri));
dbToUse->HdrIsInCache(searchUri, aMsgHdr, &hdrInCache);
if (!m_curFolderHasCachedHits || !hdrInCache)
{
if (m_sortValid)
InsertHdrFromFolder(aMsgHdr, supports);
else
AddHdrFromFolder(aMsgHdr, supports);
}
m_hdrHits.AppendObject(aMsgHdr);
m_numTotal++;
PRUint32 msgFlags;
aMsgHdr->GetFlags(&msgFlags);
if (!(msgFlags & MSG_FLAG_READ))
m_numUnread++;
@ -184,6 +286,9 @@ nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *fold
NS_IMETHODIMP
nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status)
{
// handle any non verified hits we haven't handled yet.
UpdateCacheAndViewForPrevSearchedFolders(nsnull);
//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.
@ -200,11 +305,13 @@ nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status)
dbFolderInfo->SetNumMessages(m_numTotal);
m_viewFolder->UpdateSummaryTotals(true); // force update from db.
virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
if (m_sortType != nsMsgViewSortType::byThread)
if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread)
{
m_sortValid = PR_FALSE; //sort the results
Sort(m_sortType, m_sortOrder);
}
m_foldersWithNonVerifiedCachedHits.Clear();
m_curFolderGettingHits = nsnull;
return rv;
}
@ -229,6 +336,73 @@ nsMsgXFVirtualFolderDBView::OnNewSearch()
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 <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession);
searchSession->CountSearchScopes(&scopeCount);
for (PRInt32 i = 0; i < scopeCount; i++)
{
nsMsgSearchScopeValue scopeId;
nsCOMPtr<nsIMsgFolder> searchFolder;
searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
if (searchFolder)
{
nsCOMPtr<nsISimpleEnumerator> cachedHits;
nsCOMPtr<nsIMsgDatabase> searchDB;
nsXPIDLCString searchUri;
m_viewFolder->GetURI(getter_Copies(searchUri));
nsresult rv = searchFolder->GetMsgDatabase(nsnull, getter_AddRefs(searchDB));
if (NS_SUCCEEDED(rv) && searchDB)
{
searchDB->GetCachedHits(searchUri, getter_AddRefs(cachedHits));
PRBool hasMore;
if (cachedHits)
{
cachedHits->HasMoreElements(&hasMore);
if (hasMore)
{
nsMsgKey prevKey = nsMsgKey_None;
m_foldersWithNonVerifiedCachedHits.AppendObject(searchFolder);
while (hasMore)
{
nsCOMPtr <nsIMsgDBHdr> 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;
}

View File

@ -65,11 +65,20 @@ public:
NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType);
NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command);
virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, PRBool ensureListed);
virtual nsresult InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsISupports *folder);
NS_IMETHOD GetMsgFolder(nsIMsgFolder **aMsgFolder);
void UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder);
void UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, PRUint32 numNewHits);
protected:
nsCOMPtr <nsIMsgFolder> m_viewFolder;
PRUint32 m_cachedFolderArrayIndex; // array index of next folder with cached hits to deal with.
nsCOMArray<nsIMsgFolder> m_foldersWithNonVerifiedCachedHits;
nsCOMArray<nsIMsgDBHdr> m_hdrHits;
nsCOMPtr <nsIMsgFolder> m_curFolderGettingHits;
PRUint32 m_curFolderStartKeyIndex; // keeps track of the index of the first hit from the cur folder
PRBool m_curFolderHasCachedHits;
nsWeakPtr m_searchSession;
PRInt32 m_numUnread;
PRInt32 m_numTotal;

View File

@ -54,6 +54,7 @@ interface nsIMsgOfflineImapOperation;
interface nsIMsgFolder;
interface nsIOFileStream;
interface nsIUrlListener;
interface nsISupportsArray;
[ptr] native octetPtr(PRUint8);
@ -117,7 +118,7 @@ interface nsIMsgDBService : nsISupports
void unregisterPendingListener(in nsIDBChangeListener aListener);
};
[scriptable, uuid(7b510984-bbac-4e8f-b6cd-134509000776)]
[scriptable, uuid(6393FEF1-D6C6-444A-90B8-BEC84E8BD8D7)]
interface nsIMsgDatabase : nsIDBChangeAnnouncer {
void Open(in nsIFileSpec aFolderName, in boolean aCreate, in boolean aLeaveInvalidDB);
@ -301,5 +302,13 @@ interface nsIMsgDatabase : nsIDBChangeAnnouncer {
* the caller should free when done using nsMemory::Free.
*/
void getNewList(out unsigned long count, [array, size_is(count)] out nsMsgKey newKeys);
// These are used for caching search hits in a db, to speed up saved search folders.
nsISimpleEnumerator getCachedHits(in string aSearchFolderUri);
void refreshCache(in string aSearchFolderUri, in unsigned long aNumKeys, [array, size_is (aNumKeys)] in nsMsgKey aNewHits,
out unsigned long aNumBadHits, [array, size_is(aNumBadHits)] out nsMsgKey aStaleHits);
void updateHdrInCache(in string aSearchFolderUri, in nsIMsgDBHdr aHdr, in boolean aAdd);
boolean hdrIsInCache(in string aSearchFolderUri, in nsIMsgDBHdr aHdr);
};

View File

@ -95,6 +95,7 @@ public:
virtual nsresult CreateMsgHdr(nsIMdbRow* hdrRow, nsMsgKey key, nsIMsgDBHdr **result);
virtual nsresult GetThreadForMsgKey(nsMsgKey msgKey, nsIMsgThread **result);
virtual nsresult EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, PRUint32 *pFlag);
nsresult GetSearchResultsTable(const char *searchFolderUri, PRBool createIfMissing, nsIMdbTable **table);
// this might just be for debugging - we'll see.
nsresult ListAllThreads(nsMsgKeyArray *threadIds);
@ -289,6 +290,8 @@ protected:
nsresult ClearUseHdrCache();
nsresult RemoveHdrFromUseCache(nsIMsgDBHdr *hdr, nsMsgKey key);
mdb_pos FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert);
void ClearCachedObjects(PRBool dbGoingAway);
// all instantiated headers, but doesn't hold refs.
PLDHashTable *m_headersInUse;

View File

@ -156,7 +156,7 @@ NS_IMETHODIMP nsMsgDBService::OpenFolderDB(nsIMsgFolder *aFolder, PRBool aCreate
PRInt32 numMessages;
msgDatabase->m_mdbAllMsgHeadersTable->GetCount(msgDatabase->GetEnv(), &numHdrsInTable);
msgDatabase->m_dbFolderInfo->GetNumMessages(&numMessages);
if (numMessages != numHdrsInTable)
if (numMessages != (PRInt32) numHdrsInTable)
msgDatabase->SyncCounts();
}
}
@ -2130,7 +2130,7 @@ nsMsgDatabase::MarkThreadIgnored(nsIMsgThread *thread, nsMsgKey threadKey, PRBoo
nsCOMPtr <nsIMsgDBHdr> msg;
nsresult rv = GetMsgHdrForKey(threadKey, getter_AddRefs(msg));
NS_ENSURE_SUCCESS(rv, rv);
return NotifyHdrChangeAll(msg, oldThreadFlags, threadFlags, instigator);
}
@ -2545,7 +2545,7 @@ public:
// nsMsgDBEnumerator methods:
typedef nsresult (*nsMsgDBEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure);
nsMsgDBEnumerator(nsMsgDatabase* db,
nsMsgDBEnumerator(nsMsgDatabase* db, nsIMdbTable *table,
nsMsgDBEnumeratorFilter filter, void* closure);
virtual ~nsMsgDBEnumerator();
@ -2558,16 +2558,20 @@ protected:
PRBool mDone;
PRBool mNextPrefetched;
nsMsgDBEnumeratorFilter mFilter;
nsCOMPtr <nsIMdbTable> mTable;
void* mClosure;
};
nsMsgDBEnumerator::nsMsgDBEnumerator(nsMsgDatabase* db,
nsMsgDBEnumeratorFilter filter, void* closure)
nsIMdbTable *table,
nsMsgDBEnumeratorFilter filter,
void* closure)
: mDB(db), mRowCursor(nsnull), mResultHdr(nsnull), mDone(PR_FALSE),
mFilter(filter), mClosure(closure)
{
NS_ADDREF(mDB);
mNextPrefetched = PR_FALSE;
mTable = table;
}
nsMsgDBEnumerator::~nsMsgDBEnumerator()
@ -2584,10 +2588,10 @@ nsresult nsMsgDBEnumerator::GetRowCursor()
{
mDone = PR_FALSE;
if (!mDB || !mDB->m_mdbAllMsgHeadersTable)
if (!mDB || !mTable)
return NS_ERROR_NULL_POINTER;
return mDB->m_mdbAllMsgHeadersTable->GetTableRowCursor(mDB->GetEnv(), -1, &mRowCursor);
return mTable->GetTableRowCursor(mDB->GetEnv(), -1, &mRowCursor);
}
NS_IMETHODIMP nsMsgDBEnumerator::GetNext(nsISupports **aItem)
@ -2683,7 +2687,7 @@ NS_IMETHODIMP nsMsgDBEnumerator::HasMoreElements(PRBool *aResult)
NS_IMETHODIMP
nsMsgDatabase::EnumerateMessages(nsISimpleEnumerator* *result)
{
nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, nsnull, nsnull);
nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsnull, nsnull);
if (e == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(e);
@ -2978,7 +2982,7 @@ nsMsgFlagSetFilter(nsIMsgDBHdr *msg, void *closure)
nsresult
nsMsgDatabase::EnumerateMessagesWithFlag(nsISimpleEnumerator* *result, PRUint32 *pFlag)
{
nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, nsMsgFlagSetFilter, pFlag);
nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, m_mdbAllMsgHeadersTable, nsMsgFlagSetFilter, pFlag);
if (e == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(e);
@ -4790,3 +4794,233 @@ nsMsgDatabase::GetNewList(PRUint32 *aCount, PRUint32 **aNewKeys)
*aNewKeys = nsnull;
return NS_OK;
}
nsresult nsMsgDatabase::GetSearchResultsTable(const char *searchFolderUri, PRBool createIfMissing, nsIMdbTable **table)
{
mdb_kind kindToken;
mdb_count numTables;
mdb_bool mustBeUnique;
mdb_err err = m_mdbStore->StringToToken(GetEnv(), searchFolderUri, &kindToken);
err = m_mdbStore->GetTableKind(GetEnv(), m_hdrRowScopeToken, kindToken,
&numTables, &mustBeUnique, table);
if ((!*table || NS_FAILED(err)) && createIfMissing)
err = m_mdbStore->NewTable(GetEnv(), m_hdrRowScopeToken, kindToken, PR_TRUE, nsnull, table);
return *table ? err : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMsgDatabase::GetCachedHits(const char *aSearchFolderUri, nsISimpleEnumerator **aEnumerator)
{
nsCOMPtr <nsIMdbTable> table;
nsresult err = GetSearchResultsTable(aSearchFolderUri, PR_FALSE, getter_AddRefs(table));
NS_ENSURE_SUCCESS(err, err);
if (!table)
return NS_ERROR_FAILURE;
nsMsgDBEnumerator* e = new nsMsgDBEnumerator(this, table, nsnull, nsnull);
if (e == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aEnumerator = e);
return NS_OK;
}
NS_IMETHODIMP nsMsgDatabase::RefreshCache(const char *aSearchFolderUri, PRUint32 aNumKeys, nsMsgKey *aNewHits, PRUint32 *aNumBadHits, nsMsgKey **aStaleHits)
{
nsCOMPtr <nsIMdbTable> table;
nsresult err = GetSearchResultsTable(aSearchFolderUri, PR_TRUE, getter_AddRefs(table));
NS_ENSURE_SUCCESS(err, err);
// update the table so that it just contains aNewHits.
// And, keep track of the headers in the original table but not in aNewHits, so we
// can put those in aStaleHits.
// both aNewHits and the db table are sorted by uid/key.
// So, start at the beginning of the table and the aNewHits array.
PRUint32 newHitIndex = 0;
PRUint32 tableRowIndex = 0;
PRUint32 rowCount;
table->GetCount(GetEnv(), &rowCount);
nsMsgKeyArray staleHits;
// should assert that each array is sorted
while (newHitIndex < aNumKeys || tableRowIndex < rowCount)
{
mdbOid oid;
nsMsgKey tableRowKey = nsMsgKey_None;
if (tableRowIndex < rowCount)
{
nsresult ret = table->PosToOid (GetEnv(), tableRowIndex, &oid);
if (NS_FAILED(ret))
{
tableRowIndex++;
continue;
}
tableRowKey = oid.mOid_Id; // ### TODO need the real key for the 0th key problem.
}
if (newHitIndex < aNumKeys && aNewHits[newHitIndex] == tableRowKey)
{
newHitIndex++;
tableRowIndex++;
continue;
}
else if (tableRowIndex >= rowCount || (newHitIndex < aNumKeys && aNewHits[newHitIndex] < tableRowKey))
{
nsCOMPtr <nsIMdbRow> hdrRow;
mdbOid rowObjectId;
rowObjectId.mOid_Id = aNewHits[newHitIndex];
rowObjectId.mOid_Scope = m_hdrRowScopeToken;
err = m_mdbStore->GetRow(GetEnv(), &rowObjectId, getter_AddRefs(hdrRow));
if (hdrRow)
{
table->AddRow(GetEnv(), hdrRow);
mdb_pos newPos;
#ifdef DEBUG_David_Bienvenu
printf("adding row %lx at pos %lx \n", rowObjectId.mOid_Id, tableRowIndex);
#endif
table->MoveRow(GetEnv(), hdrRow, rowCount, tableRowIndex, &newPos);
rowCount++;
tableRowIndex++;
}
newHitIndex++;
continue;
}
else if (newHitIndex >= aNumKeys || aNewHits[newHitIndex] > tableRowKey)
{
staleHits.Add(tableRowKey);
table->CutOid(GetEnv(), &oid);
rowCount--;
continue; // don't increment tableRowIndex since we removed that row.
}
}
*aNumBadHits = staleHits.GetSize();
if (*aNumBadHits)
{
*aStaleHits = NS_STATIC_CAST(PRUint32 *, nsMemory::Alloc(*aNumBadHits * sizeof(PRUint32)));
if (!*aStaleHits)
return NS_ERROR_OUT_OF_MEMORY;
memcpy(*aStaleHits, staleHits.GetArray(), *aNumBadHits * sizeof(PRUint32));
}
else
*aStaleHits = nsnull;
#ifdef DEBUG_David_Bienvenu
printf("after refreshing cache\n");
// iterate over table and assert that it's in id order
table->GetCount(GetEnv(), &rowCount);
mdbOid oid;
tableRowIndex = 0;
mdb_id prevId = 0;
while (tableRowIndex < rowCount)
{
nsresult ret = table->PosToOid (m_mdbEnv, tableRowIndex++, &oid);
if (tableRowIndex > 1 && oid.mOid_Id <= prevId)
{
NS_ASSERTION(PR_FALSE, "inserting row into cached hits table, not sorted correctly");
printf("key %lx is before or equal %lx \n", prevId, oid.mOid_Id);
}
prevId = oid.mOid_Id;
}
#endif
Commit(nsMsgDBCommitType::kLargeCommit);
return NS_OK;
}
// search sorted table
mdb_pos nsMsgDatabase::FindInsertIndexInSortedTable(nsIMdbTable *table, mdb_id idToInsert)
{
mdb_pos searchPos = 0;
PRUint32 rowCount;
table->GetCount(GetEnv(), &rowCount);
mdb_pos hi = rowCount;
mdb_pos lo = 0;
while (hi > lo)
{
mdbOid outOid;
searchPos = (lo + hi - 1) / 2;
table->PosToOid(GetEnv(), searchPos, &outOid);
if (outOid.mOid_Id == idToInsert)
{
NS_ASSERTION(PR_FALSE, "id shouldn't be in table");
return hi;
}
if (outOid.mOid_Id > idToInsert)
hi = searchPos;
else // if (outOid.mOid_Id < idToInsert)
lo = searchPos + 1;
}
return hi;
}
NS_IMETHODIMP
nsMsgDatabase::UpdateHdrInCache(const char *aSearchFolderUri, nsIMsgDBHdr *aHdr, PRBool aAdd)
{
nsCOMPtr <nsIMdbTable> table;
nsresult err = GetSearchResultsTable(aSearchFolderUri, PR_TRUE, getter_AddRefs(table));
NS_ENSURE_SUCCESS(err, err);
nsMsgKey key;
aHdr->GetMessageKey(&key);
nsMsgHdr* msgHdr = NS_STATIC_CAST(nsMsgHdr*, aHdr); // closed system, so this is ok
if (err == NS_OK && m_mdbStore && msgHdr->m_mdbRow)
{
if (!aAdd)
{
table->CutRow(m_mdbEnv, msgHdr->m_mdbRow);
#ifdef DEBUG_David_Bienvenu
printf("removing key %lx \n", key);
#endif
}
else
{
mdbOid rowId;
msgHdr->m_mdbRow->GetOid(m_mdbEnv, &rowId);
mdb_pos insertPos = FindInsertIndexInSortedTable(table, rowId.mOid_Id);
PRUint32 rowCount;
table->GetCount(m_mdbEnv, &rowCount);
table->AddRow(m_mdbEnv, msgHdr->m_mdbRow);
mdb_pos newPos;
table->MoveRow(m_mdbEnv, msgHdr->m_mdbRow, rowCount, insertPos, &newPos);
#ifdef DEBUG_David_Bienvenu
printf("moving row with key %lx to pos %lx \n", key, newPos);
// iterate over table and assert that it's in id order
table->GetCount(GetEnv(), &rowCount);
mdbOid oid;
PRUint32 tableRowIndex = 0;
mdb_id prevId = 0;
while (tableRowIndex < rowCount)
{
nsresult ret = table->PosToOid (m_mdbEnv, tableRowIndex++, &oid);
if (tableRowIndex > 1 && oid.mOid_Id <= prevId)
{
NS_ASSERTION(PR_FALSE, "inserting row into cached hits table, not sorted correctly");
printf("key %lx is before or equal %lx \n", prevId, oid.mOid_Id);
}
prevId = oid.mOid_Id;
}
#endif
}
}
// if (aAdd)
// if we need to add this hdr, we need to insert it in key order.
return NS_OK;
}
NS_IMETHODIMP
nsMsgDatabase::HdrIsInCache(const char* aSearchFolderUri, nsIMsgDBHdr *aHdr, PRBool *aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
nsCOMPtr <nsIMdbTable> table;
nsresult err = GetSearchResultsTable(aSearchFolderUri, PR_TRUE, getter_AddRefs(table));
NS_ENSURE_SUCCESS(err, err);
nsMsgKey key;
aHdr->GetMessageKey(&key);
mdbOid rowObjectId;
rowObjectId.mOid_Id = key;
rowObjectId.mOid_Scope = m_hdrRowScopeToken;
mdb_bool hasOid;
err = table->HasOid(GetEnv(), &rowObjectId, &hasOid);
*aResult = hasOid;
return err;
}