/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "msgCore.h" #include "nsMsgSearchCore.h" #include "nsMsgSearchAdapter.h" #include "nsMsgSearchBoolExpression.h" #include "nsMsgSearchSession.h" #include "nsMsgResultElement.h" #include "nsMsgSearchTerm.h" #include "nsMsgSearchScopeTerm.h" #include "nsIMsgMessageService.h" #include "nsMsgUtils.h" #include "nsIMsgSearchNotify.h" #include "nsIMsgMailSession.h" #include "nsIMsgWindow.h" #include "nsMsgFolderFlags.h" #include "nsMsgLocalSearch.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" NS_IMPL_ISUPPORTS(nsMsgSearchSession, nsIMsgSearchSession, nsIUrlListener, nsISupportsWeakReference) nsMsgSearchSession::nsMsgSearchSession() { m_sortAttribute = nsMsgSearchAttrib::Sender; m_idxRunningScope = 0; m_handlingError = false; m_expressionTree = nullptr; m_searchPaused = false; m_iListener = -1; } nsMsgSearchSession::~nsMsgSearchSession() { InterruptSearch(); delete m_expressionTree; DestroyScopeList(); DestroyTermList(); } NS_IMETHODIMP nsMsgSearchSession::AddSearchTerm(nsMsgSearchAttribValue attrib, nsMsgSearchOpValue op, nsIMsgSearchValue* value, bool BooleanANDp, const char* customString) { // stupid gcc nsMsgSearchBooleanOperator boolOp; if (BooleanANDp) boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanAND; else boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanOR; nsMsgSearchTerm* pTerm = new nsMsgSearchTerm(attrib, op, value, boolOp, customString); NS_ENSURE_TRUE(pTerm, NS_ERROR_OUT_OF_MEMORY); m_termList.AppendElement(pTerm); // force the expression tree to rebuild whenever we change the terms delete m_expressionTree; m_expressionTree = nullptr; return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::AppendTerm(nsIMsgSearchTerm* aTerm) { NS_ENSURE_ARG_POINTER(aTerm); delete m_expressionTree; m_expressionTree = nullptr; m_termList.AppendElement(aTerm); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::GetSearchTerms(nsTArray>& terms) { terms = m_termList.Clone(); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::SetSearchTerms( nsTArray> const& terms) { m_termList = terms.Clone(); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::CreateTerm(nsIMsgSearchTerm** aResult) { NS_ENSURE_ARG_POINTER(aResult); NS_ADDREF(*aResult = new nsMsgSearchTerm); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::RegisterListener( nsIMsgSearchNotify* aListener, int32_t aNotifyFlags) { NS_ENSURE_ARG_POINTER(aListener); m_listenerList.AppendElement(aListener); m_listenerFlagList.AppendElement(aNotifyFlags); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::UnregisterListener( nsIMsgSearchNotify* aListener) { NS_ENSURE_ARG_POINTER(aListener); size_t listenerIndex = m_listenerList.IndexOf(aListener); if (listenerIndex != m_listenerList.NoIndex) { m_listenerList.RemoveElementAt(listenerIndex); m_listenerFlagList.RemoveElementAt(listenerIndex); // Adjust our iterator if it is active. // Removal of something at a higher index than the iterator does not affect // it; we only care if the the index we were pointing at gets shifted down, // in which case we also want to shift down. if (m_iListener != -1 && (signed)listenerIndex <= m_iListener) m_iListener--; } return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::GetNumSearchTerms(uint32_t* aNumSearchTerms) { NS_ENSURE_ARG(aNumSearchTerms); *aNumSearchTerms = m_termList.Length(); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::GetNthSearchTerm(int32_t whichTerm, nsMsgSearchAttribValue attrib, nsMsgSearchOpValue op, nsIMsgSearchValue* value) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgSearchSession::CountSearchScopes(int32_t* _retval) { NS_ENSURE_ARG_POINTER(_retval); *_retval = m_scopeList.Length(); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::GetNthSearchScope(int32_t which, nsMsgSearchScopeValue* scopeId, nsIMsgFolder** folder) { NS_ENSURE_ARG_POINTER(scopeId); NS_ENSURE_ARG_POINTER(folder); nsMsgSearchScopeTerm* scopeTerm = m_scopeList.SafeElementAt(which, nullptr); NS_ENSURE_ARG(scopeTerm); *scopeId = scopeTerm->m_attribute; NS_IF_ADDREF(*folder = scopeTerm->m_folder); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::AddScopeTerm(nsMsgSearchScopeValue scope, nsIMsgFolder* folder) { if (scope != nsMsgSearchScope::allSearchableGroups) { NS_ASSERTION(folder, "need folder if not searching all groups"); NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER); } nsMsgSearchScopeTerm* pScopeTerm = new nsMsgSearchScopeTerm(this, scope, folder); NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY); m_scopeList.AppendElement(pScopeTerm); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::AddDirectoryScopeTerm(nsMsgSearchScopeValue scope) { nsMsgSearchScopeTerm* pScopeTerm = new nsMsgSearchScopeTerm(this, scope, nullptr); NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY); m_scopeList.AppendElement(pScopeTerm); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::ClearScopes() { DestroyScopeList(); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::ScopeUsesCustomHeaders(nsMsgSearchScopeValue scope, void* selection, bool forFilters, bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgSearchSession::IsStringAttribute(nsMsgSearchAttribValue attrib, bool* _retval) { // Is this check needed? NS_ENSURE_ARG(_retval); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgSearchSession::AddAllScopes(nsMsgSearchScopeValue attrib) { // don't think this is needed. return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgSearchSession::Search(nsIMsgWindow* aWindow) { nsresult rv = Initialize(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr listener; m_iListener = 0; while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) { listener = m_listenerList[m_iListener]; int32_t listenerFlags = m_listenerFlagList[m_iListener++]; if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onNewSearch)) listener->OnNewSearch(); } m_iListener = -1; m_msgWindowWeak = do_GetWeakReference(aWindow); return BeginSearching(); } NS_IMETHODIMP nsMsgSearchSession::InterruptSearch() { nsCOMPtr msgWindow(do_QueryReferent(m_msgWindowWeak)); if (msgWindow) { EnableFolderNotifications(true); if (m_idxRunningScope < m_scopeList.Length()) msgWindow->StopUrls(); while (m_idxRunningScope < m_scopeList.Length()) { ReleaseFolderDBRef(); m_idxRunningScope++; } // m_idxRunningScope = m_scopeList.Length() so it will make us not run // another url } if (m_backgroundTimer) { m_backgroundTimer->Cancel(); NotifyListenersDone(NS_MSG_SEARCH_INTERRUPTED); m_backgroundTimer = nullptr; } return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::PauseSearch() { if (m_backgroundTimer) { m_backgroundTimer->Cancel(); m_searchPaused = true; return NS_OK; } else return NS_ERROR_FAILURE; } NS_IMETHODIMP nsMsgSearchSession::ResumeSearch() { if (m_searchPaused) { m_searchPaused = false; return StartTimer(); } else return NS_ERROR_FAILURE; } NS_IMETHODIMP nsMsgSearchSession::GetNumResults(int32_t* aNumResults) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgSearchSession::SetWindow(nsIMsgWindow* aWindow) { m_msgWindowWeak = do_GetWeakReference(aWindow); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::GetWindow(nsIMsgWindow** aWindow) { NS_ENSURE_ARG_POINTER(aWindow); *aWindow = nullptr; nsCOMPtr msgWindow(do_QueryReferent(m_msgWindowWeak)); msgWindow.forget(aWindow); return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::OnStartRunningUrl(nsIURI* url) { return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::OnStopRunningUrl(nsIURI* url, nsresult aExitCode) { nsCOMPtr runningAdapter; nsresult rv = GetRunningAdapter(getter_AddRefs(runningAdapter)); // tell the current adapter that the current url has run. if (NS_SUCCEEDED(rv) && runningAdapter) { runningAdapter->CurrentUrlDone(aExitCode); EnableFolderNotifications(true); ReleaseFolderDBRef(); } if (++m_idxRunningScope < m_scopeList.Length()) DoNextSearch(); else NotifyListenersDone(aExitCode); return NS_OK; } nsresult nsMsgSearchSession::Initialize() { // Loop over scope terms, initializing an adapter per term. This // architecture is necessitated by two things: // 1. There might be more than one kind of adapter per if online // *and* offline mail mail folders are selected, or if newsgroups // belonging to Dredd *and* INN are selected // 2. Most of the protocols are only capable of searching one scope at a // time, so we'll do each scope in a separate adapter on the client nsMsgSearchScopeTerm* scopeTerm = nullptr; nsresult rv = NS_OK; uint32_t numTerms = m_termList.Length(); // Ensure that the FE has added scopes and terms to this search NS_ASSERTION(numTerms > 0, "no terms to search!"); if (numTerms == 0) return NS_MSG_ERROR_NO_SEARCH_VALUES; // if we don't have any search scopes to search, return that code. if (m_scopeList.Length() == 0) return NS_MSG_ERROR_INVALID_SEARCH_SCOPE; m_runningUrl.Truncate(); // clear out old url, if any. m_idxRunningScope = 0; // If this term list (loosely specified here by the first term) should be // scheduled in parallel, build up a list of scopes to do the round-robin // scheduling for (uint32_t i = 0; i < m_scopeList.Length() && NS_SUCCEEDED(rv); i++) { scopeTerm = m_scopeList.ElementAt(i); // NS_ASSERTION(scopeTerm->IsValid()); rv = scopeTerm->InitializeAdapter(m_termList); } return rv; } nsresult nsMsgSearchSession::BeginSearching() { // Here's a sloppy way to start the URL, but I don't really have time to // unify the scheduling mechanisms. If the first scope is a newsgroup, and // it's not Dredd-capable, we build the URL queue. All other searches can be // done with one URL nsCOMPtr msgWindow(do_QueryReferent(m_msgWindowWeak)); if (msgWindow) msgWindow->SetStopped(false); return DoNextSearch(); } nsresult nsMsgSearchSession::DoNextSearch() { nsMsgSearchScopeTerm* scope = m_scopeList.ElementAt(m_idxRunningScope); if (scope->m_attribute == nsMsgSearchScope::onlineMail || (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer)) { if (scope->m_adapter) { m_runningUrl.Truncate(); scope->m_adapter->GetEncoding(getter_Copies(m_runningUrl)); } NS_ENSURE_STATE(!m_runningUrl.IsEmpty()); return GetNextUrl(); } else { return SearchWOUrls(); } } nsresult nsMsgSearchSession::GetNextUrl() { nsCOMPtr msgService; bool stopped = false; nsCOMPtr msgWindow(do_QueryReferent(m_msgWindowWeak)); if (msgWindow) msgWindow->GetStopped(&stopped); if (stopped) return NS_OK; nsMsgSearchScopeTerm* currentTerm = GetRunningScope(); NS_ENSURE_TRUE(currentTerm, NS_ERROR_NULL_POINTER); EnableFolderNotifications(false); nsCOMPtr folder = currentTerm->m_folder; if (folder) { nsCString folderUri; folder->GetURI(folderUri); nsresult rv = GetMessageServiceFromURI(folderUri, getter_AddRefs(msgService)); if (NS_SUCCEEDED(rv) && msgService && currentTerm) msgService->Search(this, msgWindow, currentTerm->m_folder, m_runningUrl); return rv; } return NS_OK; } /* static */ void nsMsgSearchSession::TimerCallback(nsITimer* aTimer, void* aClosure) { NS_ENSURE_TRUE_VOID(aClosure); nsMsgSearchSession* searchSession = (nsMsgSearchSession*)aClosure; bool done; bool stopped = false; searchSession->TimeSlice(&done); nsCOMPtr msgWindow( do_QueryReferent(searchSession->m_msgWindowWeak)); if (msgWindow) msgWindow->GetStopped(&stopped); if (done || stopped) { if (aTimer) aTimer->Cancel(); searchSession->m_backgroundTimer = nullptr; if (searchSession->m_idxRunningScope < searchSession->m_scopeList.Length()) searchSession->DoNextSearch(); else searchSession->NotifyListenersDone(NS_OK); } } nsresult nsMsgSearchSession::StartTimer() { nsresult rv; m_backgroundTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); NS_ENSURE_SUCCESS(rv, rv); m_backgroundTimer->InitWithNamedFuncCallback( TimerCallback, (void*)this, 0, nsITimer::TYPE_REPEATING_SLACK, "nsMsgSearchSession::TimerCallback"); TimerCallback(m_backgroundTimer, this); return NS_OK; } nsresult nsMsgSearchSession::SearchWOUrls() { EnableFolderNotifications(false); return StartTimer(); } NS_IMETHODIMP nsMsgSearchSession::GetRunningAdapter(nsIMsgSearchAdapter** aSearchAdapter) { NS_ENSURE_ARG_POINTER(aSearchAdapter); *aSearchAdapter = nullptr; nsMsgSearchScopeTerm* scope = GetRunningScope(); if (scope) { NS_IF_ADDREF(*aSearchAdapter = scope->m_adapter); } return NS_OK; } NS_IMETHODIMP nsMsgSearchSession::AddSearchHit(nsIMsgDBHdr* aHeader, nsIMsgFolder* aFolder) { nsCOMPtr listener; m_iListener = 0; while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) { listener = m_listenerList[m_iListener]; int32_t listenerFlags = m_listenerFlagList[m_iListener++]; if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchHit)) listener->OnSearchHit(aHeader, aFolder); } m_iListener = -1; return NS_OK; } nsresult nsMsgSearchSession::NotifyListenersDone(nsresult aStatus) { // need to stabilize "this" in case one of the listeners releases the last // reference to us. RefPtr kungFuDeathGrip(this); nsCOMPtr listener; m_iListener = 0; while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) { listener = m_listenerList[m_iListener]; int32_t listenerFlags = m_listenerFlagList[m_iListener++]; if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchDone)) listener->OnSearchDone(aStatus); } m_iListener = -1; return NS_OK; } void nsMsgSearchSession::DestroyScopeList() { nsMsgSearchScopeTerm* scope = nullptr; for (int32_t i = m_scopeList.Length() - 1; i >= 0; i--) { scope = m_scopeList.ElementAt(i); // NS_ASSERTION (scope->IsValid(), "invalid search scope"); if (scope->m_adapter) scope->m_adapter->ClearScope(); } m_scopeList.Clear(); } void nsMsgSearchSession::DestroyTermList() { m_termList.Clear(); } nsMsgSearchScopeTerm* nsMsgSearchSession::GetRunningScope() { return m_scopeList.SafeElementAt(m_idxRunningScope, nullptr); } nsresult nsMsgSearchSession::TimeSlice(bool* aDone) { // we only do serial for now. return TimeSliceSerial(aDone); } void nsMsgSearchSession::ReleaseFolderDBRef() { nsMsgSearchScopeTerm* scope = GetRunningScope(); if (!scope) return; bool isOpen = false; uint32_t flags; nsCOMPtr folder; scope->GetFolder(getter_AddRefs(folder)); nsCOMPtr mailSession = do_GetService("@mozilla.org/messenger/services/session;1"); if (!mailSession || !folder) return; mailSession->IsFolderOpenInWindow(folder, &isOpen); folder->GetFlags(&flags); /*we don't null out the db reference for inbox because inbox is like the "main" folder and performance outweighs footprint */ if (!isOpen && !(nsMsgFolderFlags::Inbox & flags)) folder->SetMsgDatabase(nullptr); } nsresult nsMsgSearchSession::TimeSliceSerial(bool* aDone) { // This version of TimeSlice runs each scope term one at a time, and waits // until one scope term is finished before starting another one. When we're // searching the local disk, this is the fastest way to do it. NS_ENSURE_ARG_POINTER(aDone); nsMsgSearchScopeTerm* scope = GetRunningScope(); if (!scope) { *aDone = true; return NS_OK; } nsresult rv = scope->TimeSlice(aDone); if (*aDone || NS_FAILED(rv)) { EnableFolderNotifications(true); ReleaseFolderDBRef(); m_idxRunningScope++; EnableFolderNotifications(false); // check if the next scope is an online search; if so, // set *aDone to true so that we'll try to run the next // search in TimerCallback. scope = GetRunningScope(); if (scope && (scope->m_attribute == nsMsgSearchScope::onlineMail || (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer))) { *aDone = true; return rv; } } *aDone = false; return rv; } void nsMsgSearchSession::EnableFolderNotifications(bool aEnable) { nsMsgSearchScopeTerm* scope = GetRunningScope(); if (scope) { nsCOMPtr folder; scope->GetFolder(getter_AddRefs(folder)); if (folder) // enable msg count notifications folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, aEnable); } } // this method is used for adding new hdrs to quick search view NS_IMETHODIMP nsMsgSearchSession::MatchHdr(nsIMsgDBHdr* aMsgHdr, nsIMsgDatabase* aDatabase, bool* aResult) { nsMsgSearchScopeTerm* scope = m_scopeList.SafeElementAt(0, nullptr); if (scope) { if (!scope->m_adapter) scope->InitializeAdapter(m_termList); if (scope->m_adapter) { nsAutoString nullCharset, folderCharset; scope->m_adapter->GetSearchCharsets(nullCharset, folderCharset); NS_ConvertUTF16toUTF8 charset(folderCharset.get()); nsMsgSearchOfflineMail::MatchTermsForSearch( aMsgHdr, m_termList, charset.get(), scope, aDatabase, &m_expressionTree, aResult); } } return NS_OK; }