ECSearchFolders.cpp 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <utility>
  19. #include <kopano/lockhelper.hpp>
  20. #include <pthread.h>
  21. #include <mapidefs.h>
  22. #include <mapitags.h>
  23. #include "ECMAPI.h"
  24. #include "ECSession.h"
  25. #include <kopano/ECKeyTable.h>
  26. #include <kopano/ECLogger.h>
  27. #include "ECStoreObjectTable.h"
  28. #include "ECSubRestriction.h"
  29. #include "ECSearchFolders.h"
  30. #include "ECSessionManager.h"
  31. #include "ECStatsCollector.h"
  32. #include "ECIndexer.h"
  33. #include <kopano/ECTags.h>
  34. #include "cmdutil.hpp"
  35. #include <kopano/stringutil.h>
  36. #include "ECSearchClient.h"
  37. namespace KC {
  38. struct THREADINFO {
  39. SEARCHFOLDER *lpFolder;
  40. ECSearchFolders *lpSearchFolders;
  41. };
  42. ECSearchFolders::ECSearchFolders(ECSessionManager *lpSessionManager,
  43. ECDatabaseFactory *lpFactory) :
  44. m_lpDatabaseFactory(lpFactory), m_lpSessionManager(lpSessionManager)
  45. {
  46. pthread_create(&m_threadProcess, NULL, ECSearchFolders::ProcessThread, (void *)this);
  47. set_thread_name(m_threadProcess, "SearchFolders");
  48. }
  49. ECSearchFolders::~ECSearchFolders() {
  50. ulock_rec l_sf(m_mutexMapSearchFolders);
  51. for (auto &p : m_mapSearchFolders) {
  52. for (auto &f : p.second)
  53. delete f.second;
  54. p.second.clear();
  55. }
  56. m_mapSearchFolders.clear();
  57. l_sf.unlock();
  58. ulock_rec l_events(m_mutexEvents);
  59. m_bExitThread = true;
  60. m_condEvents.notify_all();
  61. l_events.unlock();
  62. pthread_join(m_threadProcess, NULL);
  63. }
  64. // Only loads the search criteria for all search folders. Used once at boot time
  65. ECRESULT ECSearchFolders::LoadSearchFolders()
  66. {
  67. ECRESULT er = erSuccess;
  68. ECDatabase *lpDatabase = NULL;
  69. DB_RESULT lpResult;
  70. DB_ROW lpRow = NULL;
  71. unsigned int ulFolderId = 0;
  72. unsigned int ulStoreId = 0;
  73. unsigned int ulStatus = 0;
  74. // Search for all folders with PR_EC_SEARCHCRIT that are not deleted. Note that this query can take quite some time on large databases
  75. 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";
  76. struct searchCriteria *lpSearchCriteria = NULL;
  77. // Get database
  78. er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
  79. if(er != erSuccess)
  80. goto exit;
  81. er = lpDatabase->DoSelect(strQuery, &lpResult);
  82. if(er != erSuccess)
  83. goto exit;
  84. while((lpRow = lpDatabase->FetchRow(lpResult))) {
  85. if(lpRow[0] == NULL)
  86. continue;
  87. if(lpRow[1] != NULL)
  88. ulStatus = atoi(lpRow[1]);
  89. else
  90. ulStatus = EC_SEARCHFOLDER_STATUS_RUNNING; // this is the default if no property is found
  91. ulFolderId = atoi(lpRow[0]);
  92. er = m_lpSessionManager->GetCacheManager()->GetStore(ulFolderId, &ulStoreId, NULL);
  93. if(er != erSuccess) {
  94. er = erSuccess;
  95. continue;
  96. }
  97. // Only load the table if it is not stopped
  98. if(ulStatus != EC_SEARCHFOLDER_STATUS_STOPPED) {
  99. er = LoadSearchCriteria(ulStoreId, ulFolderId, &lpSearchCriteria);
  100. if(er != erSuccess) {
  101. er = erSuccess;
  102. continue;
  103. }
  104. if(ulStatus == EC_SEARCHFOLDER_STATUS_REBUILD)
  105. ec_log_info("Rebuilding search folder %d", ulFolderId);
  106. // If the folder was in the process of rebuilding, then completely rebuild the search results (we don't know how far the search got)
  107. er = AddSearchFolder(ulStoreId, ulFolderId, ulStatus == EC_SEARCHFOLDER_STATUS_REBUILD, lpSearchCriteria);
  108. if(er != erSuccess) {
  109. er = erSuccess; // just try to skip the error
  110. continue;
  111. }
  112. if(lpSearchCriteria) {
  113. FreeSearchCriteria(lpSearchCriteria);
  114. lpSearchCriteria = NULL;
  115. }
  116. }
  117. }
  118. exit:
  119. if(lpSearchCriteria)
  120. FreeSearchCriteria(lpSearchCriteria);
  121. return er;
  122. }
  123. // Called from IMAPIContainer::SetSearchCriteria
  124. ECRESULT ECSearchFolders::SetSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCriteria)
  125. {
  126. ECRESULT er;
  127. if(lpSearchCriteria == NULL) {
  128. /* Always return successful, so that Outlook 2007 works */
  129. CancelSearchFolder(ulStoreId, ulFolderId);
  130. } else {
  131. er = AddSearchFolder(ulStoreId, ulFolderId, true, lpSearchCriteria);
  132. if(er != erSuccess)
  133. return er;
  134. er = SaveSearchCriteria(ulStoreId, ulFolderId, lpSearchCriteria);
  135. if(er != erSuccess)
  136. return er;
  137. }
  138. return erSuccess;
  139. }
  140. // Gets the search criteria from in-memory
  141. ECRESULT ECSearchFolders::GetSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria **lppSearchCriteria, unsigned int *lpulFlags)
  142. {
  143. ECRESULT er = erSuccess;
  144. FOLDERIDSEARCH::const_iterator iterFolder;
  145. scoped_rlock l_sf(m_mutexMapSearchFolders);
  146. // See if there are any searches for this store first
  147. auto iterStore = m_mapSearchFolders.find(ulStoreId);
  148. if (iterStore == m_mapSearchFolders.cend())
  149. return KCERR_NOT_INITIALIZED;
  150. iterFolder = iterStore->second.find(ulFolderId);
  151. if (iterFolder == iterStore->second.cend())
  152. return KCERR_NOT_INITIALIZED;
  153. er = CopySearchCriteria(NULL, iterFolder->second->lpSearchCriteria, lppSearchCriteria);
  154. if(er != erSuccess)
  155. return er;
  156. er = GetState(ulStoreId, ulFolderId, lpulFlags);
  157. if(er != erSuccess)
  158. return er;
  159. // Add recursive flag if necessary
  160. *lpulFlags |= iterFolder->second->lpSearchCriteria->ulFlags & SEARCH_RECURSIVE;
  161. return er;
  162. }
  163. // Add or modify a search folder
  164. ECRESULT ECSearchFolders::AddSearchFolder(unsigned int ulStoreId, unsigned int ulFolderId, bool bReStartSearch, struct searchCriteria *lpSearchCriteria)
  165. {
  166. ECRESULT er = erSuccess;
  167. struct searchCriteria *lpCriteria = NULL;
  168. STOREFOLDERIDSEARCH::iterator iterStore;
  169. SEARCHFOLDER *lpSearchFolder = NULL;
  170. unsigned int ulParent = 0;
  171. ulock_rec l_sf(m_mutexMapSearchFolders, std::defer_lock_t());
  172. if(lpSearchCriteria == NULL) {
  173. er = LoadSearchCriteria(ulStoreId, ulFolderId, &lpCriteria);
  174. if(er != erSuccess)
  175. goto exit;
  176. lpSearchCriteria = lpCriteria;
  177. }
  178. // Cancel any running searches
  179. CancelSearchFolder(ulStoreId, ulFolderId);
  180. if(bReStartSearch) {
  181. // Set the status of the table as rebuilding
  182. SetStatus(ulFolderId, EC_SEARCHFOLDER_STATUS_REBUILD);
  183. // Remove any results for this folder if we're restarting the search
  184. er = ResetResults(ulStoreId, ulFolderId);
  185. if(er != erSuccess)
  186. goto exit;
  187. }
  188. // Tell tables that we've reset:
  189. // 1. Reset cache for this folder
  190. m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulFolderId);
  191. // 2. Send reset table contents notification
  192. er = m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_CHANGE, 0, ulFolderId, 0, MAPI_MESSAGE);
  193. if(er != erSuccess)
  194. goto exit;
  195. // 3. Send change tables viewing us (hierarchy tables)
  196. if(m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParent) == erSuccess) {
  197. er = m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulFolderId, MAPI_FOLDER);
  198. if(er != erSuccess)
  199. goto exit;
  200. }
  201. lpSearchFolder = new SEARCHFOLDER(ulStoreId, ulFolderId);
  202. er = CopySearchCriteria(NULL, lpSearchCriteria, &lpSearchFolder->lpSearchCriteria);
  203. if(er != erSuccess) {
  204. delete lpSearchFolder;
  205. goto exit;
  206. }
  207. // Get searches for this store, or add it to the list.
  208. l_sf.lock();
  209. iterStore = m_mapSearchFolders.insert(STOREFOLDERIDSEARCH::value_type(ulStoreId, FOLDERIDSEARCH())).first;
  210. iterStore->second.insert(FOLDERIDSEARCH::value_type(ulFolderId, lpSearchFolder));
  211. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_COUNT);
  212. if(bReStartSearch) {
  213. lpSearchFolder->bThreadFree = false;
  214. auto ti = new THREADINFO;
  215. ti->lpSearchFolders = this;
  216. ti->lpFolder = lpSearchFolder;
  217. // Insert the actual folder with the criteria
  218. // Start the thread (will store the thread id in the original list)
  219. pthread_attr_t attr;
  220. pthread_attr_init(&attr);
  221. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  222. pthread_attr_setstacksize(&attr, 512*1024); // 512KB stack space for search threads
  223. int err = 0;
  224. if((err = pthread_create(&lpSearchFolder->sThreadId, &attr, ECSearchFolders::SearchThread, (void *)ti)) != 0) {
  225. ec_log_crit("Unable to spawn thread for search: %s", strerror(err));
  226. er = KCERR_NOT_ENOUGH_MEMORY;
  227. }
  228. set_thread_name(lpSearchFolder->sThreadId, "SearchFolders:Folder");
  229. }
  230. l_sf.unlock();
  231. exit:
  232. if (lpCriteria)
  233. FreeSearchCriteria(lpCriteria);
  234. return er;
  235. }
  236. // See if a folder is a search folder
  237. ECRESULT ECSearchFolders::IsSearchFolder(unsigned int ulStoreID, unsigned int ulFolderId)
  238. {
  239. ECRESULT er = erSuccess;
  240. ECDatabase *lpDatabase = NULL;
  241. DB_RESULT lpDBResult;
  242. DB_ROW lpDBRow = NULL;
  243. std::string strQuery;
  244. /* Get database */
  245. er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
  246. if (er != erSuccess)
  247. return er;
  248. // Find out what kind of table this is
  249. strQuery = "SELECT flags FROM hierarchy WHERE id=" + stringify(ulFolderId) + " LIMIT 1";
  250. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  251. if(er != erSuccess)
  252. return er;
  253. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  254. if (lpDBRow == nullptr || lpDBRow[0] == nullptr)
  255. return KCERR_NOT_FOUND;
  256. if (atoui(lpDBRow[0]) != FOLDER_SEARCH)
  257. return KCERR_NOT_FOUND;
  258. return erSuccess;
  259. }
  260. // Cancel a search: stop any rebuild thread and stop processing updates for this search folder
  261. ECRESULT ECSearchFolders::CancelSearchFolder(unsigned int ulStoreID, unsigned int ulFolderId)
  262. {
  263. SEARCHFOLDER *lpFolder = NULL;
  264. // Lock the list, preventing other Cancel requests messing with the thread
  265. ulock_rec l_sf(m_mutexMapSearchFolders);
  266. auto iterStore = m_mapSearchFolders.find(ulStoreID);
  267. if (iterStore == m_mapSearchFolders.cend())
  268. return KCERR_NOT_FOUND;
  269. auto iterFolder = iterStore->second.find(ulFolderId);
  270. if (iterFolder == iterStore->second.cend())
  271. return KCERR_NOT_FOUND;
  272. lpFolder = iterFolder->second;
  273. // Remove the item from the list
  274. iterStore->second.erase(iterFolder);
  275. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_COUNT, -1);
  276. l_sf.unlock();
  277. DestroySearchFolder(lpFolder);
  278. return erSuccess;
  279. }
  280. void ECSearchFolders::DestroySearchFolder(SEARCHFOLDER *lpFolder)
  281. {
  282. unsigned int ulFolderId = lpFolder->ulFolderId;
  283. // Nobody can access lpFolder now, except for us and the search thread
  284. // FIXME check this assumption !!!
  285. // Signal the thread to exit
  286. lpFolder->bThreadExit = true;
  287. /*
  288. * Wait for the thread to signal that lpFolder is no longer in use by
  289. * the thread The condition is used for all threads, so it may have
  290. * been fired for a different thread. This is efficient enough.
  291. */
  292. ulock_normal lk(lpFolder->mMutexThreadFree);
  293. m_condThreadExited.wait(lk, [=](void) { return lpFolder->bThreadFree; });
  294. lk.unlock();
  295. // Nobody is using lpFolder now
  296. delete lpFolder;
  297. // Set the search as stopped in the database
  298. SetStatus(ulFolderId, EC_SEARCHFOLDER_STATUS_STOPPED);
  299. }
  300. /**
  301. * Cancel all the search folders on a store and removing the results
  302. */
  303. ECRESULT ECSearchFolders::RemoveSearchFolder(unsigned int ulStoreID)
  304. {
  305. unsigned int ulFolderID;
  306. std::list<SEARCHFOLDER*> listSearchFolders;
  307. // Lock the list, preventing other Cancel requests messing with the thread
  308. ulock_rec l_sf(m_mutexMapSearchFolders);
  309. auto iterStore = m_mapSearchFolders.find(ulStoreID);
  310. if (iterStore == m_mapSearchFolders.cend())
  311. return KCERR_NOT_FOUND;
  312. for (const auto &p : iterStore->second)
  313. listSearchFolders.push_back(p.second);
  314. iterStore->second.clear();
  315. // Remove store from list, items of the store will be delete in 'DestroySearchFolder'
  316. m_mapSearchFolders.erase(iterStore);
  317. l_sf.unlock();
  318. //@fixme: server shutdown can result in a crash?
  319. for (auto srfolder : listSearchFolders) {
  320. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_COUNT, -1);
  321. ulFolderID = srfolder->ulFolderId;
  322. // Wait and free searchfolder data
  323. DestroySearchFolder(srfolder);
  324. // Remove results from database
  325. ResetResults(ulStoreID, ulFolderID);
  326. }
  327. return erSuccess;
  328. }
  329. // Removing a search folder is subtly different from cancelling it; removing a search folder
  330. // also removes all search results
  331. ECRESULT ECSearchFolders::RemoveSearchFolder(unsigned int ulStoreId, unsigned int ulFolderId)
  332. {
  333. // Cancel any running (rebuilding) searches
  334. CancelSearchFolder(ulStoreId, ulFolderId);
  335. // Ignore errors
  336. // Remove results from database
  337. ResetResults(ulStoreId, ulFolderId);
  338. return erSuccess;
  339. }
  340. // WARNING: THIS FUNCTION IS *NOT* THREADSAFE. IT SHOULD ONLY BE CALLED AT STARTUP WHILE SINGLE-THREADED
  341. ECRESULT ECSearchFolders::RestartSearches()
  342. {
  343. ECRESULT er = erSuccess;
  344. ec_log_crit("Starting rebuild of search folders... This may take a while.");
  345. for (const auto &store_p : m_mapSearchFolders) {
  346. ec_log_crit(" Rebuilding searchfolders of store %d", store_p.first);
  347. for (const auto &folder_p : store_p.second) {
  348. ResetResults(store_p.first, folder_p.first);
  349. Search(store_p.first, folder_p.first, folder_p.second->lpSearchCriteria, NULL, false);
  350. }
  351. }
  352. ec_log_info("Finished rebuild.");
  353. return er;
  354. }
  355. // Should be called for *any* change of *any* message object in the database.
  356. ECRESULT ECSearchFolders::UpdateSearchFolders(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int ulObjId, ECKeyTable::UpdateType ulType)
  357. {
  358. ECRESULT er = erSuccess;
  359. EVENT ev;
  360. ev.ulStoreId = ulStoreId;
  361. ev.ulFolderId = ulFolderId;
  362. ev.ulObjectId = ulObjId;
  363. ev.ulType = ulType;
  364. scoped_rlock l_ev(m_mutexEvents);
  365. // Add the event to the queue
  366. m_lstEvents.push_back(std::move(ev));
  367. /*
  368. * Signal a change in the queue (actually only needed for the first
  369. * event, but this wastes almost no time and is safer.
  370. */
  371. m_condEvents.notify_all();
  372. return er;
  373. }
  374. // Process a list of message changes in a single folder of a certain type
  375. ECRESULT ECSearchFolders::ProcessMessageChange(unsigned int ulStoreId, unsigned int ulFolderId, ECObjectTableList *lstObjectIDs, ECKeyTable::UpdateType ulType)
  376. {
  377. ECRESULT er = erSuccess;
  378. STOREFOLDERIDSEARCH::const_iterator iterStore;
  379. bool bIsInTargetFolder = false;
  380. std::set<unsigned int> setParents;
  381. unsigned int ulOwner = 0;
  382. ECSession *lpSession = NULL;
  383. ECODStore ecOBStore;
  384. SUBRESTRICTIONRESULTS *lpSubResults = NULL;
  385. struct rowSet *lpRowSet = NULL;
  386. struct propTagArray *lpPropTags = NULL;
  387. unsigned int ulParent = 0;
  388. unsigned int ulFlags = 0;
  389. unsigned int ulSCFolderId = 0;
  390. int lCount = 0; // Number of messages added, positive means more added, negative means more discarded
  391. int lUnreadCount = 0; // Same, but for unread count
  392. ECDatabase *lpDatabase = NULL;
  393. std::list<ULONG> lstPrefix;
  394. bool fInserted = false;
  395. lstPrefix.push_back(PR_MESSAGE_FLAGS);
  396. ECLocale locale = m_lpSessionManager->GetSortLocale(ulStoreId);
  397. ulock_rec l_sf(m_mutexMapSearchFolders);
  398. er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
  399. if(er != erSuccess)
  400. goto exit;
  401. iterStore = m_mapSearchFolders.find(ulStoreId);
  402. if (iterStore == m_mapSearchFolders.cend())
  403. // There are no search folders in the target store. We will therefore never match any search
  404. // result and might as well exit now.
  405. goto exit;
  406. // OPTIMIZATION: if a target folder == root folder of ulStoreId, and a recursive searchfolder, then
  407. // the following check is always TRUE
  408. // Get the owner of the search folder. This *could* be different from the owner of the objects!
  409. er = m_lpSessionManager->GetCacheManager()->GetObject(ulStoreId, NULL, &ulOwner, NULL, NULL);
  410. if(er != erSuccess)
  411. goto exit;
  412. // FIXME FIXME FIXME we still need to check MAPI_ASSOCIATED and MSGFLAG_DELETED and exclude them.. better if the caller does this.
  413. // We now have to see if the folder in which the object resides is actually a target of a search folder.
  414. // We do this by checking whether the specified folder is a searchfolder target folder, or a child of
  415. // a target folder if it is a recursive search.
  416. // Loop through search folders for this store
  417. for (const auto &folder : iterStore->second) {
  418. ULONG ulAttempts = 4; // Random number
  419. do {
  420. bIsInTargetFolder = false;
  421. lCount = 0;
  422. lUnreadCount = 0;
  423. if (folder.second->lpSearchCriteria->lpFolders == NULL ||
  424. folder.second->lpSearchCriteria->lpRestrict == NULL)
  425. continue;
  426. er = lpDatabase->Begin();
  427. if (er != erSuccess)
  428. goto exit;
  429. // Lock searchfolder
  430. WITH_SUPPRESSED_LOGGING(lpDatabase)
  431. er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " +
  432. stringify(folder.first) + " FOR UPDATE", NULL);
  433. if (er == KCERR_DATABASE_ERROR) {
  434. DB_ERROR dberr = lpDatabase->GetLastError();
  435. if (dberr != DB_E_LOCK_WAIT_TIMEOUT && dberr != DB_E_LOCK_DEADLOCK) {
  436. ec_log_err("ECSearchFolders::ProcessMessageChange(): select failed");
  437. goto exit;
  438. }
  439. er = lpDatabase->Rollback();
  440. if (er != erSuccess) {
  441. ec_log_crit("ECSearchFolders::ProcessMessageChange(): database rollback failed %d", er);
  442. goto exit;
  443. }
  444. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_RETRY);
  445. continue;
  446. } else if (er != erSuccess) {
  447. ec_log_crit("ECSearchFolders::ProcessMessageChange(): unexpected error %d", er);
  448. goto exit;
  449. }
  450. if(ulType != ECKeyTable::TABLE_ROW_DELETE) {
  451. // Loop through all targets for each searchfolder, if one matches, then match the restriction with the objects
  452. for (unsigned int i = 0; i < folder.second->lpSearchCriteria->lpFolders->__size; ++i)
  453. if (m_lpSessionManager->GetCacheManager()->GetObjectFromEntryId(&folder.second->lpSearchCriteria->lpFolders->__ptr[i], &ulSCFolderId) == erSuccess &&
  454. ulSCFolderId == ulFolderId)
  455. {
  456. bIsInTargetFolder = true;
  457. break;
  458. }
  459. if (!bIsInTargetFolder && folder.second->lpSearchCriteria->ulFlags & RECURSIVE_SEARCH) {
  460. // The item is not in one of the base folders, but it may be in one of children of the folders.
  461. // We do it in this order because the GetParent() calls below may cause database accesses, so
  462. // we only actually do those database accesses if we have to.
  463. unsigned int ulAncestor = ulFolderId;
  464. // Get all the parents of this object (usually around 5 or 6)
  465. setParents.insert(ulFolderId);
  466. while(1) {
  467. er = m_lpSessionManager->GetCacheManager()->GetParent(ulAncestor, &ulAncestor);
  468. if(er != erSuccess)
  469. break;
  470. setParents.insert(ulAncestor);
  471. }
  472. // setParents now contains all the parent of this object, now we can check if any of the ancestors
  473. // are in the search target
  474. for (unsigned int i = 0; i < folder.second->lpSearchCriteria->lpFolders->__size; ++i) {
  475. if (m_lpSessionManager->GetCacheManager()->GetObjectFromEntryId(&folder.second->lpSearchCriteria->lpFolders->__ptr[i], &ulSCFolderId) != erSuccess)
  476. continue;
  477. auto iterParents = setParents.find(ulSCFolderId);
  478. if (iterParents != setParents.cend()) {
  479. bIsInTargetFolder = true;
  480. break;
  481. }
  482. }
  483. }
  484. } else {
  485. // Table type DELETE, so the item is definitely not in the search path. Just delete it
  486. bIsInTargetFolder = false;
  487. }
  488. // The folder in which the modify message is, is in our search path for this searchfolder
  489. if(bIsInTargetFolder) {
  490. // Create a session for the target user
  491. if(lpSession == NULL) {
  492. er = m_lpSessionManager->CreateSessionInternal(&lpSession, ulOwner);
  493. if(er != erSuccess) {
  494. ec_log_crit("ECSearchFolders::ProcessMessageChange(): CreateSessionInternal failed %d", er);
  495. goto exit;
  496. }
  497. lpSession->Lock();
  498. }
  499. ecOBStore.ulStoreId = ulStoreId;
  500. ecOBStore.ulFolderId = 0;
  501. ecOBStore.ulFlags = 0;
  502. ecOBStore.ulObjType = MAPI_MESSAGE;
  503. ecOBStore.lpGuid = NULL;
  504. if(ulType == ECKeyTable::TABLE_ROW_ADD || ulType == ECKeyTable::TABLE_ROW_MODIFY) {
  505. std::list<ULONG> lstPropTags;
  506. // Get the restriction ready for this search folder
  507. er = ECGenericObjectTable::GetRestrictPropTags(folder.second->lpSearchCriteria->lpRestrict, &lstPrefix, &lpPropTags);
  508. if(er != erSuccess) {
  509. ec_log_crit("ECSearchFolders::ProcessMessageChange(): ECGenericObjectTable::GetRestrictPropTags failed %d", er);
  510. goto exit;
  511. }
  512. // Get necessary row data for the object
  513. er = ECStoreObjectTable::QueryRowData(NULL, NULL, lpSession, lstObjectIDs, lpPropTags, &ecOBStore, &lpRowSet, false, false);
  514. if(er != erSuccess) {
  515. ec_log_crit("ECSearchFolders::ProcessMessageChange(): ECStoreObjectTable::QueryRowData failed %d", er);
  516. goto exit;
  517. }
  518. er = RunSubRestrictions(lpSession, &ecOBStore, folder.second->lpSearchCriteria->lpRestrict, lstObjectIDs, locale, &lpSubResults);
  519. if(er != erSuccess) {
  520. ec_log_crit("ECSearchFolders::ProcessMessageChange(): RunSubRestrictions failed %d", er);
  521. goto exit;
  522. }
  523. auto iterObjectIDs = lstObjectIDs->cbegin();
  524. // Check if the item matches for each item
  525. for (gsoap_size_t i = 0; i < lpRowSet->__size; ++i, ++iterObjectIDs) {
  526. bool fMatch;
  527. // Match the restriction
  528. er = ECGenericObjectTable::MatchRowRestrict(lpSession->GetSessionManager()->GetCacheManager(), &lpRowSet->__ptr[i], folder.second->lpSearchCriteria->lpRestrict, lpSubResults, locale, &fMatch);
  529. if(er == erSuccess) {
  530. if (fMatch) {
  531. if(lpRowSet->__ptr[i].__ptr[0].ulPropTag != PR_MESSAGE_FLAGS)
  532. continue;
  533. // Get the read flag for this message
  534. ulFlags = lpRowSet->__ptr[i].__ptr[0].Value.ul & MSGFLAG_READ;
  535. // Update on-disk search folder
  536. if (AddResults(ulStoreId, folder.first, iterObjectIDs->ulObjId, ulFlags, &fInserted) == erSuccess) {
  537. if(fInserted) {
  538. // One more match
  539. ++lCount;
  540. if(!ulFlags)
  541. ++lUnreadCount;
  542. // Send table notification
  543. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
  544. } else {
  545. // Row was modified, so flags has changed. Since the only possible values are MSGFLAG_READ or 0, we know the new flags.
  546. if(ulFlags)
  547. --lUnreadCount; // New state is read, so old state was unread, so --unread
  548. else
  549. ++lUnreadCount; // New state is unread, so old state was read, so ++unread
  550. // Send table notification
  551. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
  552. }
  553. } else {
  554. // AddResults will return an error if the call didn't do anything (record was already in the table).
  555. // Even though, we should still send notifications since the row changed
  556. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
  557. }
  558. } else if (ulType == ECKeyTable::TABLE_ROW_MODIFY) {
  559. // Only delete modified items, not new items
  560. if (DeleteResults(ulStoreId, folder.first, iterObjectIDs->ulObjId, &ulFlags) == erSuccess) {
  561. --lCount;
  562. if(!ulFlags)
  563. --lUnreadCount; // Removed message was unread
  564. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE, 0, folder.first, iterObjectIDs->ulObjId, MAPI_MESSAGE);
  565. }
  566. }
  567. // Ignore errors from the updates
  568. er = erSuccess;
  569. }
  570. }
  571. } else {
  572. // Message was deleted anyway, update on-disk search folder and send table notification
  573. for (const auto &obj_id : *lstObjectIDs)
  574. if (DeleteResults(ulStoreId, folder.first, obj_id.ulObjId, &ulFlags) == erSuccess) {
  575. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE, 0, folder.first, obj_id.ulObjId, MAPI_MESSAGE);
  576. --lCount;
  577. if(!ulFlags)
  578. --lUnreadCount; // Removed message was unread
  579. }
  580. }
  581. if(lpPropTags) {
  582. FreePropTagArray(lpPropTags);
  583. lpPropTags = NULL;
  584. }
  585. if(lpRowSet) {
  586. FreeRowSet(lpRowSet, true);
  587. lpRowSet = NULL;
  588. }
  589. if(lpSubResults){
  590. FreeSubRestrictionResults(lpSubResults);
  591. lpSubResults = NULL;
  592. }
  593. } else {
  594. // Not in a target folder, remove from search results
  595. for (const auto &obj_id : *lstObjectIDs)
  596. if (DeleteResults(ulStoreId, folder.first, obj_id.ulObjId, &ulFlags) == erSuccess) {
  597. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE, 0, folder.first, obj_id.ulObjId, MAPI_MESSAGE);
  598. --lCount;
  599. if(!ulFlags)
  600. --lUnreadCount; // Removed message was unread
  601. }
  602. }
  603. if(lCount || lUnreadCount) {
  604. // If the searchfolder has changed, update counts and send notifications
  605. WITH_SUPPRESSED_LOGGING(lpDatabase) {
  606. er = UpdateFolderCount(lpDatabase, folder.first, PR_CONTENT_COUNT, lCount);
  607. if (er == erSuccess)
  608. er = UpdateFolderCount(lpDatabase, folder.first, PR_CONTENT_UNREAD, lUnreadCount);
  609. }
  610. if (er == KCERR_DATABASE_ERROR) {
  611. DB_ERROR dberr = lpDatabase->GetLastError();
  612. if (dberr == DB_E_LOCK_WAIT_TIMEOUT || dberr == DB_E_LOCK_DEADLOCK) {
  613. ec_log_crit("ECSearchFolders::ProcessMessageChange(): database error(1) %d", dberr);
  614. er = lpDatabase->Rollback();
  615. if (er != erSuccess) {
  616. ec_log_crit("ECSearchFolders::ProcessMessageChange(): database rollback failed(1) %d", er);
  617. goto exit;
  618. }
  619. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_RETRY);
  620. continue;
  621. } else
  622. goto exit;
  623. } else if (er != erSuccess) {
  624. ec_log_crit("ECSearchFolders::ProcessMessageChange(): unexpected error(1) %d", er);
  625. goto exit;
  626. }
  627. m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, folder.first);
  628. er = m_lpSessionManager->NotificationModified(MAPI_FOLDER, folder.first);
  629. if (m_lpSessionManager->GetCacheManager()->GetParent(folder.first, &ulParent) == erSuccess)
  630. er = m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, folder.first, MAPI_FOLDER);
  631. er = erSuccess; // Ignore errors
  632. }
  633. er = lpDatabase->Commit();
  634. if (er == KCERR_DATABASE_ERROR) {
  635. DB_ERROR dberr = lpDatabase->GetLastError();
  636. if (dberr == DB_E_LOCK_WAIT_TIMEOUT || dberr == DB_E_LOCK_DEADLOCK) {
  637. ec_log_crit("ECSearchFolders::ProcessMessageChange(): database error(2) %d", dberr);
  638. er = lpDatabase->Rollback();
  639. if (er != erSuccess) {
  640. ec_log_crit("ECSearchFolders::ProcessMessageChange(): database rollback failed(2) %d", er);
  641. goto exit;
  642. }
  643. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_RETRY);
  644. continue;
  645. } else
  646. goto exit;
  647. } else if (er != erSuccess) {
  648. ec_log_crit("ECSearchFolders::ProcessMessageChange(): unexpected error(2) %d", er);
  649. goto exit;
  650. }
  651. break; // Break the do-while loop since we succeeded
  652. } while (--ulAttempts);
  653. if (ulAttempts == 0) {
  654. // The only way to get here is if all attempts failed with an SQL error.
  655. assert(er != KCERR_DATABASE_ERROR);
  656. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_UPDATE_FAIL);
  657. }
  658. }
  659. exit:
  660. l_sf.unlock();
  661. if(lpPropTags)
  662. FreePropTagArray(lpPropTags);
  663. if(lpSession) {
  664. lpSession->Unlock();
  665. m_lpSessionManager->RemoveSessionInternal(lpSession);
  666. }
  667. delete lpSubResults;
  668. if(lpRowSet)
  669. FreeRowSet(lpRowSet, true);
  670. return er;
  671. }
  672. /**
  673. * Process a set of rows and add them to the searchresults table if they match the restriction
  674. *
  675. * Special note on transactioning:
  676. *
  677. * If you pass bNotify == false, you must begin()/commit() yourself.
  678. * If you pass bNotify == true, this function will begin()/commit() for you.
  679. *
  680. * @param[in] lpDatabase Database
  681. * @param[in] lpSession Session for the user owning the searchfolder
  682. * @param[in] lpRestrict Restriction to test items against
  683. * @param[in] lpbCancel Stops processing if it is set to TRUE while running (from another thread)
  684. * @param[in] ulStoreId Store that the searchfolder is in
  685. * @param[in] ulFolderId ID of the searchfolder
  686. * @param[in] lpODStore Information for the objects in the table
  687. * @param[in] ecRows Rows to process
  688. * @param[in] lpPropTags Precalculated proptags needed to check lpRestrict
  689. * @param[in] locale Locale to process rows in (for comparisons)
  690. * @param[in] bNotify TRUE if we should notify tables in this function, see note about transactions above
  691. * @return result
  692. */
  693. ECRESULT ECSearchFolders::ProcessCandidateRows(ECDatabase *lpDatabase,
  694. ECSession *lpSession, struct restrictTable *lpRestrict, bool *lpbCancel,
  695. unsigned int ulStoreId, unsigned int ulFolderId, ECODStore *lpODStore,
  696. ECObjectTableList ecRows, struct propTagArray *lpPropTags,
  697. const ECLocale &locale, bool bNotify)
  698. {
  699. ECRESULT er = erSuccess;
  700. struct rowSet *lpRowSet = NULL;
  701. SUBRESTRICTIONRESULTS *lpSubResults = NULL;
  702. int lCount = 0;
  703. int lUnreadCount = 0;
  704. bool fMatch = false;
  705. unsigned int ulObjFlags = 0;
  706. unsigned int ulParent = 0;
  707. std::list<unsigned int> lstMatches;
  708. std::list<unsigned int> lstFlags;
  709. assert(lpPropTags->__ptr[0] == PR_MESSAGE_FLAGS);
  710. auto iterRows = ecRows.cbegin();
  711. if(bNotify) {
  712. er = lpDatabase->Begin();
  713. if (er != erSuccess) {
  714. ec_log_err("ECSearchFolders::ProcessCandidateRows() BEGIN failed %d", er);
  715. goto exit;
  716. }
  717. er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " + stringify(ulFolderId) + " FOR UPDATE", NULL);
  718. if (er != erSuccess) {
  719. ec_log_err("ECSearchFolders::ProcessCandidateRows() SELECT failed %d", er);
  720. goto exit;
  721. }
  722. }
  723. // Get the row data for the search
  724. er = ECStoreObjectTable::QueryRowData(NULL, NULL, lpSession, &ecRows, lpPropTags, lpODStore, &lpRowSet, false, false);
  725. if(er != erSuccess) {
  726. ec_log_err("ECSearchFolders::ProcessCandidateRows() ECStoreObjectTable::QueryRowData failed %d", er);
  727. goto exit;
  728. }
  729. // Get the subrestriction results for the search
  730. er = RunSubRestrictions(lpSession, lpODStore, lpRestrict, &ecRows, locale, &lpSubResults);
  731. if(er != erSuccess) {
  732. ec_log_err("ECSearchFolders::ProcessCandidateRows() RunSubRestrictions failed %d", er);
  733. goto exit;
  734. }
  735. // Loop through the results data
  736. lCount=0;
  737. lUnreadCount=0;
  738. for (gsoap_size_t j = 0; j< lpRowSet->__size && (!lpbCancel || !*lpbCancel); ++j, ++iterRows) {
  739. if(ECGenericObjectTable::MatchRowRestrict(lpSession->GetSessionManager()->GetCacheManager(), &lpRowSet->__ptr[j], lpRestrict, lpSubResults, locale, &fMatch) != erSuccess)
  740. continue;
  741. if(!fMatch)
  742. continue;
  743. if(lpRowSet->__ptr[j].__ptr[0].ulPropTag != PR_MESSAGE_FLAGS)
  744. continue;
  745. ulObjFlags = lpRowSet->__ptr[j].__ptr[0].Value.ul & MSGFLAG_READ;
  746. lstMatches.push_back(iterRows->ulObjId);
  747. lstFlags.push_back(ulObjFlags);
  748. }
  749. // Add matched row to database
  750. er = AddResults(ulStoreId, ulFolderId, lstMatches, lstFlags, &lCount, &lUnreadCount);
  751. if (er != erSuccess) {
  752. ec_log_err("ECSearchFolders::ProcessCandidateRows() AddResults failed %d", er);
  753. goto exit;
  754. }
  755. if(lCount || lUnreadCount) {
  756. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_COUNT, lCount);
  757. if (er == erSuccess) {
  758. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_UNREAD, lUnreadCount);
  759. if (er != erSuccess)
  760. ec_log_crit("ECSearchFolders::ProcessCandidateRows() UpdateFolderCount failed(2) %d", er);
  761. }
  762. else {
  763. ec_log_crit("ECSearchFolders::ProcessCandidateRows() UpdateFolderCount failed(1) %d", er);
  764. goto exit;
  765. }
  766. }
  767. if(bNotify) {
  768. er = lpDatabase->Commit();
  769. if (er != erSuccess) {
  770. ec_log_err("ECSearchFolders::ProcessCandidateRows() DB commit failed %d", er);
  771. goto exit;
  772. }
  773. // Add matched row and send a notification to update views of this search (if any are open)
  774. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD, 0, ulFolderId, lstMatches, MAPI_MESSAGE);
  775. }
  776. // If the searchfolder counte have changed, update counts and send notifications
  777. if(bNotify) {
  778. m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulFolderId);
  779. m_lpSessionManager->NotificationModified(MAPI_FOLDER, ulFolderId); // folder has modified due to PR_CONTENT_*
  780. if(m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParent) == erSuccess)
  781. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulFolderId, MAPI_FOLDER); // PR_CONTENT_* has changed in tables too
  782. }
  783. exit:
  784. if(bNotify && lpDatabase && er != erSuccess)
  785. lpDatabase->Rollback();
  786. if(lpRowSet) {
  787. FreeRowSet(lpRowSet, true);
  788. lpRowSet = NULL;
  789. }
  790. if(lpSubResults) {
  791. FreeSubRestrictionResults(lpSubResults);
  792. lpSubResults = NULL;
  793. }
  794. return er;
  795. }
  796. // Does an actual search of a specific search Criteria in store ulStoreId, search folder ulFolderId. Will cancel if *lpbCancel
  797. // is TRUE. We check after each message row set to see if the cancel has been requested.
  798. ECRESULT ECSearchFolders::Search(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCrit, bool *lpbCancel, bool bNotify)
  799. {
  800. ECRESULT er = erSuccess;
  801. ECListInt lstFolders;
  802. ECObjectTableList ecRows;
  803. sObjectTableKey sRow;
  804. ECODStore ecODStore;
  805. ECSession *lpSession = NULL;
  806. unsigned int ulUserId = 0;
  807. std::string strQuery;
  808. ECDatabase *lpDatabase = NULL;
  809. DB_RESULT lpDBResult;
  810. DB_ROW lpDBRow = NULL;
  811. struct propTagArray *lpPropTags = NULL;
  812. unsigned int i=0;
  813. struct restrictTable *lpAdditionalRestrict = NULL;
  814. unsigned int ulParent = 0;
  815. std::string suggestion;
  816. std::list<ULONG> lstPrefix;
  817. lstPrefix.push_back(PR_MESSAGE_FLAGS);
  818. //Indexer
  819. std::list<unsigned int> lstIndexerResults;
  820. GUID guidServer;
  821. GUID guidStore;
  822. ECLocale locale = m_lpSessionManager->GetSortLocale(ulStoreId);
  823. ecODStore.ulStoreId = ulStoreId;
  824. ecODStore.ulFolderId = 0;
  825. ecODStore.ulFlags = 0;
  826. ecODStore.ulObjType = 0;
  827. ecODStore.lpGuid = NULL; // FIXME: optimisation possible
  828. if(lpSearchCrit->lpFolders == NULL || lpSearchCrit->lpRestrict == NULL) {
  829. er = KCERR_NOT_FOUND;
  830. ec_log_err("ECSearchFolders::Search() no folder or search criteria");
  831. goto exit;
  832. }
  833. er = m_lpSessionManager->GetCacheManager()->GetStore(ulStoreId, NULL, &guidStore);
  834. if(er != erSuccess) {
  835. ec_log_crit("ECSearchFolders::Search() GetStore failed: 0x%x", er);
  836. goto exit;
  837. }
  838. ecODStore.lpGuid = &guidStore;
  839. // Get the owner of the store
  840. er = m_lpSessionManager->GetCacheManager()->GetObject(ulStoreId, NULL, &ulUserId, NULL, NULL);
  841. if(er != erSuccess) {
  842. ec_log_crit("ECSearchFolders::Search() GetObject failed: 0x%x", er);
  843. goto exit;
  844. }
  845. // Create a session with the security credentials of the owner of the store
  846. er = m_lpSessionManager->CreateSessionInternal(&lpSession, ulUserId);
  847. if(er != erSuccess) {
  848. ec_log_crit("ECSearchFolders::Search() CreateSessionInternal failed: 0x%x", er);
  849. goto exit;
  850. }
  851. lpSession->Lock();
  852. er = lpSession->GetDatabase(&lpDatabase);
  853. if(er != erSuccess) {
  854. ec_log_crit("ECSearchFolders::Search() GetDatabase failed: 0x%x", er);
  855. goto exit;
  856. }
  857. // Get target folders
  858. er = m_lpSessionManager->GetCacheManager()->GetEntryListToObjectList(lpSearchCrit->lpFolders, &lstFolders);
  859. if (er != erSuccess) {
  860. ec_log_crit("ECSearchFolders::Search() GetEntryListToObjectList failed: 0x%x", er);
  861. goto exit;
  862. }
  863. // Reset search results in database
  864. er = ResetResults(ulStoreId, ulFolderId);
  865. if(er != erSuccess) {
  866. ec_log_crit("ECSearchFolders::Search() ResetResults failed: 0x%x", er);
  867. goto exit;
  868. }
  869. // Expand target folders if recursive
  870. if(lpSearchCrit->ulFlags & RECURSIVE_SEARCH) {
  871. auto iterFolders = lstFolders.cbegin();
  872. while (iterFolders != lstFolders.cend()) {
  873. 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";
  874. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  875. if(er == erSuccess) {
  876. while ((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL)
  877. if(lpDBRow && lpDBRow[0])
  878. lstFolders.push_back(atoi(lpDBRow[0]));
  879. } else
  880. ec_log_crit("ECSearchFolders::Search() could not expand target folders: 0x%x", er);
  881. ++iterFolders;
  882. }
  883. }
  884. // Check if we can use indexed search
  885. er = m_lpSessionManager->GetServerGUID(&guidServer);
  886. if(er != erSuccess)
  887. goto exit;
  888. if (GetIndexerResults(lpDatabase, m_lpSessionManager->GetConfig(), m_lpSessionManager->GetCacheManager(), &guidServer, &guidStore, lstFolders, lpSearchCrit->lpRestrict, &lpAdditionalRestrict, lstIndexerResults, suggestion) == erSuccess) {
  889. 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) + "'";
  890. er = lpDatabase->DoInsert(strQuery);
  891. if(er != erSuccess) {
  892. ec_log_err("ECSearchFolders::Search(): could not add suggestion");
  893. goto exit;
  894. }
  895. // Get the additional restriction properties ready
  896. er = ECGenericObjectTable::GetRestrictPropTags(lpAdditionalRestrict, &lstPrefix, &lpPropTags);
  897. if(er != erSuccess) {
  898. ec_log_err("ECSearchFolders::Search() ECGenericObjectTable::GetRestrictPropTags failed: 0x%x", er);
  899. goto exit;
  900. }
  901. // Since an indexed search should be fast, do the entire query as a single transaction, and notify after Commit()
  902. er = lpDatabase->Begin();
  903. if (er != erSuccess) {
  904. ec_log_err("ECSearchFolders::Search() database begin failed: 0x%x", er);
  905. goto exit;
  906. }
  907. auto iterResults = lstIndexerResults.cbegin();
  908. while(1) {
  909. // Loop through the results data
  910. int n = 0;
  911. ecRows.clear();
  912. for (; iterResults != lstIndexerResults.cend() &&
  913. (lpbCancel == NULL || !*lpbCancel) && n < 200; ++iterResults) {
  914. sRow.ulObjId = *iterResults;
  915. sRow.ulOrderId = 0;
  916. ecRows.push_back(sRow);
  917. }
  918. if(ecRows.empty())
  919. break; // no more rows
  920. // Note that we do not want ProcessCandidateRows to send notifications since we will send a bulk TABLE_CHANGE later, so bNotify == false here
  921. er = ProcessCandidateRows(lpDatabase, lpSession, lpAdditionalRestrict, lpbCancel, ulStoreId, ulFolderId, &ecODStore, ecRows, lpPropTags, locale, false);
  922. if (er != erSuccess) {
  923. ec_log_err("ECSearchFolders::Search() ProcessCandidateRows failed: 0x%x", er);
  924. goto exit;
  925. }
  926. }
  927. er = lpDatabase->Commit();
  928. if (er != erSuccess) {
  929. ec_log_err("ECSearchFolders::Search() database commit failed: 0x%x", er);
  930. goto exit;
  931. }
  932. // Notify the search folder and his parent
  933. if(bNotify) {
  934. // Add matched rows and send a notification to update views of this search (if any are open)
  935. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_CHANGE, 0, ulFolderId, 0, MAPI_MESSAGE);
  936. m_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulFolderId);
  937. m_lpSessionManager->NotificationModified(MAPI_FOLDER, ulFolderId); // folder has modified due to PR_CONTENT_*
  938. if(m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParent) == erSuccess)
  939. m_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulFolderId, MAPI_FOLDER); // PR_CONTENT_* has changed in tables too
  940. }
  941. } else {
  942. // Get the restriction ready for this search folder
  943. er = ECGenericObjectTable::GetRestrictPropTags(lpSearchCrit->lpRestrict, &lstPrefix, &lpPropTags);
  944. if(er != erSuccess) {
  945. ec_log_err("ECSearchFolders::Search() ECGenericObjectTable::GetRestrictPropTags failed: 0x%x", er);
  946. goto exit;
  947. }
  948. // If we needn't notify, we don't need to commit each message before notifying, so Begin() here
  949. if(!bNotify)
  950. lpDatabase->Begin();
  951. // lstFolders now contains all folders to search through
  952. for (auto iterFolders = lstFolders.cbegin();
  953. iterFolders != lstFolders.cend() &&
  954. (lpbCancel == NULL || !*lpbCancel); ++iterFolders) {
  955. // Optimisation: we know the folder id of the objects we're querying
  956. ecODStore.ulFolderId = *iterFolders;
  957. // Get a list of messages in folders, sorted descending by creation date so the newest are found first
  958. 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";
  959. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  960. if(er != erSuccess) {
  961. ec_log_err("ECSearchFolders::Search() SELECT failed: 0x%x", er);
  962. continue;
  963. }
  964. while(1) {
  965. if(lpbCancel && *lpbCancel)
  966. break;
  967. // Read max. 20 rows from the database
  968. i = 0;
  969. ecRows.clear();
  970. while(i < 20 && (lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
  971. if(lpDBRow[0] == NULL)
  972. continue;
  973. sRow.ulObjId = atoui(lpDBRow[0]);
  974. sRow.ulOrderId = 0;
  975. ecRows.push_back(sRow);
  976. ++i;
  977. }
  978. if(ecRows.empty())
  979. break; // no more rows
  980. er = ProcessCandidateRows(lpDatabase, lpSession, lpSearchCrit->lpRestrict, lpbCancel, ulStoreId, ulFolderId, &ecODStore, ecRows, lpPropTags, locale, bNotify);
  981. if (er != erSuccess) {
  982. ec_log_err("ECSearchFolders::Search() ProcessCandidateRows failed: 0x%x", er);
  983. goto exit;
  984. }
  985. }
  986. }
  987. // Search done
  988. // If we needn't notify, we don't need to commit each message before notifying, so Commit() here
  989. if(!bNotify)
  990. lpDatabase->Commit();
  991. } //if(!bUseIndexer)
  992. // Save this information in the database.
  993. SetStatus(ulFolderId, EC_SEARCHFOLDER_STATUS_RUNNING);
  994. exit:
  995. if(lpDatabase && er != erSuccess)
  996. lpDatabase->Rollback();
  997. if(lpSession) {
  998. lpSession->Unlock();
  999. m_lpSessionManager->RemoveSessionInternal(lpSession);
  1000. }
  1001. if(lpPropTags)
  1002. FreePropTagArray(lpPropTags);
  1003. if (lpAdditionalRestrict)
  1004. FreeRestrictTable(lpAdditionalRestrict);
  1005. return er;
  1006. }
  1007. // Return whether we are stopped (no entry found), active (no thread found), or rebuilding (thread active)
  1008. ECRESULT ECSearchFolders::GetState(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int *lpulState)
  1009. {
  1010. ECRESULT er = erSuccess;
  1011. unsigned int ulState = 0;
  1012. auto iterStore = m_mapSearchFolders.find(ulStoreId);
  1013. if (iterStore == m_mapSearchFolders.cend()) {
  1014. ulState = 0;
  1015. } else {
  1016. auto iterFolder = iterStore->second.find(ulFolderId);
  1017. if (iterFolder == iterStore->second.cend()) {
  1018. ulState = 0;
  1019. } else {
  1020. ulState = SEARCH_RUNNING;
  1021. if(iterFolder->second->bThreadFree == false)
  1022. ulState |= SEARCH_REBUILD;
  1023. }
  1024. }
  1025. *lpulState = ulState;
  1026. return er;
  1027. }
  1028. // Entrypoint for the SearchThread
  1029. void* ECSearchFolders::SearchThread(void *lpParam)
  1030. {
  1031. auto ti = static_cast<THREADINFO *>(lpParam);
  1032. auto lpFolder = ti->lpFolder; // The entry in the m_mapSearchFolders map
  1033. auto lpSearchFolders = ti->lpSearchFolders; // The main ECSearchFolders object
  1034. // We no longer need this
  1035. delete ti;
  1036. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_THREADS);
  1037. // Start the search
  1038. lpSearchFolders->Search(lpFolder->ulStoreId, lpFolder->ulFolderId, lpFolder->lpSearchCriteria, &lpFolder->bThreadExit);
  1039. // Signal search complete to clients
  1040. lpSearchFolders->m_lpSessionManager->NotificationSearchComplete(lpFolder->ulFolderId, lpFolder->ulStoreId);
  1041. /* Signal exit from thread */
  1042. ulock_normal l_thr(lpFolder->mMutexThreadFree);
  1043. lpFolder->bThreadFree = true;
  1044. lpSearchFolders->m_condThreadExited.notify_one();
  1045. l_thr.unlock();
  1046. // We may not access lpFolder from this point on (it will be freed when the searchfolder is removed)
  1047. lpFolder = NULL;
  1048. g_lpStatsCollector->Increment(SCN_SEARCHFOLDER_THREADS, -1);
  1049. return NULL;
  1050. }
  1051. // Functions to do things in the database
  1052. ECRESULT ECSearchFolders::ResetResults(unsigned int ulStoreId, unsigned int ulFolderId)
  1053. {
  1054. ECDatabase *lpDatabase = NULL;
  1055. ECRESULT er = erSuccess;
  1056. unsigned int ulParentId = 0;
  1057. std::string strQuery;
  1058. er = m_lpSessionManager->GetCacheManager()->GetParent(ulFolderId, &ulParentId);
  1059. if(er != erSuccess) {
  1060. ec_log_crit("ECSearchFolders::ResetResults(): GetParent failed 0x%x", er);
  1061. goto exit;
  1062. }
  1063. er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
  1064. if(er != erSuccess) {
  1065. ec_log_crit("ECSearchFolders::ResetResults(): GetThreadLocalDatabase failed 0x%x", er);
  1066. goto exit;
  1067. }
  1068. er = lpDatabase->Begin();
  1069. if (er != erSuccess) {
  1070. ec_log_err("ECSearchFolders::ResetResults(): BEGIN failed 0x%x", er);
  1071. goto exit;
  1072. }
  1073. er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " + stringify(ulFolderId) + " FOR UPDATE", NULL);
  1074. if (er != erSuccess) {
  1075. ec_log_err("ECSearchFolders::ResetResults(): SELECT failed 0x%x", er);
  1076. goto exit;
  1077. }
  1078. strQuery = "DELETE FROM searchresults WHERE folderid = " + stringify(ulFolderId);
  1079. er = lpDatabase->DoDelete(strQuery);
  1080. if(er != erSuccess) {
  1081. ec_log_err("ECSearchFolders::ResetResults(): DELETE failed 0x%x", er);
  1082. goto exit;
  1083. }
  1084. 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));
  1085. er = lpDatabase->DoUpdate(strQuery);
  1086. if(er != erSuccess) {
  1087. ec_log_err("ECSearchFolders::ResetResults(): DoUpdate failed 0x%x", er);
  1088. goto exit;
  1089. }
  1090. er = UpdateTProp(lpDatabase, PR_CONTENT_COUNT, ulParentId, ulFolderId);
  1091. if(er != erSuccess) {
  1092. ec_log_err("ECSearchFolders::ResetResults(): UpdateTProp failed(1) 0x%x", er);
  1093. goto exit;
  1094. }
  1095. er = UpdateTProp(lpDatabase, PR_CONTENT_UNREAD, ulParentId, ulFolderId);
  1096. if(er != erSuccess) {
  1097. ec_log_err("ECSearchFolders::ResetResults(): UpdateTProp failed(2) 0x%x", er);
  1098. goto exit;
  1099. }
  1100. er = lpDatabase->Commit();
  1101. if (er != erSuccess)
  1102. ec_log_err("ECSearchFolders::ResetResults(): database commit failed 0x%x", er);
  1103. exit:
  1104. if (er != erSuccess && lpDatabase)
  1105. lpDatabase->Rollback();
  1106. return er;
  1107. }
  1108. // Add a single search result message (eg one match in a search folder)
  1109. ECRESULT ECSearchFolders::AddResults(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int ulObjId, unsigned int ulFlags, bool *lpfInserted)
  1110. {
  1111. ECDatabase *lpDatabase = NULL;
  1112. ECRESULT er = erSuccess;
  1113. std::string strQuery;
  1114. DB_RESULT lpDBResult;
  1115. DB_ROW lpDBRow = NULL;
  1116. assert((ulFlags &~ MSGFLAG_READ) == 0);
  1117. er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
  1118. if(er != erSuccess) {
  1119. ec_log_crit("ECSearchFolders::AddResults(): GetThreadLocalDatabase failed 0x%x", er);
  1120. return er;
  1121. }
  1122. strQuery = "SELECT flags FROM searchresults WHERE folderid = " + stringify(ulFolderId) + " AND hierarchyid = " + stringify(ulObjId) + " LIMIT 1";
  1123. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1124. if(er != erSuccess) {
  1125. ec_log_err("ECSearchFolders::AddResults(): select searchresults failed 0x%x", er);
  1126. return er;
  1127. }
  1128. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1129. if (lpDBRow != nullptr && lpDBRow[0] != nullptr && atoui(lpDBRow[0]) == ulFlags)
  1130. // 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
  1131. return KCERR_NOT_FOUND;
  1132. // This will either update or insert the record
  1133. strQuery = "INSERT INTO searchresults (folderid, hierarchyid, flags) VALUES(" + stringify(ulFolderId) + "," + stringify(ulObjId) + "," + stringify(ulFlags) + ") ON DUPLICATE KEY UPDATE flags=" + stringify(ulFlags);
  1134. er = lpDatabase->DoInsert(strQuery);
  1135. if(er != erSuccess) {
  1136. ec_log_err("ECSearchFolders::AddResults(): INSERT failed 0x%x", er);
  1137. return er;
  1138. }
  1139. // We have inserted if the previous SELECT returned no row
  1140. if (lpfInserted)
  1141. *lpfInserted = (lpDBRow == NULL);
  1142. return erSuccess;
  1143. }
  1144. ECRESULT ECSearchFolders::AddResults(unsigned int ulStoreId, unsigned int ulFolderId, std::list<unsigned int> &lstObjId, std::list<unsigned int>& lstFlags, int *lpulCount, int *lpulUnread)
  1145. {
  1146. ECDatabase *lpDatabase = NULL;
  1147. ECRESULT er;
  1148. std::string strQuery;
  1149. unsigned int ulInserted = 0;
  1150. unsigned int ulModified = 0;
  1151. assert(lstObjId.size() == lstFlags.size());
  1152. if(lstObjId.empty())
  1153. return erSuccess;
  1154. er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
  1155. if(er != erSuccess) {
  1156. ec_log_crit("ECSearchFolders::AddResults(): GetThreadLocalDatabase failed 0x%x", er);
  1157. return er;
  1158. }
  1159. strQuery = "INSERT IGNORE INTO searchresults (folderid, hierarchyid, flags) VALUES";
  1160. for (const auto n : lstObjId) {
  1161. strQuery += "(";
  1162. strQuery += stringify(ulFolderId);
  1163. strQuery += ",";
  1164. strQuery += stringify(n);
  1165. strQuery += ",1),";
  1166. }
  1167. strQuery.resize(strQuery.size()-1);
  1168. er = lpDatabase->DoInsert(strQuery, NULL, &ulInserted);
  1169. if (er != erSuccess) {
  1170. ec_log_err("ECSearchFolders::AddResults(): DoInsert failed 0x%x", er);
  1171. return er;
  1172. }
  1173. /*
  1174. * Combining the following queries in one query seems to cause MySQL to do a range- or gaplock, causing deadlocks
  1175. * when folders with adjacent ids are updated at the same time.
  1176. */
  1177. for (auto i = lstFlags.cbegin(), j = lstObjId.cbegin();
  1178. i != lstFlags.cend(); ++i, ++j) {
  1179. if(*i == 0) {
  1180. unsigned int modified = 0;
  1181. strQuery = "UPDATE searchresults SET flags = 0 WHERE hierarchyid = " + stringify(*j) + " AND folderid = " + stringify(ulFolderId);
  1182. er = lpDatabase->DoUpdate(strQuery, &modified);
  1183. if (er != erSuccess) {
  1184. ec_log_err("ECSearchFolders::AddResults(): UPDATE failed 0x%x", er);
  1185. return er;
  1186. }
  1187. ulModified += modified;
  1188. }
  1189. }
  1190. if (lpulCount != NULL)
  1191. *lpulCount += ulInserted;
  1192. if (lpulUnread != NULL)
  1193. *lpulUnread += ulModified;
  1194. return erSuccess;
  1195. }
  1196. // 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
  1197. ECRESULT ECSearchFolders::DeleteResults(unsigned int ulStoreId, unsigned int ulFolderId, unsigned int ulObjId, unsigned int *lpulOldFlags)
  1198. {
  1199. ECDatabase *lpDatabase = NULL;
  1200. ECRESULT er = erSuccess;
  1201. std::string strQuery;
  1202. DB_RESULT lpResult;
  1203. DB_ROW lpRow = NULL;
  1204. unsigned int ulAffected = 0;
  1205. er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
  1206. if (er != erSuccess)
  1207. return er;
  1208. if(lpulOldFlags) {
  1209. strQuery = "SELECT flags FROM searchresults WHERE folderid=" + stringify(ulFolderId) + " AND hierarchyid=" + stringify(ulObjId) + " LIMIT 1";
  1210. er = lpDatabase->DoSelect(strQuery, &lpResult);
  1211. if(er != erSuccess) {
  1212. ec_log_err("ECSearchFolders::DeleteResults(): SELECT failed 0x%x", er);
  1213. return er;
  1214. }
  1215. lpRow = lpDatabase->FetchRow(lpResult);
  1216. if (lpRow == nullptr || lpRow[0] == nullptr)
  1217. return KCERR_NOT_FOUND;
  1218. *lpulOldFlags = atoui(lpRow[0]);
  1219. }
  1220. strQuery = "DELETE FROM searchresults WHERE folderid=" + stringify(ulFolderId) + " AND hierarchyid=" + stringify(ulObjId);
  1221. er = lpDatabase->DoDelete(strQuery, &ulAffected);
  1222. if(er != erSuccess) {
  1223. ec_log_err("ECSearchFolders::DeleteResults(): DELETE failed 0x%x", er);
  1224. return er;
  1225. }
  1226. return ulAffected != 0 ? erSuccess : KCERR_NOT_FOUND;
  1227. }
  1228. // Write the status of a search folder to the PR_EC_SEARCHFOLDER_STATUS property
  1229. ECRESULT ECSearchFolders::SetStatus(unsigned int ulFolderId, unsigned int ulStatus)
  1230. {
  1231. ECDatabase *lpDatabase = NULL;
  1232. ECRESULT er;
  1233. std::string strQuery;
  1234. // Do not use transactions because this function is called inside a transaction.
  1235. er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
  1236. if(er != erSuccess) {
  1237. ec_log_crit("ECSearchFolders::SetStatus(): GetThreadLocalDatabase failed 0x%x", er);
  1238. return er;
  1239. }
  1240. // No record == running
  1241. if(ulStatus != EC_SEARCHFOLDER_STATUS_RUNNING) {
  1242. strQuery = "REPLACE INTO properties (tag, type, hierarchyid, val_ulong) "
  1243. "VALUES(" + stringify(PROP_ID(PR_EC_SEARCHFOLDER_STATUS)) + "," +
  1244. stringify(PROP_TYPE(PR_EC_SEARCHFOLDER_STATUS)) + "," +
  1245. stringify(ulFolderId) + "," +
  1246. stringify(ulStatus) + ")";
  1247. er = lpDatabase->DoInsert(strQuery);
  1248. if(er != erSuccess) {
  1249. ec_log_err("ECSearchFolders::SetStatus(): DoInsert failed 0x%x", er);
  1250. return er;
  1251. }
  1252. } else {
  1253. strQuery = "DELETE FROM properties "
  1254. "WHERE hierarchyid=" + stringify(ulFolderId) +
  1255. " AND tag=" + stringify(PROP_ID(PR_EC_SEARCHFOLDER_STATUS)) +
  1256. " AND type=" + stringify(PROP_TYPE(PR_EC_SEARCHFOLDER_STATUS));
  1257. er = lpDatabase->DoDelete(strQuery);
  1258. if (er != erSuccess) {
  1259. ec_log_err("ECSearchFolders::SetStatus(): DELETE failed 0x%x", er);
  1260. return er;
  1261. }
  1262. }
  1263. return erSuccess;
  1264. }
  1265. // Get all results of a certain search folder in a list of hierarchy IDs
  1266. ECRESULT ECSearchFolders::GetSearchResults(unsigned int ulStoreId, unsigned int ulFolderId, std::list<unsigned int> *lstObjIds)
  1267. {
  1268. ECDatabase *lpDatabase = NULL;
  1269. DB_RESULT lpResult;
  1270. DB_ROW lpRow = NULL;
  1271. ECRESULT er = erSuccess;
  1272. std::string strQuery;
  1273. er = GetThreadLocalDatabase(this->m_lpDatabaseFactory, &lpDatabase);
  1274. if(er != erSuccess) {
  1275. ec_log_crit("ECSearchFolders::GetSearchResults(): GetThreadLocalDatabase failed 0x%x", er);
  1276. return er;
  1277. }
  1278. strQuery = "SELECT hierarchyid FROM searchresults WHERE folderid=" + stringify(ulFolderId);
  1279. er = lpDatabase->DoSelect(strQuery, &lpResult);
  1280. if(er != erSuccess) {
  1281. ec_log_err("ECSearchFolders::GetSearchResults(): SELECT failed 0x%x", er);
  1282. return er;
  1283. }
  1284. lstObjIds->clear();
  1285. while(1) {
  1286. lpRow = lpDatabase->FetchRow(lpResult);
  1287. if(lpRow == NULL || lpRow[0] == NULL)
  1288. break;
  1289. lstObjIds->push_back(atoui(lpRow[0]));
  1290. }
  1291. return erSuccess;
  1292. }
  1293. // Loads the search criteria from the database
  1294. ECRESULT ECSearchFolders::LoadSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria **lppSearchCriteria)
  1295. {
  1296. ECRESULT er = erSuccess;
  1297. ECDatabase *lpDatabase = NULL;
  1298. DB_RESULT lpDBResult;
  1299. DB_ROW lpDBRow = NULL;
  1300. std::string strQuery;
  1301. struct soap xmlsoap;
  1302. // Get database
  1303. er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
  1304. if(er != erSuccess) {
  1305. ec_log_crit("ECSearchFolders::LoadSearchCriteria(): GetThreadLocalDatabase failed 0x%x", er);
  1306. return er;
  1307. }
  1308. // We use the soap serializer / deserializer to store the data
  1309. soap_set_mode(&xmlsoap, SOAP_XML_TREE | SOAP_C_UTFSTRING);
  1310. // Find out what kind of table this is
  1311. 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";
  1312. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1313. if(er != erSuccess) {
  1314. ec_log_err("ECSearchFolders::LoadSearchCriteria(): SELECT failed 0x%x", er);
  1315. return er;
  1316. }
  1317. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1318. if(lpDBRow && lpDBRow[0] && atoi(lpDBRow[0]) == 2 && lpDBRow[1]) {
  1319. std::string xmldata(lpDBRow[1]);
  1320. std::istringstream xml(xmldata);
  1321. struct searchCriteria crit;
  1322. xmlsoap.is = &xml;
  1323. soap_default_searchCriteria(&xmlsoap, &crit);
  1324. if (soap_begin_recv(&xmlsoap) != 0)
  1325. return KCERR_NETWORK_ERROR;
  1326. soap_get_searchCriteria(&xmlsoap, &crit, "SearchCriteria", NULL);
  1327. // We now have the object, allocated by xmlsoap object,
  1328. if (soap_end_recv(&xmlsoap) != 0)
  1329. er = KCERR_NETWORK_ERROR;
  1330. else
  1331. er = CopySearchCriteria(nullptr, &crit, lppSearchCriteria);
  1332. /*
  1333. * We do not need the error here: lppSearchCriteria will not be
  1334. * touched, and we need to free the soap structs.
  1335. */
  1336. soap_destroy(&xmlsoap);
  1337. soap_end(&xmlsoap);
  1338. soap_done(&xmlsoap);
  1339. } else {
  1340. er = KCERR_NOT_FOUND;
  1341. }
  1342. return er;
  1343. }
  1344. // Saves the search criteria in the database
  1345. ECRESULT ECSearchFolders::SaveSearchCriteria(unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCriteria)
  1346. {
  1347. ECRESULT er = erSuccess;
  1348. ECDatabase *lpDatabase = NULL;
  1349. // Get database
  1350. er = GetThreadLocalDatabase(m_lpDatabaseFactory, &lpDatabase);
  1351. if(er != erSuccess) {
  1352. ec_log_crit("ECSearchFolders::SaveSearchCriteria(): GetThreadLocalDatabase failed 0x%x", er);
  1353. goto exit;
  1354. }
  1355. er = lpDatabase->Begin();
  1356. if(er != hrSuccess) {
  1357. ec_log_err("ECSearchFolders::SaveSearchCriteria(): BEGIN failed 0x%x", er);
  1358. goto exit;
  1359. }
  1360. er = SaveSearchCriteria(lpDatabase, ulStoreId, ulFolderId, lpSearchCriteria);
  1361. if(er != hrSuccess) {
  1362. ec_log_err("ECSearchFolders::SaveSearchCriteria(): SaveSearchCriteria failed 0x%x", er);
  1363. goto exit;
  1364. }
  1365. er = lpDatabase->Commit();
  1366. if (er != hrSuccess)
  1367. ec_log_err("ECSearchFolders::SaveSearchCriteria(): commit failed 0x%x", er);
  1368. exit:
  1369. if(lpDatabase && er != erSuccess)
  1370. lpDatabase->Rollback();
  1371. return er;
  1372. }
  1373. // Serialize and save the search criteria for a certain folder. The property is saved as a PR_EC_SEARCHCRIT property
  1374. ECRESULT ECSearchFolders::SaveSearchCriteria(ECDatabase *lpDatabase, unsigned int ulStoreId, unsigned int ulFolderId, struct searchCriteria *lpSearchCriteria)
  1375. {
  1376. ECRESULT er;
  1377. std::string strQuery;
  1378. struct soap xmlsoap;
  1379. struct searchCriteria sSearchCriteria;
  1380. std::ostringstream xml;
  1381. // We use the soap serializer / deserializer to store the data
  1382. soap_set_mode(&xmlsoap, SOAP_XML_TREE | SOAP_C_UTFSTRING);
  1383. sSearchCriteria.lpFolders = lpSearchCriteria->lpFolders;
  1384. sSearchCriteria.lpRestrict = lpSearchCriteria->lpRestrict;
  1385. sSearchCriteria.ulFlags = lpSearchCriteria->ulFlags;
  1386. xmlsoap.os = &xml;
  1387. soap_serialize_searchCriteria(&xmlsoap, &sSearchCriteria);
  1388. soap_begin_send(&xmlsoap);
  1389. soap_put_searchCriteria(&xmlsoap, &sSearchCriteria, "SearchCriteria",NULL);
  1390. soap_end_send(&xmlsoap);
  1391. // Make sure we're linking with the correct SOAP (c++ version)
  1392. assert(!xml.str().empty());
  1393. // xml now contains XML version of search criteria
  1394. // Replace PR_EC_SEARCHCRIT in database
  1395. 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));
  1396. er = lpDatabase->DoDelete(strQuery);
  1397. if(er != erSuccess)
  1398. return er;
  1399. 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() ) + "')";
  1400. return lpDatabase->DoInsert(strQuery);
  1401. }
  1402. void ECSearchFolders::FlushAndWait()
  1403. {
  1404. ulock_rec l_ev(m_mutexEvents);
  1405. m_condEvents.notify_all();
  1406. l_ev.unlock();
  1407. // let ProcessThread get this lock, and mark the thread running
  1408. l_ev.lock();
  1409. l_ev.unlock();
  1410. // wait for an inactive search thread
  1411. while (m_bRunning) Sleep(10);
  1412. }
  1413. /*
  1414. * This is the main processing thread, which processes changes from the queue. After processing it removes them from the queue and waits for
  1415. * new events
  1416. */
  1417. void * ECSearchFolders::ProcessThread(void *lpSearchFolders)
  1418. {
  1419. auto lpThis = static_cast<ECSearchFolders *>(lpSearchFolders);
  1420. while(1) {
  1421. // Get events to process
  1422. ulock_rec l_ev(lpThis->m_mutexEvents);
  1423. if (lpThis->m_bExitThread)
  1424. break;
  1425. if (lpThis->m_lstEvents.empty())
  1426. /*
  1427. * No events, wait until one arrives (the mutex is
  1428. * unlocked by pthread_cond_wait so people are able to
  1429. * add new events). The condition also occurs when the
  1430. * server is exiting.
  1431. */
  1432. lpThis->m_condEvents.wait(l_ev);
  1433. lpThis->m_bRunning = true;
  1434. /*
  1435. * The condition ended. Two things can have happened: there is
  1436. * now at least one event waiting, or and exit has been
  1437. * requested. In both cases, we simply unlock the mutex and
  1438. * process any (may be 0) events currently in the queue. This
  1439. * means that the caller must make sure that no new events can
  1440. * be added after the m_bThreadExit flag is set to TRUE.
  1441. */
  1442. l_ev.unlock();
  1443. lpThis->FlushEvents();
  1444. Sleep(1000);
  1445. lpThis->m_bRunning = false;
  1446. // Check if we need to exit
  1447. }
  1448. return NULL;
  1449. }
  1450. struct FOLDERSORT {
  1451. bool operator () (const EVENT &a, const EVENT &b) { return a.ulFolderId < b.ulFolderId; }
  1452. };
  1453. // Process all waiting events in an efficient order
  1454. ECRESULT ECSearchFolders::FlushEvents()
  1455. {
  1456. ECRESULT er = erSuccess;
  1457. std::list<EVENT> lstEvents;
  1458. ECObjectTableList lstObjectIDs;
  1459. sObjectTableKey sRow;
  1460. FOLDERSORT sort;
  1461. unsigned int ulStoreId = 0;
  1462. unsigned int ulFolderId = 0;
  1463. ECKeyTable::UpdateType ulType;
  1464. // We do a copy-remove-process cycle here to keep the event queue locked for the least time as possible with
  1465. // 500 events at a time
  1466. ulock_rec l_ev(m_mutexEvents);
  1467. for (int i = 0; i < 500; ++i) {
  1468. // Move the first element of m_lstEvents to the head of our list.
  1469. if(m_lstEvents.empty())
  1470. break;
  1471. lstEvents.splice(lstEvents.end(), m_lstEvents, m_lstEvents.begin());
  1472. }
  1473. l_ev.unlock();
  1474. // Sort the items by folder. The order of DELETE and ADDs will remain unchanged. This is important
  1475. // because the order of the incoming ADD or DELETE is obviously important for the final result.
  1476. lstEvents.sort(sort);
  1477. // Send the changes grouped by folder (and therefore also by store)
  1478. ulStoreId = 0;
  1479. ulFolderId = 0;
  1480. ulType = ECKeyTable::TABLE_ROW_MODIFY;
  1481. // Process changes by finding sequences of events of the same type (eg ADD ADD ADD DELETE will result in two sequences: 3xADD + 1xDELETE)
  1482. for (const auto &event : lstEvents) {
  1483. if (event.ulFolderId != ulFolderId || event.ulType != ulType) {
  1484. if(!lstObjectIDs.empty()) {
  1485. // This is important: make the events unique. We need to do this because the ECStoreObjectTable
  1486. // row engine does not support requesting the exact same row twice within the same call. If we have
  1487. // duplicates here, this will filter through to the row engine and cause all kinds of nastiness, mainly
  1488. // causing the item to be deleted from search folders irrespective of whether it should have been deleted
  1489. // or added.
  1490. lstObjectIDs.sort();
  1491. lstObjectIDs.unique();
  1492. ProcessMessageChange(ulStoreId, ulFolderId, &lstObjectIDs, ulType);
  1493. lstObjectIDs.clear();
  1494. }
  1495. }
  1496. ulStoreId = event.ulStoreId;
  1497. ulFolderId = event.ulFolderId;
  1498. ulType = event.ulType;
  1499. sRow.ulObjId = event.ulObjectId;
  1500. sRow.ulOrderId = 0;
  1501. lstObjectIDs.push_back(sRow);
  1502. }
  1503. // Flush last set
  1504. if(!lstObjectIDs.empty()) {
  1505. // This is important: make the events unique. We need to do this because the ECStoreObjectTable
  1506. // row engine does not support requesting the exact same row twice within the same call. If we have
  1507. // duplicates here, this will filter through to the row engine and cause all kinds of nastiness, mainly
  1508. // causing the item to be deleted from search folders irrespective of whether it should have been deleted
  1509. // or added.
  1510. lstObjectIDs.sort();
  1511. lstObjectIDs.unique();
  1512. ProcessMessageChange(ulStoreId, ulFolderId, &lstObjectIDs, ulType);
  1513. }
  1514. return er;
  1515. }
  1516. /**
  1517. * Get object statistics
  1518. *
  1519. * @param[out] sStats Reference to searchfolder statistics
  1520. *
  1521. * @return This functions return always success
  1522. */
  1523. ECRESULT ECSearchFolders::GetStats(sSearchFolderStats &sStats)
  1524. {
  1525. memset(&sStats, 0, sizeof(sSearchFolderStats));
  1526. ulock_rec l_sf(m_mutexMapSearchFolders);
  1527. sStats.ulStores = m_mapSearchFolders.size();
  1528. sStats.ullSize = sStats.ulStores * sizeof(STOREFOLDERIDSEARCH::value_type);
  1529. for (const auto &storefolder : m_mapSearchFolders) {
  1530. sStats.ulFolders += storefolder.second.size();
  1531. sStats.ullSize += storefolder.second.size() * (sizeof(FOLDERIDSEARCH::value_type) + sizeof(SEARCHFOLDER));
  1532. for (const auto &fs : storefolder.second)
  1533. sStats.ullSize += SearchCriteriaSize(fs.second->lpSearchCriteria);
  1534. }
  1535. l_sf.unlock();
  1536. ulock_rec l_ev(m_mutexEvents);
  1537. sStats.ulEvents = m_lstEvents.size();
  1538. l_ev.unlock();
  1539. sStats.ullSize += sStats.ulEvents * sizeof(EVENT);
  1540. return erSuccess;
  1541. }
  1542. } /* namespace */