12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <kopano/platform.h>
- #include <utility>
- #include <kopano/lockhelper.hpp>
- #include <pthread.h>
- #include <mapidefs.h>
- #include <mapitags.h>
- #include "ECMAPI.h"
- #include "ECSession.h"
- #include <kopano/ECKeyTable.h>
- #include <kopano/ECLogger.h>
- #include "ECStoreObjectTable.h"
- #include "ECSubRestriction.h"
- #include "ECSearchFolders.h"
- #include "ECSessionManager.h"
- #include "ECStatsCollector.h"
- #include "ECIndexer.h"
- #include <kopano/ECTags.h>
- #include "cmdutil.hpp"
- #include <kopano/stringutil.h>
- #include "ECSearchClient.h"
- namespace KC {
- struct THREADINFO {
- SEARCHFOLDER *lpFolder;
- ECSearchFolders *lpSearchFolders;
- };
- ECSearchFolders::ECSearchFolders(ECSessionManager *lpSessionManager,
- ECDatabaseFactory *lpFactory) :
- m_lpDatabaseFactory(lpFactory), m_lpSessionManager(lpSessionManager)
- {
- pthread_create(&m_threadProcess, NULL, ECSearchFolders::ProcessThread, (void *)this);
- set_thread_name(m_threadProcess, "SearchFolders");
- }
- ECSearchFolders::~ECSearchFolders() {
- ulock_rec l_sf(m_mutexMapSearchFolders);
- for (auto &p : m_mapSearchFolders) {
- for (auto &f : p.second)
- delete f.second;
- p.second.clear();
- }
-
- m_mapSearchFolders.clear();
- l_sf.unlock();
-
- ulock_rec l_events(m_mutexEvents);
- m_bExitThread = true;
- m_condEvents.notify_all();
- l_events.unlock();
- pthread_join(m_threadProcess, NULL);
- }
- // Only loads the search criteria for all search folders. Used once at boot time
- ECRESULT ECSearchFolders::LoadSearchFolders()
- {
- ECRESULT er = erSuccess;
- ECDatabase *lpDatabase = NULL;
- DB_RESULT lpResult;
- DB_ROW lpRow = NULL;
- unsigned int ulFolderId = 0;
- unsigned int ulStoreId = 0;
- unsigned int ulStatus = 0;
- // Search for all folders with PR_EC_SEARCHCRIT that are not deleted. Note that this query can take quite some time on large databases
- std::string strQuery = "SELECT hierarchy.id, properties.val_ulong FROM hierarchy LEFT JOIN properties ON properties.hierarchyid=hierarchy.id AND properties.tag=" + stringify(PROP_ID(PR_EC_SEARCHFOLDER_STATUS)) +" AND properties.type=" + stringify(PROP_TYPE(PR_EC_SEARCHFOLDER_STATUS)) + " WHERE hierarchy.type=3 AND hierarchy.flags=2";
- struct searchCriteria *lpSearchCriteria = NULL;
-
- // Get database
- er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess)
- goto exit;
- er = lpDatabase->DoSelect(strQuery, &lpResult);
- if(er != erSuccess)
- goto exit;
-
- while((lpRow = lpDatabase->FetchRow(lpResult))) {
- if(lpRow[0] == NULL)
- continue;
-
- if(lpRow[1] != NULL)
- ulStatus = atoi(lpRow[1]);
- else
- ulStatus = EC_SEARCHFOLDER_STATUS_RUNNING; // this is the default if no property is found
-
- ulFolderId = atoi(lpRow[0]);
-
- er = m_lpSessionManager->GetCacheManager()->GetStore(ulFolderId, &ulStoreId, NULL);
- if(er != erSuccess) {
- er = erSuccess;
- continue;
- }
-
- // Only load the table if it is not stopped
- if(ulStatus != EC_SEARCHFOLDER_STATUS_STOPPED) {
- er = LoadSearchCriteria(ulStoreId, ulFolderId, &lpSearchCriteria);
- if(er != erSuccess) {
- er = erSuccess;
- continue;
- }
-
- if(ulStatus == EC_SEARCHFOLDER_STATUS_REBUILD)
- ec_log_info("Rebuilding search folder %d", ulFolderId);
-
- // If the folder was in the process of rebuilding, then completely rebuild the search results (we don't know how far the search got)
- er = AddSearchFolder(ulStoreId, ulFolderId, ulStatus == EC_SEARCHFOLDER_STATUS_REBUILD, lpSearchCriteria);
- if(er != erSuccess) {
- er = erSuccess; // just try to skip the error
- continue;
- }
-
-
- if(lpSearchCriteria) {
- FreeSearchCriteria(lpSearchCriteria);
- lpSearchCriteria = NULL;
- }
- }
- }
- exit:
- if(lpSearchCriteria)
- FreeSearchCriteria(lpSearchCriteria);
-
- return er;
- }
- // Called from IMAPIContainer::SetSearchCriteria
- ECRESULT ECSearchFolders::SetSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCriteria)
- {
- ECRESULT er;
- if(lpSearchCriteria == NULL) {
- /* Always return successful, so that Outlook 2007 works */
- CancelSearchFolder(ulStoreId, ulFolderId);
- } else {
- er = AddSearchFolder(ulStoreId, ulFolderId, true, lpSearchCriteria);
- if(er != erSuccess)
- return er;
- er = SaveSearchCriteria(ulStoreId, ulFolderId, lpSearchCriteria);
- if(er != erSuccess)
- return er;
- }
- return erSuccess;
- }
- // Gets the search criteria from in-memory
- ECRESULT ECSearchFolders::GetSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria **lppSearchCriteria, unsigned int *lpulFlags)
- {
- ECRESULT er = erSuccess;
- FOLDERIDSEARCH::const_iterator iterFolder;
- scoped_rlock l_sf(m_mutexMapSearchFolders);
- // See if there are any searches for this store first
- auto iterStore = m_mapSearchFolders.find(ulStoreId);
- if (iterStore == m_mapSearchFolders.cend())
- return KCERR_NOT_INITIALIZED;
- iterFolder = iterStore->second.find(ulFolderId);
- if (iterFolder == iterStore->second.cend())
- return KCERR_NOT_INITIALIZED;
-
- er = CopySearchCriteria(NULL, iterFolder->second->lpSearchCriteria, lppSearchCriteria);
- if(er != erSuccess)
- return er;
- er = GetState(ulStoreId, ulFolderId, lpulFlags);
- if(er != erSuccess)
- return er;
-
- // Add recursive flag if necessary
- *lpulFlags |= iterFolder->second->lpSearchCriteria->ulFlags & SEARCH_RECURSIVE;
- return er;
- }
- // Add or modify a search folder
- ECRESULT ECSearchFolders::AddSearchFolder(unsigned int ulStoreId, unsigned int ulFolderId, bool bReStartSearch, struct searchCriteria *lpSearchCriteria)
- {
- ECRESULT er = erSuccess;
- struct searchCriteria *lpCriteria = NULL;
- STOREFOLDERIDSEARCH::iterator iterStore;
- SEARCHFOLDER *lpSearchFolder = NULL;
- unsigned int ulParent = 0;
- ulock_rec l_sf(m_mutexMapSearchFolders, std::defer_lock_t());
- if(lpSearchCriteria == NULL) {
- er = LoadSearchCriteria(ulStoreId, ulFolderId, &lpCriteria);
-
- if(er != erSuccess)
- goto exit;
-
- lpSearchCriteria = lpCriteria;
- }
- // Cancel any running searches
- CancelSearchFolder(ulStoreId, ulFolderId);
- if(bReStartSearch) {
- // Set the status of the table as rebuilding
- SetStatus(ulFolderId, EC_SEARCHFOLDER_STATUS_REBUILD);
-
- // Remove any results for this folder if we're restarting the search
- er = ResetResults(ulStoreId, ulFolderId);
- if(er != erSuccess)
- goto exit;
- }
-
- // Tell tables that we've reset:
- // 1. Reset cache for this folder
- m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulFolderId);
-
- // 2. Send reset table contents notification
- er = m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_CHANGE, 0, ulFolderId, 0, MAPI_MESSAGE);
- if(er != erSuccess)
- goto exit;
-
- // 3. Send change tables viewing us (hierarchy tables)
- if(m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParent) == erSuccess) {
- er = m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulFolderId, MAPI_FOLDER);
- if(er != erSuccess)
- goto exit;
- }
- lpSearchFolder = new SEARCHFOLDER(ulStoreId, ulFolderId);
- er = CopySearchCriteria(NULL, lpSearchCriteria, &lpSearchFolder->lpSearchCriteria);
- if(er != erSuccess) {
- delete lpSearchFolder;
- goto exit;
- }
- // Get searches for this store, or add it to the list.
- l_sf.lock();
- iterStore = m_mapSearchFolders.insert(STOREFOLDERIDSEARCH::value_type(ulStoreId, FOLDERIDSEARCH())).first;
- iterStore->second.insert(FOLDERIDSEARCH::value_type(ulFolderId, lpSearchFolder));
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_COUNT);
-
- if(bReStartSearch) {
- lpSearchFolder->bThreadFree = false;
-
- auto ti = new THREADINFO;
- ti->lpSearchFolders = this;
- ti->lpFolder = lpSearchFolder;
-
- // Insert the actual folder with the criteria
- // Start the thread (will store the thread id in the original list)
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_attr_setstacksize(&attr, 512*1024); // 512KB stack space for search threads
- int err = 0;
-
- if((err = pthread_create(&lpSearchFolder->sThreadId, &attr, ECSearchFolders::SearchThread, (void *)ti)) != 0) {
- ec_log_crit("Unable to spawn thread for search: %s", strerror(err));
- er = KCERR_NOT_ENOUGH_MEMORY;
- }
- set_thread_name(lpSearchFolder->sThreadId, "SearchFolders:Folder");
- }
- l_sf.unlock();
- exit:
- if (lpCriteria)
- FreeSearchCriteria(lpCriteria);
- return er;
- }
- // See if a folder is a search folder
- ECRESULT ECSearchFolders::IsSearchFolder(unsigned int ulStoreID, unsigned int ulFolderId)
- {
- ECRESULT er = erSuccess;
- ECDatabase *lpDatabase = NULL;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- /* Get database */
- er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
- if (er != erSuccess)
- return er;
- // Find out what kind of table this is
- strQuery = "SELECT flags FROM hierarchy WHERE id=" + stringify(ulFolderId) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if (lpDBRow == nullptr || lpDBRow[0] == nullptr)
- return KCERR_NOT_FOUND;
- if (atoui(lpDBRow[0]) != FOLDER_SEARCH)
- return KCERR_NOT_FOUND;
- return erSuccess;
- }
- // Cancel a search: stop any rebuild thread and stop processing updates for this search folder
- ECRESULT ECSearchFolders::CancelSearchFolder(unsigned int ulStoreID, unsigned int ulFolderId)
- {
- SEARCHFOLDER *lpFolder = NULL;
- // Lock the list, preventing other Cancel requests messing with the thread
- ulock_rec l_sf(m_mutexMapSearchFolders);
- auto iterStore = m_mapSearchFolders.find(ulStoreID);
- if (iterStore == m_mapSearchFolders.cend())
- return KCERR_NOT_FOUND;
- auto iterFolder = iterStore->second.find(ulFolderId);
- if (iterFolder == iterStore->second.cend())
- return KCERR_NOT_FOUND;
-
- lpFolder = iterFolder->second;
-
- // Remove the item from the list
- iterStore->second.erase(iterFolder);
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_COUNT, -1);
- l_sf.unlock();
- DestroySearchFolder(lpFolder);
- return erSuccess;
- }
- void ECSearchFolders::DestroySearchFolder(SEARCHFOLDER *lpFolder)
- {
- unsigned int ulFolderId = lpFolder->ulFolderId;
- // Nobody can access lpFolder now, except for us and the search thread
- // FIXME check this assumption !!!
-
- // Signal the thread to exit
- lpFolder->bThreadExit = true;
-
- /*
- * Wait for the thread to signal that lpFolder is no longer in use by
- * the thread The condition is used for all threads, so it may have
- * been fired for a different thread. This is efficient enough.
- */
- ulock_normal lk(lpFolder->mMutexThreadFree);
- m_condThreadExited.wait(lk, [=](void) { return lpFolder->bThreadFree; });
- lk.unlock();
- // Nobody is using lpFolder now
- delete lpFolder;
-
- // Set the search as stopped in the database
- SetStatus(ulFolderId, EC_SEARCHFOLDER_STATUS_STOPPED);
- }
- /**
- * Cancel all the search folders on a store and removing the results
- */
- ECRESULT ECSearchFolders::RemoveSearchFolder(unsigned int ulStoreID)
- {
- unsigned int ulFolderID;
- std::list<SEARCHFOLDER*> listSearchFolders;
- // Lock the list, preventing other Cancel requests messing with the thread
- ulock_rec l_sf(m_mutexMapSearchFolders);
- auto iterStore = m_mapSearchFolders.find(ulStoreID);
- if (iterStore == m_mapSearchFolders.cend())
- return KCERR_NOT_FOUND;
- for (const auto &p : iterStore->second)
- listSearchFolders.push_back(p.second);
- iterStore->second.clear();
-
- // Remove store from list, items of the store will be delete in 'DestroySearchFolder'
- m_mapSearchFolders.erase(iterStore);
- l_sf.unlock();
- //@fixme: server shutdown can result in a crash?
- for (auto srfolder : listSearchFolders) {
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_COUNT, -1);
- ulFolderID = srfolder->ulFolderId;
- // Wait and free searchfolder data
- DestroySearchFolder(srfolder);
- // Remove results from database
- ResetResults(ulStoreID, ulFolderID);
- }
- return erSuccess;
- }
- // Removing a search folder is subtly different from cancelling it; removing a search folder
- // also removes all search results
- ECRESULT ECSearchFolders::RemoveSearchFolder(unsigned int ulStoreId, unsigned int ulFolderId)
- {
- // Cancel any running (rebuilding) searches
- CancelSearchFolder(ulStoreId, ulFolderId);
- // Ignore errors
-
- // Remove results from database
- ResetResults(ulStoreId, ulFolderId);
- return erSuccess;
- }
-
- // WARNING: THIS FUNCTION IS *NOT* THREADSAFE. IT SHOULD ONLY BE CALLED AT STARTUP WHILE SINGLE-THREADED
- ECRESULT ECSearchFolders::RestartSearches()
- {
- ECRESULT er = erSuccess;
- ec_log_crit("Starting rebuild of search folders... This may take a while.");
-
- for (const auto &store_p : m_mapSearchFolders) {
- ec_log_crit(" Rebuilding searchfolders of store %d", store_p.first);
- for (const auto &folder_p : store_p.second) {
- ResetResults(store_p.first, folder_p.first);
- Search(store_p.first, folder_p.first, folder_p.second->lpSearchCriteria, NULL, false);
- }
- }
- ec_log_info("Finished rebuild.");
-
- return er;
- }
-
- // Should be called for *any* change of *any* message object in the database.
- ECRESULT ECSearchFolders::UpdateSearchFolders(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int ulObjId, ECKeyTable::UpdateType ulType)
- {
- ECRESULT er = erSuccess;
-
- EVENT ev;
-
- ev.ulStoreId = ulStoreId;
- ev.ulFolderId = ulFolderId;
- ev.ulObjectId = ulObjId;
- ev.ulType = ulType;
-
- scoped_rlock l_ev(m_mutexEvents);
- // Add the event to the queue
- m_lstEvents.push_back(std::move(ev));
- /*
- * Signal a change in the queue (actually only needed for the first
- * event, but this wastes almost no time and is safer.
- */
- m_condEvents.notify_all();
- return er;
- }
- // Process a list of message changes in a single folder of a certain type
- ECRESULT ECSearchFolders::ProcessMessageChange(unsigned int ulStoreId, unsigned int ulFolderId, ECObjectTableList *lstObjectIDs, ECKeyTable::UpdateType ulType)
- {
- ECRESULT er = erSuccess;
- STOREFOLDERIDSEARCH::const_iterator iterStore;
- bool bIsInTargetFolder = false;
- std::set<unsigned int> setParents;
- unsigned int ulOwner = 0;
- ECSession *lpSession = NULL;
- ECODStore ecOBStore;
- SUBRESTRICTIONRESULTS *lpSubResults = NULL;
- struct rowSet *lpRowSet = NULL;
- struct propTagArray *lpPropTags = NULL;
- unsigned int ulParent = 0;
- unsigned int ulFlags = 0;
- unsigned int ulSCFolderId = 0;
- int lCount = 0; // Number of messages added, positive means more added, negative means more discarded
- int lUnreadCount = 0; // Same, but for unread count
- ECDatabase *lpDatabase = NULL;
- std::list<ULONG> lstPrefix;
- bool fInserted = false;
-
- lstPrefix.push_back(PR_MESSAGE_FLAGS);
- ECLocale locale = m_lpSessionManager->GetSortLocale(ulStoreId);
- ulock_rec l_sf(m_mutexMapSearchFolders);
- er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess)
- goto exit;
- iterStore = m_mapSearchFolders.find(ulStoreId);
- if (iterStore == m_mapSearchFolders.cend())
- // There are no search folders in the target store. We will therefore never match any search
- // result and might as well exit now.
- goto exit;
- // OPTIMIZATION: if a target folder == root folder of ulStoreId, and a recursive searchfolder, then
- // the following check is always TRUE
- // Get the owner of the search folder. This *could* be different from the owner of the objects!
- er = m_lpSessionManager->GetCacheManager()->GetObject(ulStoreId, NULL, &ulOwner, NULL, NULL);
- if(er != erSuccess)
- goto exit;
- // FIXME FIXME FIXME we still need to check MAPI_ASSOCIATED and MSGFLAG_DELETED and exclude them.. better if the caller does this.
- // We now have to see if the folder in which the object resides is actually a target of a search folder.
- // We do this by checking whether the specified folder is a searchfolder target folder, or a child of
- // a target folder if it is a recursive search.
-
- // Loop through search folders for this store
- for (const auto &folder : iterStore->second) {
- ULONG ulAttempts = 4; // Random number
-
- do {
- bIsInTargetFolder = false;
- lCount = 0;
- lUnreadCount = 0;
-
- if (folder.second->lpSearchCriteria->lpFolders == NULL ||
- folder.second->lpSearchCriteria->lpRestrict == NULL)
- continue;
-
- er = lpDatabase->Begin();
- if (er != erSuccess)
- goto exit;
-
- // Lock searchfolder
- WITH_SUPPRESSED_LOGGING(lpDatabase)
- er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " +
- stringify(folder.first) + " FOR UPDATE", NULL);
- if (er == KCERR_DATABASE_ERROR) {
- DB_ERROR dberr = lpDatabase->GetLastError();
- if (dberr != DB_E_LOCK_WAIT_TIMEOUT && dberr != DB_E_LOCK_DEADLOCK) {
- ec_log_err("ECSearchFolders::ProcessMessageChange(): select failed");
- goto exit;
- }
- er = lpDatabase->Rollback();
- if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): database rollback failed %d", er);
- goto exit;
- }
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_RETRY);
- continue;
- } else if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): unexpected error %d", er);
- goto exit;
- }
-
- if(ulType != ECKeyTable::TABLE_ROW_DELETE) {
- // Loop through all targets for each searchfolder, if one matches, then match the restriction with the objects
- for (unsigned int i = 0; i < folder.second->lpSearchCriteria->lpFolders->__size; ++i)
- if (m_lpSessionManager->GetCacheManager()->GetObjectFromEntryId(&folder.second->lpSearchCriteria->lpFolders->__ptr[i], &ulSCFolderId) == erSuccess &&
- ulSCFolderId == ulFolderId)
- {
- bIsInTargetFolder = true;
- break;
- }
- if (!bIsInTargetFolder && folder.second->lpSearchCriteria->ulFlags & RECURSIVE_SEARCH) {
- // The item is not in one of the base folders, but it may be in one of children of the folders.
- // We do it in this order because the GetParent() calls below may cause database accesses, so
- // we only actually do those database accesses if we have to.
-
- unsigned int ulAncestor = ulFolderId;
-
- // Get all the parents of this object (usually around 5 or 6)
- setParents.insert(ulFolderId);
-
- while(1) {
- er = m_lpSessionManager->GetCacheManager()->GetParent(ulAncestor, &ulAncestor);
- if(er != erSuccess)
- break;
-
- setParents.insert(ulAncestor);
- }
-
- // setParents now contains all the parent of this object, now we can check if any of the ancestors
- // are in the search target
- for (unsigned int i = 0; i < folder.second->lpSearchCriteria->lpFolders->__size; ++i) {
- if (m_lpSessionManager->GetCacheManager()->GetObjectFromEntryId(&folder.second->lpSearchCriteria->lpFolders->__ptr[i], &ulSCFolderId) != erSuccess)
- continue;
- auto iterParents = setParents.find(ulSCFolderId);
- if (iterParents != setParents.cend()) {
- bIsInTargetFolder = true;
- break;
- }
- }
- }
- } else {
- // Table type DELETE, so the item is definitely not in the search path. Just delete it
- bIsInTargetFolder = false;
- }
-
- // The folder in which the modify message is, is in our search path for this searchfolder
- if(bIsInTargetFolder) {
- // Create a session for the target user
- if(lpSession == NULL) {
- er = m_lpSessionManager->CreateSessionInternal(&lpSession, ulOwner);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): CreateSessionInternal failed %d", er);
- goto exit;
- }
-
- lpSession->Lock();
- }
-
- ecOBStore.ulStoreId = ulStoreId;
- ecOBStore.ulFolderId = 0;
- ecOBStore.ulFlags = 0;
- ecOBStore.ulObjType = MAPI_MESSAGE;
- ecOBStore.lpGuid = NULL;
- if(ulType == ECKeyTable::TABLE_ROW_ADD || ulType == ECKeyTable::TABLE_ROW_MODIFY) {
- std::list<ULONG> lstPropTags;
-
- // Get the restriction ready for this search folder
- er = ECGenericObjectTable::GetRestrictPropTags(folder.second->lpSearchCriteria->lpRestrict, &lstPrefix, &lpPropTags);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): ECGenericObjectTable::GetRestrictPropTags failed %d", er);
- goto exit;
- }
-
- // Get necessary row data for the object
- er = ECStoreObjectTable::QueryRowData(NULL, NULL, lpSession, lstObjectIDs, lpPropTags, &ecOBStore, &lpRowSet, false, false);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): ECStoreObjectTable::QueryRowData failed %d", er);
- goto exit;
- }
-
- er = RunSubRestrictions(lpSession, &ecOBStore, folder.second->lpSearchCriteria->lpRestrict, lstObjectIDs, locale, &lpSubResults);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): RunSubRestrictions failed %d", er);
- goto exit;
- }
- auto iterObjectIDs = lstObjectIDs->cbegin();
- // Check if the item matches for each item
- for (gsoap_size_t i = 0; i < lpRowSet->__size; ++i, ++iterObjectIDs) {
- bool fMatch;
- // Match the restriction
- er = ECGenericObjectTable::MatchRowRestrict(lpSession->GetSessionManager()->GetCacheManager(), &lpRowSet->__ptr[i], folder.second->lpSearchCriteria->lpRestrict, lpSubResults, locale, &fMatch);
- if(er == erSuccess) {
- if (fMatch) {
- if(lpRowSet->__ptr[i].__ptr[0].ulPropTag != PR_MESSAGE_FLAGS)
- continue;
-
- // Get the read flag for this message
- ulFlags = lpRowSet->__ptr[i].__ptr[0].Value.ul & MSGFLAG_READ;
-
- // Update on-disk search folder
- if (AddResults(ulStoreId, folder.first, iterObjectIDs->ulObjId, ulFlags, &fInserted) == erSuccess) {
- if(fInserted) {
- // One more match
- ++lCount;
- if(!ulFlags)
- ++lUnreadCount;
- // Send table notification
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
- } else {
- // Row was modified, so flags has changed. Since the only possible values are MSGFLAG_READ or 0, we know the new flags.
-
- if(ulFlags)
- --lUnreadCount; // New state is read, so old state was unread, so --unread
- else
- ++lUnreadCount; // New state is unread, so old state was read, so ++unread
-
- // Send table notification
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
- }
-
- } else {
- // AddResults will return an error if the call didn't do anything (record was already in the table).
-
- // Even though, we should still send notifications since the row changed
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
- }
- } else if (ulType == ECKeyTable::TABLE_ROW_MODIFY) {
- // Only delete modified items, not new items
- if (DeleteResults(ulStoreId, folder.first, iterObjectIDs->ulObjId, &ulFlags) == erSuccess) {
- --lCount;
- if(!ulFlags)
- --lUnreadCount; // Removed message was unread
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
- }
- }
- // Ignore errors from the updates
- er = erSuccess;
- }
- }
- } else {
- // Message was deleted anyway, update on-disk search folder and send table notification
- for (const auto &obj_id : *lstObjectIDs)
- if (DeleteResults(ulStoreId, folder.first, obj_id.ulObjId, &ulFlags) == erSuccess) {
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE, 0, folder.first, obj_id.ulObjId, MAPI_MESSAGE);
- --lCount;
- if(!ulFlags)
- --lUnreadCount; // Removed message was unread
- }
- }
- if(lpPropTags) {
- FreePropTagArray(lpPropTags);
- lpPropTags = NULL;
- }
- if(lpRowSet) {
- FreeRowSet(lpRowSet, true);
- lpRowSet = NULL;
- }
- if(lpSubResults){
- FreeSubRestrictionResults(lpSubResults);
- lpSubResults = NULL;
- }
- } else {
- // Not in a target folder, remove from search results
- for (const auto &obj_id : *lstObjectIDs)
- if (DeleteResults(ulStoreId, folder.first, obj_id.ulObjId, &ulFlags) == erSuccess) {
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE, 0, folder.first, obj_id.ulObjId, MAPI_MESSAGE);
- --lCount;
- if(!ulFlags)
- --lUnreadCount; // Removed message was unread
- }
- }
- if(lCount || lUnreadCount) {
- // If the searchfolder has changed, update counts and send notifications
- WITH_SUPPRESSED_LOGGING(lpDatabase) {
- er = UpdateFolderCount(lpDatabase, folder.first, PR_CONTENT_COUNT, lCount);
- if (er == erSuccess)
- er = UpdateFolderCount(lpDatabase, folder.first, PR_CONTENT_UNREAD, lUnreadCount);
- }
-
- if (er == KCERR_DATABASE_ERROR) {
- DB_ERROR dberr = lpDatabase->GetLastError();
- if (dberr == DB_E_LOCK_WAIT_TIMEOUT || dberr == DB_E_LOCK_DEADLOCK) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): database error(1) %d", dberr);
- er = lpDatabase->Rollback();
- if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): database rollback failed(1) %d", er);
- goto exit;
- }
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_RETRY);
- continue;
- } else
- goto exit;
- } else if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): unexpected error(1) %d", er);
- goto exit;
- }
-
- m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, folder.first);
- er = m_lpSessionManager->NotificationModified(MAPI_FOLDER, folder.first);
- if (m_lpSessionManager->GetCacheManager()->GetParent(folder.first, &ulParent) == erSuccess)
- er = m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, folder.first, MAPI_FOLDER);
- er = erSuccess; // Ignore errors
- }
-
- er = lpDatabase->Commit();
- if (er == KCERR_DATABASE_ERROR) {
- DB_ERROR dberr = lpDatabase->GetLastError();
- if (dberr == DB_E_LOCK_WAIT_TIMEOUT || dberr == DB_E_LOCK_DEADLOCK) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): database error(2) %d", dberr);
- er = lpDatabase->Rollback();
- if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): database rollback failed(2) %d", er);
- goto exit;
- }
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_RETRY);
- continue;
- } else
- goto exit;
- } else if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::ProcessMessageChange(): unexpected error(2) %d", er);
- goto exit;
- }
-
- break; // Break the do-while loop since we succeeded
- } while (--ulAttempts);
-
- if (ulAttempts == 0) {
- // The only way to get here is if all attempts failed with an SQL error.
- assert(er != KCERR_DATABASE_ERROR);
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_FAIL);
- }
- }
- exit:
- l_sf.unlock();
- if(lpPropTags)
- FreePropTagArray(lpPropTags);
-
- if(lpSession) {
- lpSession->Unlock();
- m_lpSessionManager->RemoveSessionInternal(lpSession);
- }
-
- delete lpSubResults;
-
- if(lpRowSet)
- FreeRowSet(lpRowSet, true);
-
- return er;
- }
- /**
- * Process a set of rows and add them to the searchresults table if they match the restriction
- *
- * Special note on transactioning:
- *
- * If you pass bNotify == false, you must begin()/commit() yourself.
- * If you pass bNotify == true, this function will begin()/commit() for you.
- *
- * @param[in] lpDatabase Database
- * @param[in] lpSession Session for the user owning the searchfolder
- * @param[in] lpRestrict Restriction to test items against
- * @param[in] lpbCancel Stops processing if it is set to TRUE while running (from another thread)
- * @param[in] ulStoreId Store that the searchfolder is in
- * @param[in] ulFolderId ID of the searchfolder
- * @param[in] lpODStore Information for the objects in the table
- * @param[in] ecRows Rows to process
- * @param[in] lpPropTags Precalculated proptags needed to check lpRestrict
- * @param[in] locale Locale to process rows in (for comparisons)
- * @param[in] bNotify TRUE if we should notify tables in this function, see note about transactions above
- * @return result
- */
- ECRESULT ECSearchFolders::ProcessCandidateRows(ECDatabase *lpDatabase,
- ECSession *lpSession, struct restrictTable *lpRestrict, bool *lpbCancel,
- unsigned int ulStoreId, unsigned int ulFolderId, ECODStore *lpODStore,
- ECObjectTableList ecRows, struct propTagArray *lpPropTags,
- const ECLocale &locale, bool bNotify)
- {
- ECRESULT er = erSuccess;
- struct rowSet *lpRowSet = NULL;
- SUBRESTRICTIONRESULTS *lpSubResults = NULL;
- int lCount = 0;
- int lUnreadCount = 0;
- bool fMatch = false;
- unsigned int ulObjFlags = 0;
- unsigned int ulParent = 0;
- std::list<unsigned int> lstMatches;
- std::list<unsigned int> lstFlags;
-
- assert(lpPropTags->__ptr[0] == PR_MESSAGE_FLAGS);
- auto iterRows = ecRows.cbegin();
- if(bNotify) {
- er = lpDatabase->Begin();
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::ProcessCandidateRows() BEGIN failed %d", er);
- goto exit;
- }
-
- er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " + stringify(ulFolderId) + " FOR UPDATE", NULL);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::ProcessCandidateRows() SELECT failed %d", er);
- goto exit;
- }
- }
-
- // Get the row data for the search
- er = ECStoreObjectTable::QueryRowData(NULL, NULL, lpSession, &ecRows, lpPropTags, lpODStore, &lpRowSet, false, false);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::ProcessCandidateRows() ECStoreObjectTable::QueryRowData failed %d", er);
- goto exit;
- }
-
- // Get the subrestriction results for the search
- er = RunSubRestrictions(lpSession, lpODStore, lpRestrict, &ecRows, locale, &lpSubResults);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::ProcessCandidateRows() RunSubRestrictions failed %d", er);
- goto exit;
- }
-
- // Loop through the results data
- lCount=0;
- lUnreadCount=0;
- for (gsoap_size_t j = 0; j< lpRowSet->__size && (!lpbCancel || !*lpbCancel); ++j, ++iterRows) {
- if(ECGenericObjectTable::MatchRowRestrict(lpSession->GetSessionManager()->GetCacheManager(), &lpRowSet->__ptr[j], lpRestrict, lpSubResults, locale, &fMatch) != erSuccess)
- continue;
- if(!fMatch)
- continue;
-
- if(lpRowSet->__ptr[j].__ptr[0].ulPropTag != PR_MESSAGE_FLAGS)
- continue;
-
- ulObjFlags = lpRowSet->__ptr[j].__ptr[0].Value.ul & MSGFLAG_READ;
-
- lstMatches.push_back(iterRows->ulObjId);
- lstFlags.push_back(ulObjFlags);
- }
-
- // Add matched row to database
- er = AddResults(ulStoreId, ulFolderId, lstMatches, lstFlags, &lCount, &lUnreadCount);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::ProcessCandidateRows() AddResults failed %d", er);
- goto exit;
- }
- if(lCount || lUnreadCount) {
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_COUNT, lCount);
- if (er == erSuccess) {
- er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_UNREAD, lUnreadCount);
- if (er != erSuccess)
- ec_log_crit("ECSearchFolders::ProcessCandidateRows() UpdateFolderCount failed(2) %d", er);
- }
- else {
- ec_log_crit("ECSearchFolders::ProcessCandidateRows() UpdateFolderCount failed(1) %d", er);
- goto exit;
- }
- }
- if(bNotify) {
- er = lpDatabase->Commit();
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::ProcessCandidateRows() DB commit failed %d", er);
- goto exit;
- }
-
- // Add matched row and send a notification to update views of this search (if any are open)
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD, 0, ulFolderId, lstMatches, MAPI_MESSAGE);
- }
-
- // If the searchfolder counte have changed, update counts and send notifications
- if(bNotify) {
- m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulFolderId);
- m_lpSessionManager->NotificationModified(MAPI_FOLDER, ulFolderId); // folder has modified due to PR_CONTENT_*
-
- if(m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParent) == erSuccess)
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulFolderId, MAPI_FOLDER); // PR_CONTENT_* has changed in tables too
- }
- exit:
- if(bNotify && lpDatabase && er != erSuccess)
- lpDatabase->Rollback();
-
- if(lpRowSet) {
- FreeRowSet(lpRowSet, true);
- lpRowSet = NULL;
- }
-
- if(lpSubResults) {
- FreeSubRestrictionResults(lpSubResults);
- lpSubResults = NULL;
- }
-
- return er;
- }
- // Does an actual search of a specific search Criteria in store ulStoreId, search folder ulFolderId. Will cancel if *lpbCancel
- // is TRUE. We check after each message row set to see if the cancel has been requested.
- ECRESULT ECSearchFolders::Search(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCrit, bool *lpbCancel, bool bNotify)
- {
- ECRESULT er = erSuccess;
- ECListInt lstFolders;
- ECObjectTableList ecRows;
- sObjectTableKey sRow;
- ECODStore ecODStore;
- ECSession *lpSession = NULL;
- unsigned int ulUserId = 0;
- std::string strQuery;
- ECDatabase *lpDatabase = NULL;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- struct propTagArray *lpPropTags = NULL;
- unsigned int i=0;
- struct restrictTable *lpAdditionalRestrict = NULL;
- unsigned int ulParent = 0;
- std::string suggestion;
- std::list<ULONG> lstPrefix;
- lstPrefix.push_back(PR_MESSAGE_FLAGS);
- //Indexer
- std::list<unsigned int> lstIndexerResults;
- GUID guidServer;
- GUID guidStore;
- ECLocale locale = m_lpSessionManager->GetSortLocale(ulStoreId);
-
- ecODStore.ulStoreId = ulStoreId;
- ecODStore.ulFolderId = 0;
- ecODStore.ulFlags = 0;
- ecODStore.ulObjType = 0;
- ecODStore.lpGuid = NULL; // FIXME: optimisation possible
-
- if(lpSearchCrit->lpFolders == NULL || lpSearchCrit->lpRestrict == NULL) {
- er = KCERR_NOT_FOUND;
- ec_log_err("ECSearchFolders::Search() no folder or search criteria");
- goto exit;
- }
- er = m_lpSessionManager->GetCacheManager()->GetStore(ulStoreId, NULL, &guidStore);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::Search() GetStore failed: 0x%x", er);
- goto exit;
- }
- ecODStore.lpGuid = &guidStore;
-
- // Get the owner of the store
- er = m_lpSessionManager->GetCacheManager()->GetObject(ulStoreId, NULL, &ulUserId, NULL, NULL);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::Search() GetObject failed: 0x%x", er);
- goto exit;
- }
-
- // Create a session with the security credentials of the owner of the store
- er = m_lpSessionManager->CreateSessionInternal(&lpSession, ulUserId);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::Search() CreateSessionInternal failed: 0x%x", er);
- goto exit;
- }
-
- lpSession->Lock();
- er = lpSession->GetDatabase(&lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::Search() GetDatabase failed: 0x%x", er);
- goto exit;
- }
- // Get target folders
- er = m_lpSessionManager->GetCacheManager()->GetEntryListToObjectList(lpSearchCrit->lpFolders, &lstFolders);
- if (er != erSuccess) {
- ec_log_crit("ECSearchFolders::Search() GetEntryListToObjectList failed: 0x%x", er);
- goto exit;
- }
-
- // Reset search results in database
- er = ResetResults(ulStoreId, ulFolderId);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::Search() ResetResults failed: 0x%x", er);
- goto exit;
- }
- // Expand target folders if recursive
- if(lpSearchCrit->ulFlags & RECURSIVE_SEARCH) {
- auto iterFolders = lstFolders.cbegin();
- while (iterFolders != lstFolders.cend()) {
- strQuery = "SELECT hierarchy.id from hierarchy WHERE hierarchy.parent = " + stringify(*iterFolders) + " AND hierarchy.type=3 AND hierarchy.flags & " + stringify(MSGFLAG_DELETED|MSGFLAG_ASSOCIATED) + " = 0 ORDER by hierarchy.id DESC";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er == erSuccess) {
- while ((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL)
- if(lpDBRow && lpDBRow[0])
- lstFolders.push_back(atoi(lpDBRow[0]));
- } else
- ec_log_crit("ECSearchFolders::Search() could not expand target folders: 0x%x", er);
- ++iterFolders;
- }
- }
- // Check if we can use indexed search
- er = m_lpSessionManager->GetServerGUID(&guidServer);
- if(er != erSuccess)
- goto exit;
-
- if (GetIndexerResults(lpDatabase, m_lpSessionManager->GetConfig(), m_lpSessionManager->GetCacheManager(), &guidServer, &guidStore, lstFolders, lpSearchCrit->lpRestrict, &lpAdditionalRestrict, lstIndexerResults, suggestion) == erSuccess) {
- strQuery = "INSERT INTO properties (hierarchyid, tag, type, val_string) VALUES(" + stringify(ulFolderId) + "," + stringify(PROP_ID(PR_EC_SUGGESTION)) + "," + stringify(PROP_TYPE(PR_EC_SUGGESTION)) + ",'" + lpDatabase->Escape(suggestion) + "') ON DUPLICATE KEY UPDATE val_string='" + lpDatabase->Escape(suggestion) + "'";
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::Search(): could not add suggestion");
- goto exit;
- }
- // Get the additional restriction properties ready
- er = ECGenericObjectTable::GetRestrictPropTags(lpAdditionalRestrict, &lstPrefix, &lpPropTags);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() ECGenericObjectTable::GetRestrictPropTags failed: 0x%x", er);
- goto exit;
- }
- // Since an indexed search should be fast, do the entire query as a single transaction, and notify after Commit()
- er = lpDatabase->Begin();
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() database begin failed: 0x%x", er);
- goto exit;
- }
- auto iterResults = lstIndexerResults.cbegin();
- while(1) {
- // Loop through the results data
- int n = 0;
-
- ecRows.clear();
- for (; iterResults != lstIndexerResults.cend() &&
- (lpbCancel == NULL || !*lpbCancel) && n < 200; ++iterResults) {
- sRow.ulObjId = *iterResults;
- sRow.ulOrderId = 0;
-
- ecRows.push_back(sRow);
- }
-
- if(ecRows.empty())
- break; // no more rows
-
- // Note that we do not want ProcessCandidateRows to send notifications since we will send a bulk TABLE_CHANGE later, so bNotify == false here
- er = ProcessCandidateRows(lpDatabase, lpSession, lpAdditionalRestrict, lpbCancel, ulStoreId, ulFolderId, &ecODStore, ecRows, lpPropTags, locale, false);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() ProcessCandidateRows failed: 0x%x", er);
- goto exit;
- }
- }
-
- er = lpDatabase->Commit();
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() database commit failed: 0x%x", er);
- goto exit;
- }
- // Notify the search folder and his parent
- if(bNotify) {
- // Add matched rows and send a notification to update views of this search (if any are open)
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_CHANGE, 0, ulFolderId, 0, MAPI_MESSAGE);
- m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulFolderId);
- m_lpSessionManager->NotificationModified(MAPI_FOLDER, ulFolderId); // folder has modified due to PR_CONTENT_*
- if(m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParent) == erSuccess)
- m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulFolderId, MAPI_FOLDER); // PR_CONTENT_* has changed in tables too
- }
- } else {
- // Get the restriction ready for this search folder
- er = ECGenericObjectTable::GetRestrictPropTags(lpSearchCrit->lpRestrict, &lstPrefix, &lpPropTags);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() ECGenericObjectTable::GetRestrictPropTags failed: 0x%x", er);
- goto exit;
- }
- // If we needn't notify, we don't need to commit each message before notifying, so Begin() here
- if(!bNotify)
- lpDatabase->Begin();
- // lstFolders now contains all folders to search through
- for (auto iterFolders = lstFolders.cbegin();
- iterFolders != lstFolders.cend() &&
- (lpbCancel == NULL || !*lpbCancel); ++iterFolders) {
- // Optimisation: we know the folder id of the objects we're querying
- ecODStore.ulFolderId = *iterFolders;
-
- // Get a list of messages in folders, sorted descending by creation date so the newest are found first
- strQuery = "SELECT hierarchy.id from hierarchy WHERE hierarchy.parent = " + stringify(*iterFolders) + " AND hierarchy.type=5 AND hierarchy.flags & " + stringify(MSGFLAG_DELETED|MSGFLAG_ASSOCIATED) + " = 0 ORDER by hierarchy.id DESC";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() SELECT failed: 0x%x", er);
- continue;
- }
-
- while(1) {
- if(lpbCancel && *lpbCancel)
- break;
-
- // Read max. 20 rows from the database
- i = 0;
-
- ecRows.clear();
- while(i < 20 && (lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
- if(lpDBRow[0] == NULL)
- continue;
- sRow.ulObjId = atoui(lpDBRow[0]);
- sRow.ulOrderId = 0;
-
- ecRows.push_back(sRow);
- ++i;
- }
-
- if(ecRows.empty())
- break; // no more rows
-
- er = ProcessCandidateRows(lpDatabase, lpSession, lpSearchCrit->lpRestrict, lpbCancel, ulStoreId, ulFolderId, &ecODStore, ecRows, lpPropTags, locale, bNotify);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::Search() ProcessCandidateRows failed: 0x%x", er);
- goto exit;
- }
- }
- }
- // Search done
- // If we needn't notify, we don't need to commit each message before notifying, so Commit() here
- if(!bNotify)
- lpDatabase->Commit();
- } //if(!bUseIndexer)
-
- // Save this information in the database.
- SetStatus(ulFolderId, EC_SEARCHFOLDER_STATUS_RUNNING);
-
- exit:
- if(lpDatabase && er != erSuccess)
- lpDatabase->Rollback();
-
- if(lpSession) {
- lpSession->Unlock();
- m_lpSessionManager->RemoveSessionInternal(lpSession);
- }
-
- if(lpPropTags)
- FreePropTagArray(lpPropTags);
- if (lpAdditionalRestrict)
- FreeRestrictTable(lpAdditionalRestrict);
- return er;
- }
- // Return whether we are stopped (no entry found), active (no thread found), or rebuilding (thread active)
- ECRESULT ECSearchFolders::GetState(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int *lpulState)
- {
- ECRESULT er = erSuccess;
- unsigned int ulState = 0;
-
- auto iterStore = m_mapSearchFolders.find(ulStoreId);
- if (iterStore == m_mapSearchFolders.cend()) {
- ulState = 0;
- } else {
-
- auto iterFolder = iterStore->second.find(ulFolderId);
- if (iterFolder == iterStore->second.cend()) {
- ulState = 0;
- } else {
-
- ulState = SEARCH_RUNNING;
-
- if(iterFolder->second->bThreadFree == false)
- ulState |= SEARCH_REBUILD;
- }
- }
- *lpulState = ulState;
- return er;
- }
- // Entrypoint for the SearchThread
- void* ECSearchFolders::SearchThread(void *lpParam)
- {
- auto ti = static_cast<THREADINFO *>(lpParam);
- auto lpFolder = ti->lpFolder; // The entry in the m_mapSearchFolders map
- auto lpSearchFolders = ti->lpSearchFolders; // The main ECSearchFolders object
- // We no longer need this
- delete ti;
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_THREADS);
- // Start the search
- lpSearchFolders->Search(lpFolder->ulStoreId, lpFolder->ulFolderId, lpFolder->lpSearchCriteria, &lpFolder->bThreadExit);
- // Signal search complete to clients
- lpSearchFolders->m_lpSessionManager->NotificationSearchComplete(lpFolder->ulFolderId, lpFolder->ulStoreId);
-
- /* Signal exit from thread */
- ulock_normal l_thr(lpFolder->mMutexThreadFree);
- lpFolder->bThreadFree = true;
- lpSearchFolders->m_condThreadExited.notify_one();
- l_thr.unlock();
-
- // We may not access lpFolder from this point on (it will be freed when the searchfolder is removed)
- lpFolder = NULL;
- g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_THREADS, -1);
-
- return NULL;
- }
- // Functions to do things in the database
- ECRESULT ECSearchFolders::ResetResults(unsigned int ulStoreId, unsigned int ulFolderId)
- {
- ECDatabase *lpDatabase = NULL;
- ECRESULT er = erSuccess;
- unsigned int ulParentId = 0;
- std::string strQuery;
-
- er = m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParentId);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::ResetResults(): GetParent failed 0x%x", er);
- goto exit;
- }
-
- er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::ResetResults(): GetThreadLocalDatabase failed 0x%x", er);
- goto exit;
- }
-
- er = lpDatabase->Begin();
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::ResetResults(): BEGIN failed 0x%x", er);
- goto exit;
- }
-
- er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " + stringify(ulFolderId) + " FOR UPDATE", NULL);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::ResetResults(): SELECT failed 0x%x", er);
- goto exit;
- }
-
- strQuery = "DELETE FROM searchresults WHERE folderid = " + stringify(ulFolderId);
- er = lpDatabase->DoDelete(strQuery);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::ResetResults(): DELETE failed 0x%x", er);
- goto exit;
- }
-
- strQuery = "UPDATE properties SET val_ulong = 0 WHERE hierarchyid = " + stringify(ulFolderId) + " AND tag IN(" + stringify(PROP_ID(PR_CONTENT_COUNT)) + "," + stringify(PROP_ID(PR_CONTENT_UNREAD)) + ") AND type = " + stringify(PROP_TYPE(PR_CONTENT_COUNT));
- er = lpDatabase->DoUpdate(strQuery);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::ResetResults(): DoUpdate failed 0x%x", er);
- goto exit;
- }
- er = UpdateTProp(lpDatabase, PR_CONTENT_COUNT, ulParentId, ulFolderId);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::ResetResults(): UpdateTProp failed(1) 0x%x", er);
- goto exit;
- }
-
- er = UpdateTProp(lpDatabase, PR_CONTENT_UNREAD, ulParentId, ulFolderId);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::ResetResults(): UpdateTProp failed(2) 0x%x", er);
- goto exit;
- }
-
- er = lpDatabase->Commit();
- if (er != erSuccess)
- ec_log_err("ECSearchFolders::ResetResults(): database commit failed 0x%x", er);
-
- exit:
- if (er != erSuccess && lpDatabase)
- lpDatabase->Rollback();
-
- return er;
- }
- // Add a single search result message (eg one match in a search folder)
- ECRESULT ECSearchFolders::AddResults(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int ulObjId, unsigned int ulFlags, bool *lpfInserted)
- {
- ECDatabase *lpDatabase = NULL;
- ECRESULT er = erSuccess;
- std::string strQuery;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
-
- assert((ulFlags &~ MSGFLAG_READ) == 0);
- er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::AddResults(): GetThreadLocalDatabase failed 0x%x", er);
- return er;
- }
- strQuery = "SELECT flags FROM searchresults WHERE folderid = " + stringify(ulFolderId) + " AND hierarchyid = " + stringify(ulObjId) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::AddResults(): select searchresults failed 0x%x", er);
- return er;
- }
-
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if (lpDBRow != nullptr && lpDBRow[0] != nullptr && atoui(lpDBRow[0]) == ulFlags)
- // The record in the database is the same as what we're trying to insert; this is an error because we can't update or insert the record
- return KCERR_NOT_FOUND;
-
- // This will either update or insert the record
- strQuery = "INSERT INTO searchresults (folderid, hierarchyid, flags) VALUES(" + stringify(ulFolderId) + "," + stringify(ulObjId) + "," + stringify(ulFlags) + ") ON DUPLICATE KEY UPDATE flags=" + stringify(ulFlags);
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::AddResults(): INSERT failed 0x%x", er);
- return er;
- }
-
- // We have inserted if the previous SELECT returned no row
- if (lpfInserted)
- *lpfInserted = (lpDBRow == NULL);
- return erSuccess;
- }
- ECRESULT ECSearchFolders::AddResults(unsigned int ulStoreId, unsigned int ulFolderId, std::list<unsigned int> &lstObjId, std::list<unsigned int>& lstFlags, int *lpulCount, int *lpulUnread)
- {
- ECDatabase *lpDatabase = NULL;
- ECRESULT er;
- std::string strQuery;
- unsigned int ulInserted = 0;
- unsigned int ulModified = 0;
-
- assert(lstObjId.size() == lstFlags.size());
- if(lstObjId.empty())
- return erSuccess;
-
- er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::AddResults(): GetThreadLocalDatabase failed 0x%x", er);
- return er;
- }
- strQuery = "INSERT IGNORE INTO searchresults (folderid, hierarchyid, flags) VALUES";
- for (const auto n : lstObjId) {
- strQuery += "(";
- strQuery += stringify(ulFolderId);
- strQuery += ",";
- strQuery += stringify(n);
- strQuery += ",1),";
- }
- strQuery.resize(strQuery.size()-1);
-
- er = lpDatabase->DoInsert(strQuery, NULL, &ulInserted);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::AddResults(): DoInsert failed 0x%x", er);
- return er;
- }
- /*
- * Combining the following queries in one query seems to cause MySQL to do a range- or gaplock, causing deadlocks
- * when folders with adjacent ids are updated at the same time.
- */
- for (auto i = lstFlags.cbegin(), j = lstObjId.cbegin();
- i != lstFlags.cend(); ++i, ++j) {
- if(*i == 0) {
- unsigned int modified = 0;
-
- strQuery = "UPDATE searchresults SET flags = 0 WHERE hierarchyid = " + stringify(*j) + " AND folderid = " + stringify(ulFolderId);
- er = lpDatabase->DoUpdate(strQuery, &modified);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::AddResults(): UPDATE failed 0x%x", er);
- return er;
- }
- ulModified += modified;
- }
- }
- if (lpulCount != NULL)
- *lpulCount += ulInserted;
- if (lpulUnread != NULL)
- *lpulUnread += ulModified;
-
- return erSuccess;
- }
- // Remove a single search result (so one message in a search folder). Returns NOT_FOUND if the item wasn't in the database in the first place
- ECRESULT ECSearchFolders::DeleteResults(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int ulObjId, unsigned int *lpulOldFlags)
- {
- ECDatabase *lpDatabase = NULL;
- ECRESULT er = erSuccess;
- std::string strQuery;
- DB_RESULT lpResult;
- DB_ROW lpRow = NULL;
-
- unsigned int ulAffected = 0;
-
- er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
- if (er != erSuccess)
- return er;
- if(lpulOldFlags) {
- strQuery = "SELECT flags FROM searchresults WHERE folderid=" + stringify(ulFolderId) + " AND hierarchyid=" + stringify(ulObjId) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpResult);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::DeleteResults(): SELECT failed 0x%x", er);
- return er;
- }
-
- lpRow = lpDatabase->FetchRow(lpResult);
- if (lpRow == nullptr || lpRow[0] == nullptr)
- return KCERR_NOT_FOUND;
- *lpulOldFlags = atoui(lpRow[0]);
- }
-
- strQuery = "DELETE FROM searchresults WHERE folderid=" + stringify(ulFolderId) + " AND hierarchyid=" + stringify(ulObjId);
- er = lpDatabase->DoDelete(strQuery, &ulAffected);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::DeleteResults(): DELETE failed 0x%x", er);
- return er;
- }
- return ulAffected != 0 ? erSuccess : KCERR_NOT_FOUND;
- }
- // Write the status of a search folder to the PR_EC_SEARCHFOLDER_STATUS property
- ECRESULT ECSearchFolders::SetStatus(unsigned int ulFolderId, unsigned int ulStatus)
- {
- ECDatabase *lpDatabase = NULL;
- ECRESULT er;
- std::string strQuery;
-
- // Do not use transactions because this function is called inside a transaction.
-
- er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::SetStatus(): GetThreadLocalDatabase failed 0x%x", er);
- return er;
- }
-
- // No record == running
- if(ulStatus != EC_SEARCHFOLDER_STATUS_RUNNING) {
- strQuery = "REPLACE INTO properties (tag, type, hierarchyid, val_ulong) "
- "VALUES(" + stringify(PROP_ID(PR_EC_SEARCHFOLDER_STATUS)) + "," +
- stringify(PROP_TYPE(PR_EC_SEARCHFOLDER_STATUS)) + "," +
- stringify(ulFolderId) + "," +
- stringify(ulStatus) + ")";
- er = lpDatabase->DoInsert(strQuery);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::SetStatus(): DoInsert failed 0x%x", er);
- return er;
- }
- } else {
- strQuery = "DELETE FROM properties "
- "WHERE hierarchyid=" + stringify(ulFolderId) +
- " AND tag=" + stringify(PROP_ID(PR_EC_SEARCHFOLDER_STATUS)) +
- " AND type=" + stringify(PROP_TYPE(PR_EC_SEARCHFOLDER_STATUS));
- er = lpDatabase->DoDelete(strQuery);
- if (er != erSuccess) {
- ec_log_err("ECSearchFolders::SetStatus(): DELETE failed 0x%x", er);
- return er;
- }
- }
- return erSuccess;
- }
- // Get all results of a certain search folder in a list of hierarchy IDs
- ECRESULT ECSearchFolders::GetSearchResults(unsigned int ulStoreId, unsigned int ulFolderId, std::list<unsigned int> *lstObjIds)
- {
- ECDatabase *lpDatabase = NULL;
- DB_RESULT lpResult;
- DB_ROW lpRow = NULL;
- ECRESULT er = erSuccess;
- std::string strQuery;
-
- er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::GetSearchResults(): GetThreadLocalDatabase failed 0x%x", er);
- return er;
- }
-
- strQuery = "SELECT hierarchyid FROM searchresults WHERE folderid=" + stringify(ulFolderId);
- er = lpDatabase->DoSelect(strQuery, &lpResult);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::GetSearchResults(): SELECT failed 0x%x", er);
- return er;
- }
-
- lstObjIds->clear();
- while(1) {
- lpRow = lpDatabase->FetchRow(lpResult);
- if(lpRow == NULL || lpRow[0] == NULL)
- break;
- lstObjIds->push_back(atoui(lpRow[0]));
- }
- return erSuccess;
- }
- // Loads the search criteria from the database
- ECRESULT ECSearchFolders::LoadSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria **lppSearchCriteria)
- {
- ECRESULT er = erSuccess;
- ECDatabase *lpDatabase = NULL;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- struct soap xmlsoap;
- // Get database
- er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::LoadSearchCriteria(): GetThreadLocalDatabase failed 0x%x", er);
- return er;
- }
- // We use the soap serializer / deserializer to store the data
- soap_set_mode(&xmlsoap, SOAP_XML_TREE | SOAP_C_UTFSTRING);
-
- // Find out what kind of table this is
- strQuery = "SELECT hierarchy.flags, properties.val_string FROM hierarchy JOIN properties on hierarchy.id=properties.hierarchyid AND properties.tag =" + stringify(PROP_ID(PR_EC_SEARCHCRIT)) + " AND properties.type =" + stringify(PROP_TYPE(PR_EC_SEARCHCRIT)) + " WHERE hierarchy.id =" + stringify(ulFolderId) + " LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess) {
- ec_log_err("ECSearchFolders::LoadSearchCriteria(): SELECT failed 0x%x", er);
- return er;
- }
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if(lpDBRow && lpDBRow[0] && atoi(lpDBRow[0]) == 2 && lpDBRow[1]) {
- std::string xmldata(lpDBRow[1]);
- std::istringstream xml(xmldata);
- struct searchCriteria crit;
- xmlsoap.is = &xml;
- soap_default_searchCriteria(&xmlsoap, &crit);
- if (soap_begin_recv(&xmlsoap) != 0)
- return KCERR_NETWORK_ERROR;
- soap_get_searchCriteria(&xmlsoap, &crit, "SearchCriteria", NULL);
- // We now have the object, allocated by xmlsoap object,
- if (soap_end_recv(&xmlsoap) != 0)
- er = KCERR_NETWORK_ERROR;
- else
- er = CopySearchCriteria(nullptr, &crit, lppSearchCriteria);
- /*
- * We do not need the error here: lppSearchCriteria will not be
- * touched, and we need to free the soap structs.
- */
- soap_destroy(&xmlsoap);
- soap_end(&xmlsoap);
- soap_done(&xmlsoap);
- } else {
- er = KCERR_NOT_FOUND;
- }
- return er;
- }
- // Saves the search criteria in the database
- ECRESULT ECSearchFolders::SaveSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCriteria)
- {
- ECRESULT er = erSuccess;
- ECDatabase *lpDatabase = NULL;
- // Get database
- er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
- if(er != erSuccess) {
- ec_log_crit("ECSearchFolders::SaveSearchCriteria(): GetThreadLocalDatabase failed 0x%x", er);
- goto exit;
- }
-
- er = lpDatabase->Begin();
- if(er != hrSuccess) {
- ec_log_err("ECSearchFolders::SaveSearchCriteria(): BEGIN failed 0x%x", er);
- goto exit;
- }
- er = SaveSearchCriteria(lpDatabase, ulStoreId, ulFolderId, lpSearchCriteria);
- if(er != hrSuccess) {
- ec_log_err("ECSearchFolders::SaveSearchCriteria(): SaveSearchCriteria failed 0x%x", er);
- goto exit;
- }
- er = lpDatabase->Commit();
- if (er != hrSuccess)
- ec_log_err("ECSearchFolders::SaveSearchCriteria(): commit failed 0x%x", er);
- exit:
- if(lpDatabase && er != erSuccess)
- lpDatabase->Rollback();
-
- return er;
- }
- // Serialize and save the search criteria for a certain folder. The property is saved as a PR_EC_SEARCHCRIT property
- ECRESULT ECSearchFolders::SaveSearchCriteria(ECDatabase *lpDatabase, unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCriteria)
- {
- ECRESULT er;
- std::string strQuery;
- struct soap xmlsoap;
- struct searchCriteria sSearchCriteria;
- std::ostringstream xml;
- // We use the soap serializer / deserializer to store the data
- soap_set_mode(&xmlsoap, SOAP_XML_TREE | SOAP_C_UTFSTRING);
- sSearchCriteria.lpFolders = lpSearchCriteria->lpFolders;
- sSearchCriteria.lpRestrict = lpSearchCriteria->lpRestrict;
- sSearchCriteria.ulFlags = lpSearchCriteria->ulFlags;
- xmlsoap.os = &xml;
- soap_serialize_searchCriteria(&xmlsoap, &sSearchCriteria);
- soap_begin_send(&xmlsoap);
- soap_put_searchCriteria(&xmlsoap, &sSearchCriteria, "SearchCriteria",NULL);
- soap_end_send(&xmlsoap);
- // Make sure we're linking with the correct SOAP (c++ version)
- assert(!xml.str().empty());
- // xml now contains XML version of search criteria
- // Replace PR_EC_SEARCHCRIT in database
- strQuery = "DELETE from properties WHERE properties.hierarchyid = " + stringify(ulFolderId) + " AND tag=" + stringify(PROP_ID(PR_EC_SEARCHCRIT)) + " AND type=" + stringify(PROP_TYPE(PR_EC_SEARCHCRIT));
- er = lpDatabase->DoDelete(strQuery);
- if(er != erSuccess)
- return er;
- strQuery = "INSERT INTO properties (hierarchyid, tag, type, val_string) VALUES(" + stringify(ulFolderId) + "," + stringify(PROP_ID(PR_EC_SEARCHCRIT)) + "," + stringify(PROP_TYPE(PR_EC_SEARCHCRIT)) + ",'" + lpDatabase->Escape( xml.str() ) + "')";
- return lpDatabase->DoInsert(strQuery);
- }
- void ECSearchFolders::FlushAndWait()
- {
- ulock_rec l_ev(m_mutexEvents);
- m_condEvents.notify_all();
- l_ev.unlock();
- // let ProcessThread get this lock, and mark the thread running
- l_ev.lock();
- l_ev.unlock();
- // wait for an inactive search thread
- while (m_bRunning) Sleep(10);
- }
- /*
- * This is the main processing thread, which processes changes from the queue. After processing it removes them from the queue and waits for
- * new events
- */
-
- void * ECSearchFolders::ProcessThread(void *lpSearchFolders)
- {
- auto lpThis = static_cast<ECSearchFolders *>(lpSearchFolders);
-
- while(1) {
- // Get events to process
- ulock_rec l_ev(lpThis->m_mutexEvents);
- if (lpThis->m_bExitThread)
- break;
- if (lpThis->m_lstEvents.empty())
- /*
- * No events, wait until one arrives (the mutex is
- * unlocked by pthread_cond_wait so people are able to
- * add new events). The condition also occurs when the
- * server is exiting.
- */
- lpThis->m_condEvents.wait(l_ev);
- lpThis->m_bRunning = true;
- /*
- * The condition ended. Two things can have happened: there is
- * now at least one event waiting, or and exit has been
- * requested. In both cases, we simply unlock the mutex and
- * process any (may be 0) events currently in the queue. This
- * means that the caller must make sure that no new events can
- * be added after the m_bThreadExit flag is set to TRUE.
- */
- l_ev.unlock();
-
- lpThis->FlushEvents();
- Sleep(1000);
- lpThis->m_bRunning = false;
- // Check if we need to exit
- }
-
- return NULL;
- }
- struct FOLDERSORT {
- bool operator () (const EVENT &a, const EVENT &b) { return a.ulFolderId < b.ulFolderId; }
- };
- // Process all waiting events in an efficient order
- ECRESULT ECSearchFolders::FlushEvents()
- {
- ECRESULT er = erSuccess;
- std::list<EVENT> lstEvents;
- ECObjectTableList lstObjectIDs;
- sObjectTableKey sRow;
- FOLDERSORT sort;
-
- unsigned int ulStoreId = 0;
- unsigned int ulFolderId = 0;
- ECKeyTable::UpdateType ulType;
-
- // We do a copy-remove-process cycle here to keep the event queue locked for the least time as possible with
- // 500 events at a time
- ulock_rec l_ev(m_mutexEvents);
- for (int i = 0; i < 500; ++i) {
- // Move the first element of m_lstEvents to the head of our list.
- if(m_lstEvents.empty())
- break;
- lstEvents.splice(lstEvents.end(), m_lstEvents, m_lstEvents.begin());
- }
- l_ev.unlock();
-
- // Sort the items by folder. The order of DELETE and ADDs will remain unchanged. This is important
- // because the order of the incoming ADD or DELETE is obviously important for the final result.
- lstEvents.sort(sort);
-
- // Send the changes grouped by folder (and therefore also by store)
- ulStoreId = 0;
- ulFolderId = 0;
- ulType = ECKeyTable::TABLE_ROW_MODIFY;
-
- // Process changes by finding sequences of events of the same type (eg ADD ADD ADD DELETE will result in two sequences: 3xADD + 1xDELETE)
- for (const auto &event : lstEvents) {
- if (event.ulFolderId != ulFolderId || event.ulType != ulType) {
- if(!lstObjectIDs.empty()) {
- // This is important: make the events unique. We need to do this because the ECStoreObjectTable
- // row engine does not support requesting the exact same row twice within the same call. If we have
- // duplicates here, this will filter through to the row engine and cause all kinds of nastiness, mainly
- // causing the item to be deleted from search folders irrespective of whether it should have been deleted
- // or added.
- lstObjectIDs.sort();
- lstObjectIDs.unique();
-
- ProcessMessageChange(ulStoreId, ulFolderId, &lstObjectIDs, ulType);
- lstObjectIDs.clear();
- }
- }
- ulStoreId = event.ulStoreId;
- ulFolderId = event.ulFolderId;
- ulType = event.ulType;
-
- sRow.ulObjId = event.ulObjectId;
- sRow.ulOrderId = 0;
-
- lstObjectIDs.push_back(sRow);
- }
- // Flush last set
- if(!lstObjectIDs.empty()) {
- // This is important: make the events unique. We need to do this because the ECStoreObjectTable
- // row engine does not support requesting the exact same row twice within the same call. If we have
- // duplicates here, this will filter through to the row engine and cause all kinds of nastiness, mainly
- // causing the item to be deleted from search folders irrespective of whether it should have been deleted
- // or added.
- lstObjectIDs.sort();
- lstObjectIDs.unique();
- ProcessMessageChange(ulStoreId, ulFolderId, &lstObjectIDs, ulType);
- }
-
- return er;
- }
- /**
- * Get object statistics
- *
- * @param[out] sStats Reference to searchfolder statistics
- *
- * @return This functions return always success
- */
- ECRESULT ECSearchFolders::GetStats(sSearchFolderStats &sStats)
- {
- memset(&sStats, 0, sizeof(sSearchFolderStats));
- ulock_rec l_sf(m_mutexMapSearchFolders);
- sStats.ulStores = m_mapSearchFolders.size();
- sStats.ullSize = sStats.ulStores * sizeof(STOREFOLDERIDSEARCH::value_type);
- for (const auto &storefolder : m_mapSearchFolders) {
- sStats.ulFolders += storefolder.second.size();
- sStats.ullSize += storefolder.second.size() * (sizeof(FOLDERIDSEARCH::value_type) + sizeof(SEARCHFOLDER));
- for (const auto &fs : storefolder.second)
- sStats.ullSize += SearchCriteriaSize(fs.second->lpSearchCriteria);
- }
- l_sf.unlock();
- ulock_rec l_ev(m_mutexEvents);
- sStats.ulEvents = m_lstEvents.size();
- l_ev.unlock();
- sStats.ullSize += sStats.ulEvents * sizeof(EVENT);
- return erSuccess;
- }
- } /* namespace */
|