cmdutil.cpp 85 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301
  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 <exception>
  19. #include <set>
  20. #include <stdexcept>
  21. #include <string>
  22. #include <list>
  23. #include <map>
  24. #include <utility>
  25. #include <mapidefs.h>
  26. #include <mapitags.h>
  27. #include <kopano/mapiext.h>
  28. #include <kopano/memory.hpp>
  29. #include <kopano/EMSAbTag.h>
  30. #include <edkmdb.h>
  31. #include "ECMAPI.h"
  32. #include "soapH.h"
  33. #include "ECSessionManager.h"
  34. #include "ECSecurity.h"
  35. #include "ics.h"
  36. #include "ECICS.h"
  37. #include "StorageUtil.h"
  38. #include "ECAttachmentStorage.h"
  39. #include "ECStatsCollector.h"
  40. #include "ECStringCompat.h"
  41. #include "ECTPropsPurge.h"
  42. #include "cmdutil.hpp"
  43. #define FIELD_NR_NAMEID (FIELD_NR_MAX + 1)
  44. #define FIELD_NR_NAMESTR (FIELD_NR_MAX + 2)
  45. #define FIELD_NR_NAMEGUID (FIELD_NR_MAX + 3)
  46. using namespace KCHL;
  47. namespace KC {
  48. extern ECSessionManager* g_lpSessionManager; // FIXME: remove this global and change the depended source code!
  49. extern ECStatsCollector* g_lpStatsCollector;
  50. ECRESULT GetSourceKey(unsigned int ulObjId, SOURCEKEY *lpSourceKey)
  51. {
  52. ECRESULT er = erSuccess;
  53. unsigned char *lpData = NULL;
  54. unsigned int cbData = 0;
  55. er = g_lpSessionManager->GetCacheManager()->GetPropFromObject(PROP_ID(PR_SOURCE_KEY), ulObjId, NULL, &cbData, &lpData);
  56. if (er == erSuccess)
  57. *lpSourceKey = SOURCEKEY(cbData, reinterpret_cast<const char *>(lpData));
  58. s_free(nullptr, lpData);
  59. return er;
  60. }
  61. /*
  62. * This is a generic delete function that is called from
  63. *
  64. * ns__deleteObjects
  65. * ns__emptyFolder
  66. * ns__deleteFolder
  67. * purgeSoftDelete
  68. *
  69. * Functions which using sub set of the delete system are:
  70. * ns__saveObject
  71. * importMessageFromStream
  72. *
  73. * It does a recursive delete of objects in the hierarchytable, according to the flags given
  74. * which is any combination of
  75. *
  76. * EC_DELETE_FOLDERS - Delete subfolders
  77. * EC_DELETE_MESSAGES - Delete messages
  78. * EC_DELETE_RECIPIENTS - Delete recipients of messages
  79. * EC_DELETE_ATTACHMENTS - Delete attachments of messages
  80. * EC_DELETE_CONTAINER - Delete the container specified in the first place (otherwise only subobjects)
  81. *
  82. * This is done by first recusively retrieving the object IDs, then checking the types of objects in that
  83. * list. If there is any subobject that has *not* been specified for deletion, the function fails. Else,
  84. * all properties in the subobjects and the subobjects themselves are deleted. If EC_DELETE_CONTAINER
  85. * is specified, then the objects passed in lpEntryList are also deleted (together with their properties).
  86. *
  87. */
  88. /**
  89. * Validate permissions and match object type
  90. *
  91. * @param[in] lpSession Reference to a session object; cannot be NULL.
  92. * @param[in] bCheckPermission Check if the object folder or message has delete permissions.
  93. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  94. * @param[in] sItem Reference to a DELETEITEM structure that contains object information which identifying the folder, message, reciptient and attachment.
  95. *
  96. * @return Kopano error code
  97. */
  98. ECRESULT ValidateDeleteObject(ECSession *lpSession, bool bCheckPermission, unsigned int ulFlags, const DELETEITEM &sItem)
  99. {
  100. ECRESULT er;
  101. if (lpSession == NULL)
  102. return KCERR_INVALID_PARAMETER;
  103. // Check permission for each folder and messages
  104. if (bCheckPermission && ((sItem.ulObjType == MAPI_MESSAGE && sItem.ulParentType == MAPI_FOLDER) || sItem.ulObjType == MAPI_FOLDER)) {
  105. er = lpSession->GetSecurity()->CheckPermission(sItem.ulId, ecSecurityDelete);
  106. if(er != erSuccess)
  107. return er;
  108. }
  109. if (sItem.fRoot == true)
  110. return erSuccess; // Not for a root
  111. switch(RealObjType(sItem.ulObjType, sItem.ulParentType)) {
  112. case MAPI_MESSAGE:
  113. if (!(ulFlags & EC_DELETE_MESSAGES))
  114. return KCERR_HAS_MESSAGES;
  115. break;
  116. case MAPI_FOLDER:
  117. if (!(ulFlags & EC_DELETE_FOLDERS))
  118. return KCERR_HAS_FOLDERS;
  119. break;
  120. case MAPI_MAILUSER:
  121. case MAPI_DISTLIST:
  122. if (!(ulFlags & EC_DELETE_RECIPIENTS))
  123. return KCERR_HAS_RECIPIENTS;
  124. break;
  125. case MAPI_ATTACH:
  126. if (!(ulFlags & EC_DELETE_ATTACHMENTS))
  127. return KCERR_HAS_ATTACHMENTS;
  128. break;
  129. case MAPI_STORE: // only admins can delete a store, rights checked in ns__removeStore
  130. if (!(ulFlags & EC_DELETE_STORE))
  131. return KCERR_NOT_FOUND;
  132. break;
  133. default:
  134. // Unknown object type? We'll delete it anyway so we don't get frustrating non-deletable items
  135. assert(false); // Only frustrating developers!
  136. break;
  137. }
  138. return erSuccess;
  139. }
  140. /**
  141. * Expand a list of objects, validate permissions and object types
  142. * This function returns a list of all items that need to be
  143. * deleted. This may or may not include the given list in
  144. * lpsObjectList, because of EC_DELETE_CONTAINER.
  145. *
  146. * If the ulFLags includes EC_DELETE_NOT_ASSOCIATED_MSG only the associated messages for the container folder is
  147. * not deleted. If ulFlags EC_DELETE_CONTAINER included, the EC_DELETE_NOT_ASSOCIATED_MSG flag will be ignored.
  148. *
  149. * @param[in] lpSession Reference to a session object; cannot be NULL.
  150. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  151. * @param[in] lpsObjectList Reference to a list of objects that contains itemid and must be expanded.
  152. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  153. * @param[in] bCheckPermission Check the objects delete permissions.
  154. * @param[out] lplstDeleteItems Recursive list with objects
  155. *
  156. * @return Kopano error code
  157. */
  158. ECRESULT ExpandDeletedItems(ECSession *lpSession, ECDatabase *lpDatabase, ECListInt *lpsObjectList, unsigned int ulFlags, bool bCheckPermission, ECListDeleteItems *lplstDeleteItems)
  159. {
  160. ECRESULT er = erSuccess;
  161. ECListIntIterator iListObjectId;
  162. DB_RESULT lpDBResult;
  163. DB_ROW lpDBRow = NULL;
  164. std::string strQuery;
  165. std::set<unsigned int> setIDs;
  166. ECListDeleteItems lstDeleteItems;
  167. ECListDeleteItems lstContainerItems;
  168. DELETEITEM sItem;
  169. ECSessionManager *lpSessionManager = NULL;
  170. ECCacheManager *lpCacheManager = NULL;
  171. unsigned int ulParent = 0;
  172. if (lpSession == NULL || lpDatabase == NULL || lpsObjectList == NULL || lplstDeleteItems == NULL) {
  173. er = KCERR_INVALID_PARAMETER;
  174. goto exit;
  175. }
  176. lpSessionManager = lpSession->GetSessionManager();
  177. lpCacheManager = lpSessionManager->GetCacheManager();
  178. // First, put all the root objects in the list
  179. for (iListObjectId = lpsObjectList->begin();
  180. iListObjectId != lpsObjectList->end(); ++iListObjectId)
  181. {
  182. sItem.fRoot = true;
  183. // Lock the root records's parent counter to maintain locking order (counters/content/storesize/committimemax)
  184. er = lpCacheManager->GetObject(*iListObjectId, &ulParent, NULL, NULL, NULL);
  185. if(er != erSuccess)
  186. goto exit;
  187. er = lpDatabase->DoSelect("SELECT properties.val_ulong FROM properties WHERE hierarchyid = " + stringify(ulParent) + " FOR UPDATE", NULL);
  188. if(er != erSuccess)
  189. goto exit;
  190. // Lock the root records to make sure that we don't interfere with modifies or deletes on the same record
  191. er = lpDatabase->DoSelect("SELECT hierarchy.flags FROM hierarchy WHERE id = " + stringify(*iListObjectId) + " FOR UPDATE", NULL);
  192. if(er != erSuccess)
  193. goto exit;
  194. strQuery = "SELECT h.id, h.parent, h.type, h.flags, p.type, properties.val_ulong, (SELECT hierarchy_id FROM outgoingqueue WHERE outgoingqueue.hierarchy_id = h.id LIMIT 1) FROM hierarchy as h LEFT JOIN properties ON properties.hierarchyid=h.id AND properties.tag = " + stringify(PROP_ID(PR_MESSAGE_FLAGS)) + " AND properties.type = " + stringify(PROP_TYPE(PR_MESSAGE_FLAGS)) + " LEFT JOIN hierarchy AS p ON h.parent=p.id WHERE ";
  195. if((ulFlags & EC_DELETE_CONTAINER) == 0)
  196. strQuery += "h.parent=" + stringify(*iListObjectId);
  197. else
  198. strQuery += "h.id=" + stringify(*iListObjectId);
  199. if((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
  200. strQuery += " AND (h.flags&"+stringify(MSGFLAG_DELETED)+") !="+stringify(MSGFLAG_DELETED);
  201. if ((ulFlags & (EC_DELETE_CONTAINER | EC_DELETE_NOT_ASSOCIATED_MSG)) == EC_DELETE_NOT_ASSOCIATED_MSG)
  202. strQuery += " AND (h.flags&"+stringify(MSGFLAG_ASSOCIATED)+") !="+stringify(MSGFLAG_ASSOCIATED);
  203. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  204. if(er != erSuccess)
  205. goto exit;
  206. while ( (lpDBRow = lpDatabase->FetchRow(lpDBResult)) )
  207. {
  208. // No type or flags exist
  209. if(lpDBRow[2] == NULL || lpDBRow[3] == NULL) {
  210. //er = KCERR_DATABASE_ERROR;
  211. //goto exit;
  212. continue;
  213. }
  214. // When you delete a store the parent id is NULL, object type must be MAPI_STORE
  215. if(lpDBRow[1] == NULL && atoi(lpDBRow[2]) != MAPI_STORE) {
  216. //er = KCERR_DATABASE_ERROR;
  217. //goto exit;
  218. continue;
  219. }
  220. // Loop protection, don't insert duplicates.
  221. if (setIDs.insert(atoui(lpDBRow[0])).second == false)
  222. continue;
  223. sItem.ulId = atoui(lpDBRow[0]);
  224. sItem.ulParent = (lpDBRow[1])?atoui(lpDBRow[1]) : 0;
  225. sItem.ulObjType = atoi(lpDBRow[2]);
  226. sItem.ulFlags = atoui(lpDBRow[3]);
  227. sItem.ulObjSize = 0;
  228. sItem.ulStoreId = 0;
  229. sItem.ulParentType = (lpDBRow[4])?atoui(lpDBRow[4]) : 0;
  230. sItem.sEntryId.__size = 0;
  231. sItem.sEntryId.__ptr = NULL;
  232. sItem.ulMessageFlags = lpDBRow[5] ? atoui(lpDBRow[5]) : 0;
  233. sItem.fInOGQueue = lpDBRow[6] ? true : false;
  234. // Validate deleted object, if not valid, break directly
  235. er = ValidateDeleteObject(lpSession, bCheckPermission, ulFlags, sItem);
  236. if (er != erSuccess)
  237. goto exit;
  238. // Get extended data
  239. if(sItem.ulObjType == MAPI_STORE || sItem.ulObjType == MAPI_FOLDER || sItem.ulObjType == MAPI_MESSAGE) {
  240. lpCacheManager->GetStore(sItem.ulId, &sItem.ulStoreId , NULL); //CHECKme:"oude gaf geen errors
  241. if (!(sItem.ulFlags & MSGFLAG_DELETED))
  242. GetObjectSize(lpDatabase, sItem.ulId, &sItem.ulObjSize);
  243. lpCacheManager->GetEntryIdFromObject(sItem.ulId, NULL, 0, &sItem.sEntryId);//CHECKme:"oude gaf geen errors
  244. GetSourceKey(sItem.ulId, &sItem.sSourceKey);
  245. GetSourceKey(sItem.ulParent, &sItem.sParentSourceKey);
  246. }
  247. lstDeleteItems.push_back(sItem);
  248. }
  249. }
  250. // Now, run through the list, adding children to the bottom of the list. This means
  251. // we're actually running width-first, and don't have to do anything recursive.
  252. for (const auto &di : lstDeleteItems) {
  253. strQuery = "SELECT id, type, flags, (SELECT hierarchy_id FROM outgoingqueue WHERE outgoingqueue.hierarchy_id = hierarchy.id LIMIT 1) FROM hierarchy WHERE parent=" +
  254. stringify(di.ulId);
  255. if((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
  256. strQuery += " AND (flags&"+stringify(MSGFLAG_DELETED)+") !="+stringify(MSGFLAG_DELETED);
  257. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  258. if(er != erSuccess)
  259. goto exit;
  260. while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL )
  261. {
  262. // No id, type or flags exist
  263. if(lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL)
  264. continue;
  265. // Loop protection, don't insert duplicates.
  266. if (setIDs.insert(atoui(lpDBRow[0])).second == false)
  267. continue;
  268. // Add this object as a node to the end of the list
  269. sItem.fRoot = false;
  270. sItem.ulObjSize = 0;
  271. sItem.ulStoreId = 0;
  272. sItem.sEntryId.__size = 0;
  273. sItem.sEntryId.__ptr = NULL;
  274. sItem.ulId = atoui(lpDBRow[0]);
  275. sItem.ulParent = di.ulId;
  276. sItem.ulParentType = di.ulObjType;
  277. sItem.ulObjType = atoi(lpDBRow[1]);
  278. // Add the parent delete flag, because only the top-level object is marked for deletion
  279. sItem.ulFlags = atoui(lpDBRow[2]) | (di.ulFlags & MSGFLAG_DELETED);
  280. sItem.fInOGQueue = lpDBRow[3] ? true : false;
  281. // Validate deleted object, if no valid, break directly
  282. er = ValidateDeleteObject(lpSession, bCheckPermission, ulFlags, sItem);
  283. if (er != erSuccess)
  284. goto exit;
  285. if(sItem.ulObjType == MAPI_STORE || sItem.ulObjType == MAPI_FOLDER || (sItem.ulObjType == MAPI_MESSAGE && sItem.ulParentType == MAPI_FOLDER) ) {
  286. lpCacheManager->GetStore(sItem.ulId, &sItem.ulStoreId , NULL); //CHECKme:"oude gaf geen errors
  287. if (!(sItem.ulFlags & MSGFLAG_DELETED))
  288. GetObjectSize(lpDatabase, sItem.ulId, &sItem.ulObjSize);
  289. lpCacheManager->GetEntryIdFromObject(sItem.ulId, NULL, 0, &sItem.sEntryId);//CHECKme:"oude gaf geen errors
  290. GetSourceKey(sItem.ulId, &sItem.sSourceKey);
  291. GetSourceKey(sItem.ulParent, &sItem.sParentSourceKey);
  292. }
  293. lstDeleteItems.push_back(sItem);
  294. }
  295. }
  296. // Move list
  297. std::swap(lstDeleteItems, *lplstDeleteItems);
  298. exit:
  299. FreeDeletedItems(&lstDeleteItems);
  300. return er;
  301. }
  302. /*
  303. * Add changes into the ICS system.
  304. *
  305. * This adds a change for each removed folder and message. The change could be a soft- or hard delete.
  306. * It is possible to gives a list with different deleted objects, all not supported object types will be skipped.
  307. *
  308. * @param[in] lpSession Reference to a session object; cannot be NULL.
  309. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  310. * @param[in] lstDeleted List with deleted objects
  311. * @param[in] ulSyncId ???
  312. *
  313. */
  314. ECRESULT DeleteObjectUpdateICS(ECSession *lpSession, unsigned int ulFlags, ECListDeleteItems &lstDeleted, unsigned int ulSyncId)
  315. {
  316. ECRESULT er = erSuccess;
  317. for (const auto &di : lstDeleted)
  318. // ICS update
  319. if (di.ulObjType == MAPI_MESSAGE &&
  320. di.ulParentType == MAPI_FOLDER)
  321. AddChange(lpSession, ulSyncId, di.sSourceKey, di.sParentSourceKey, ulFlags & EC_DELETE_HARD_DELETE ? ICS_MESSAGE_HARD_DELETE : ICS_MESSAGE_SOFT_DELETE);
  322. else if (di.ulObjType == MAPI_FOLDER &&
  323. !(di.ulFlags & FOLDER_SEARCH))
  324. AddChange(lpSession, ulSyncId, di.sSourceKey, di.sParentSourceKey, ulFlags & EC_DELETE_HARD_DELETE ? ICS_FOLDER_HARD_DELETE : ICS_FOLDER_SOFT_DELETE);
  325. return er;
  326. }
  327. /**
  328. * Check if the delete of the object should actually occur in the sync
  329. * scope. Checks the syncedmessages table.
  330. *
  331. * @param lpDatabase Database object
  332. * @param lstDeleted Expanded list of all objects to check
  333. * @param ulSyncId syncid to check with
  334. *
  335. * @return Kopano error code
  336. */
  337. static ECRESULT CheckICSDeleteScope(ECDatabase *lpDatabase,
  338. ECListDeleteItems &lstDeleted, unsigned int ulSyncId)
  339. {
  340. ECRESULT er;
  341. for (auto iterDeleteItems = lstDeleted.begin();
  342. iterDeleteItems != lstDeleted.end(); ) {
  343. er = CheckWithinLastSyncedMessagesSet(lpDatabase, ulSyncId, iterDeleteItems->sSourceKey);
  344. if (er == KCERR_NOT_FOUND) {
  345. // ignore delete of message
  346. ec_log_debug("Message not in sync scope, ignoring delete");
  347. FreeDeleteItem(&(*iterDeleteItems));
  348. lstDeleted.erase(iterDeleteItems++);
  349. } else if (er != erSuccess)
  350. return er;
  351. else
  352. ++iterDeleteItems;
  353. }
  354. return erSuccess;
  355. }
  356. /*
  357. * Calculate and update the store size for deleted items
  358. *
  359. * The DeleteObjectStoreSize methode calculate and update the store size. Only top-level messages will
  360. * be calculate, all other objects are not supported and will be skipped. If a message has the
  361. * MSGFLAG_DELETED flag, the size will ignored because it is already subtract from the store size.
  362. * The deleted object list may include more than one store.
  363. *
  364. * @param[in] lpSession Reference to a session object; cannot be NULL.
  365. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  366. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  367. * @param[in] lstDeleted List with deleted objects
  368. */
  369. ECRESULT DeleteObjectStoreSize(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulFlags, ECListDeleteItems &lstDeleted)
  370. {
  371. ECRESULT er = erSuccess;
  372. std::map<unsigned int, long long> mapStoreSize;
  373. //TODO: check or foldersize also is used
  374. for (const auto &di : lstDeleted) {
  375. // Get size of all the messages
  376. bool k = di.ulObjType == MAPI_MESSAGE &&
  377. di.ulParentType == MAPI_FOLDER &&
  378. (di.ulFlags & MSGFLAG_DELETED) != MSGFLAG_DELETED;
  379. if (!k)
  380. continue;
  381. assert(di.ulStoreId != 0);
  382. if (mapStoreSize.find(di.ulStoreId) != mapStoreSize.end() )
  383. mapStoreSize[di.ulStoreId] += di.ulObjSize;
  384. else
  385. mapStoreSize[di.ulStoreId] = di.ulObjSize;
  386. }
  387. // Update store size for each store
  388. for (auto i = mapStoreSize.cbegin();
  389. i != mapStoreSize.cend() && er == erSuccess; ++i)
  390. if (i->second > 0)
  391. er = UpdateObjectSize(lpDatabase, i->first, MAPI_STORE, UPDATE_SUB, i->second);
  392. return er;
  393. }
  394. /*
  395. * Soft delete objects, mark the root objects as deleted
  396. *
  397. * Mark the root objects as deleted, add deleted on date on the root objects.
  398. * Since this is a fairly simple operation, we are doing soft deletes in a single transaction. Once the SQL has gone OK,
  399. * we know that all items were successfully mark as deleted and we can therefore add all soft-deleted items into
  400. * the 'lstDeleted' list at once
  401. *
  402. * @param[in] lpSession Reference to a session object; cannot be NULL.
  403. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  404. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  405. * @param[in] lstDeleteItems List with objecta which must be deleted.
  406. * @param[out] lstDeleted List with deleted objects.
  407. *
  408. */
  409. ECRESULT DeleteObjectSoft(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulFlags, ECListDeleteItems &lstDeleteItems, ECListDeleteItems &lstDeleted)
  410. {
  411. ECRESULT er;
  412. FILETIME ft;
  413. std::string strInclauseOQQ;
  414. std::string strInclause;
  415. std::string strQuery;
  416. PARENTINFO pi;
  417. std::map<unsigned int, PARENTINFO> mapFolderCounts;
  418. // Build where condition
  419. for (const auto &di : lstDeleteItems) {
  420. bool k = (di.ulObjType == MAPI_MESSAGE &&
  421. di.ulParentType == MAPI_FOLDER) ||
  422. di.ulObjType == MAPI_FOLDER ||
  423. di.ulObjType == MAPI_STORE;
  424. if (!k)
  425. continue;
  426. if (di.fInOGQueue) {
  427. if(!strInclauseOQQ.empty())
  428. strInclauseOQQ += ",";
  429. strInclauseOQQ += stringify(di.ulId);
  430. }
  431. if (!di.fRoot)
  432. continue;
  433. if (!strInclause.empty())
  434. strInclause += ",";
  435. strInclause += stringify(di.ulId);
  436. // Track counter changes
  437. if (di.ulParentType != MAPI_FOLDER)
  438. continue;
  439. // Ignore already-softdeleted items
  440. if ((di.ulFlags & MSGFLAG_DELETED) != 0)
  441. continue;
  442. if (di.ulObjType == MAPI_MESSAGE) {
  443. if (di.ulFlags & MAPI_ASSOCIATED) {
  444. --mapFolderCounts[di.ulParent].lAssoc;
  445. ++mapFolderCounts[di.ulParent].lDeletedAssoc;
  446. } else {
  447. --mapFolderCounts[di.ulParent].lItems;
  448. ++mapFolderCounts[di.ulParent].lDeleted;
  449. if ((di.ulMessageFlags & MSGFLAG_READ) == 0)
  450. --mapFolderCounts[di.ulParent].lUnread;
  451. }
  452. }
  453. if (di.ulObjType == MAPI_FOLDER) {
  454. --mapFolderCounts[di.ulParent].lFolders;
  455. ++mapFolderCounts[di.ulParent].lDeletedFolders;
  456. }
  457. }
  458. // Mark all items as deleted, if a item in the outgoingqueue and remove the submit flag
  459. if (!strInclauseOQQ.empty())
  460. {
  461. // Remove any entries in the outgoing queue for deleted items
  462. strQuery = "DELETE FROM outgoingqueue WHERE hierarchy_id IN ( " + strInclauseOQQ + ")";
  463. er = lpDatabase->DoDelete(strQuery);
  464. if(er!= erSuccess)
  465. return er;
  466. // Remove the submit flag
  467. strQuery = "UPDATE properties SET val_ulong=val_ulong&~" + stringify(MSGFLAG_SUBMIT)+" WHERE hierarchyid IN(" + strInclauseOQQ + ") AND tag = " + stringify(PROP_ID(PR_MESSAGE_FLAGS)) + " and type = " + stringify(PROP_TYPE(PR_MESSAGE_FLAGS));
  468. er = lpDatabase->DoUpdate(strQuery);
  469. if(er!= erSuccess)
  470. return er;
  471. }
  472. if(!strInclause.empty())
  473. {
  474. // Mark item as deleted
  475. strQuery = "UPDATE hierarchy SET flags=flags|"+stringify(MSGFLAG_DELETED)+" WHERE id IN(" + strInclause + ")";
  476. er = lpDatabase->DoUpdate(strQuery);
  477. if(er!= erSuccess)
  478. return er;
  479. // Remove the MSGSTATUS_DELMARKED flag (IMAP gateway set)
  480. strQuery = "UPDATE properties SET val_ulong=val_ulong&~" + stringify(MSGSTATUS_DELMARKED) +
  481. " WHERE hierarchyid IN(" + strInclause + ") AND tag = " + stringify(PROP_ID(PR_MSG_STATUS)) + " and type = " + stringify(PROP_TYPE(PR_MSG_STATUS));
  482. er = lpDatabase->DoUpdate(strQuery);
  483. if(er!= erSuccess)
  484. return er;
  485. }
  486. er = ApplyFolderCounts(lpDatabase, mapFolderCounts);
  487. if(er != erSuccess)
  488. return er;
  489. // Add properties: PR_DELETED_ON
  490. GetSystemTimeAsFileTime(&ft);
  491. for (const auto &di : lstDeleteItems) {
  492. bool k = di.fRoot == true &&
  493. ((di.ulObjType == MAPI_MESSAGE &&
  494. di.ulParentType == MAPI_FOLDER) ||
  495. di.ulObjType == MAPI_FOLDER ||
  496. di.ulObjType == MAPI_STORE);
  497. if (!k)
  498. continue;
  499. strQuery = "INSERT INTO properties(hierarchyid, tag, type, val_lo, val_hi) VALUES(" +
  500. stringify(di.ulId) + "," +
  501. stringify(PROP_ID(PR_DELETED_ON)) + "," +
  502. stringify(PROP_TYPE(PR_DELETED_ON)) + "," +
  503. stringify(ft.dwLowDateTime) + "," +
  504. stringify(ft.dwHighDateTime) +
  505. ") ON DUPLICATE KEY UPDATE val_lo=" +
  506. stringify(ft.dwLowDateTime) + ",val_hi=" +
  507. stringify(ft.dwHighDateTime);
  508. er = lpDatabase->DoUpdate(strQuery);
  509. if (er!= erSuccess)
  510. return er;
  511. er = ECTPropsPurge::AddDeferredUpdateNoPurge(lpDatabase,
  512. di.ulParent, 0, di.ulId);
  513. if (er != erSuccess)
  514. return er;
  515. }
  516. lstDeleted = lstDeleteItems;
  517. return erSuccess;
  518. }
  519. /**
  520. * Hard delete objects, remove the data from storage
  521. *
  522. * This means we should be really deleting the actual data from the database and storage. This will be done in
  523. * bachtches of 32 items each because deleteing records is generally fairly slow. Also, very large delete batches
  524. * can taking up to more than an hour to process. We don't want to have a transaction lasting an hour because it
  525. * would cause lots of locking problems. Also, each item successfully deleted and committed to the database will
  526. * added into a list. So, If something fails we notify the items in the 'deleted items list' only.
  527. *
  528. * @param[in] lpSession Reference to a session object; cannot be NULL.
  529. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  530. * @param[in] lpAttachmentStorage Reference to an Attachment object. could not NULL if bNoTransaction is true.
  531. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  532. * @param[in] lstDeleteItems List with objects which must be deleted.
  533. * @param[in] bNoTransaction Disable the database transactions.
  534. * @param[in] lstDeleted List with deleted objects.
  535. *
  536. * @return Kopano error code
  537. */
  538. ECRESULT DeleteObjectHard(ECSession *lpSession, ECDatabase *lpDatabase, ECAttachmentStorage *lpAttachmentStorage, unsigned int ulFlags, ECListDeleteItems &lstDeleteItems, bool bNoTransaction, ECListDeleteItems &lstDeleted)
  539. {
  540. ECRESULT er = erSuccess;
  541. object_ptr<ECAttachmentStorage> lpInternalAttachmentStorage;
  542. std::list<ULONG> lstDeleteAttachments;
  543. std::string strInclause;
  544. std::string strOGQInclause;
  545. std::string strQuery;
  546. ECListDeleteItems lstToBeDeleted;
  547. PARENTINFO pi;
  548. std::map<unsigned int, PARENTINFO> mapFolderCounts;
  549. int i;
  550. if(!(ulFlags & EC_DELETE_HARD_DELETE)) {
  551. er = KCERR_INVALID_PARAMETER;
  552. goto exit;
  553. }
  554. if (bNoTransaction && lpAttachmentStorage == NULL) {
  555. assert(false);
  556. er = KCERR_INVALID_PARAMETER;
  557. goto exit;
  558. }
  559. if (!lpAttachmentStorage) {
  560. er = CreateAttachmentStorage(lpDatabase, &~lpInternalAttachmentStorage);
  561. if (er != erSuccess)
  562. goto exit;
  563. lpAttachmentStorage = lpInternalAttachmentStorage;
  564. }
  565. for (auto iterDeleteItems = lstDeleteItems.crbegin();
  566. iterDeleteItems != lstDeleteItems.crend(); ) {
  567. strInclause.clear();
  568. strOGQInclause.clear();
  569. lstDeleteAttachments.clear();
  570. lstToBeDeleted.clear();
  571. i = 0;
  572. // Delete max 32 items per query
  573. while (i < 32 && iterDeleteItems != lstDeleteItems.crend()) {
  574. if(!strInclause.empty())
  575. strInclause += ",";
  576. strInclause += stringify(iterDeleteItems->ulId);
  577. if(iterDeleteItems->fInOGQueue) {
  578. if(!strOGQInclause.empty())
  579. strOGQInclause += ",";
  580. strOGQInclause += stringify(iterDeleteItems->ulId);
  581. }
  582. // make new list for attachment deletes. messages can have imap "attachment".
  583. if (iterDeleteItems->ulObjType == MAPI_ATTACH || (iterDeleteItems->ulObjType == MAPI_MESSAGE && iterDeleteItems->ulParentType == MAPI_FOLDER))
  584. lstDeleteAttachments.push_back(iterDeleteItems->ulId);
  585. lstToBeDeleted.push_front(*iterDeleteItems);
  586. if(!(ulFlags&EC_DELETE_STORE) && iterDeleteItems->ulParentType == MAPI_FOLDER && iterDeleteItems->fRoot) {
  587. // Track counter changes
  588. memset(&pi, 0, sizeof(pi));
  589. pi.ulStoreId = iterDeleteItems->ulStoreId;
  590. mapFolderCounts.insert(std::make_pair(iterDeleteItems->ulParent, pi));
  591. if(iterDeleteItems->ulObjType == MAPI_MESSAGE) {
  592. if(iterDeleteItems->ulFlags == MAPI_ASSOCIATED) {
  593. // Delete associated
  594. --mapFolderCounts[iterDeleteItems->ulParent].lAssoc;
  595. } else if(iterDeleteItems->ulFlags == 0) {
  596. // Deleting directly from normal item, count normal and unread items
  597. --mapFolderCounts[iterDeleteItems->ulParent].lItems;
  598. if((iterDeleteItems->ulMessageFlags & MSGFLAG_READ) == 0)
  599. --mapFolderCounts[iterDeleteItems->ulParent].lUnread;
  600. } else if(iterDeleteItems->ulFlags == (MAPI_ASSOCIATED | MSGFLAG_DELETED)) {
  601. // Deleting softdeleted associated item
  602. --mapFolderCounts[iterDeleteItems->ulParent].lDeletedAssoc;
  603. } else {
  604. // Deleting normal softdeleted item
  605. --mapFolderCounts[iterDeleteItems->ulParent].lDeleted;
  606. }
  607. }
  608. if(iterDeleteItems->ulObjType == MAPI_FOLDER) {
  609. if ((iterDeleteItems->ulFlags & MSGFLAG_DELETED) == 0)
  610. --mapFolderCounts[iterDeleteItems->ulParent].lFolders;
  611. else
  612. --mapFolderCounts[iterDeleteItems->ulParent].lDeletedFolders;
  613. }
  614. }
  615. ++i;
  616. ++iterDeleteItems;
  617. }
  618. // Start transaction
  619. if (!bNoTransaction) {
  620. er = lpAttachmentStorage->Begin();
  621. if (er != erSuccess)
  622. goto exit;
  623. er = lpDatabase->Begin();
  624. if (er != erSuccess)
  625. goto exit;
  626. }
  627. if(!strInclause.empty()) {
  628. // First, Remove any entries in the outgoing queue for deleted items
  629. if(!strOGQInclause.empty()) {
  630. strQuery = "DELETE FROM outgoingqueue WHERE hierarchy_id IN ( " + strOGQInclause + ")";
  631. er = lpDatabase->DoDelete(strQuery);
  632. if(er!= erSuccess)
  633. goto exit;
  634. }
  635. // Then, the hierarchy entries of all the objects
  636. strQuery = "DELETE FROM hierarchy WHERE id IN (" + strInclause + ")";
  637. er = lpDatabase->DoDelete(strQuery);
  638. if(er!= erSuccess)
  639. goto exit;
  640. // Then, the table properties for the objects we just deleted
  641. strQuery = "DELETE FROM tproperties WHERE hierarchyid IN (" + strInclause + ")";
  642. er = lpDatabase->DoDelete(strQuery);
  643. if(er!= erSuccess)
  644. goto exit;
  645. // Then, the properties for the objects we just deleted
  646. strQuery = "DELETE FROM properties WHERE hierarchyid IN (" + strInclause + ")";
  647. er = lpDatabase->DoDelete(strQuery);
  648. if(er!= erSuccess)
  649. goto exit;
  650. // Then, the MVproperties for the objects we just deleted
  651. strQuery = "DELETE FROM mvproperties WHERE hierarchyid IN (" + strInclause + ")";
  652. er = lpDatabase->DoDelete(strQuery);
  653. if(er!= erSuccess)
  654. goto exit;
  655. // Then, the acls for the objects we just deleted (if exist)
  656. strQuery = "DELETE FROM acl WHERE hierarchy_id IN (" + strInclause + ")";
  657. er = lpDatabase->DoDelete(strQuery);
  658. if(er != erSuccess)
  659. goto exit;
  660. // remove indexedproperties
  661. strQuery = "DELETE FROM indexedproperties WHERE hierarchyid IN (" + strInclause + ")";
  662. er = lpDatabase->DoDelete(strQuery);
  663. if(er != erSuccess)
  664. goto exit;
  665. // remove deferred table updates
  666. strQuery = "DELETE FROM deferredupdate WHERE hierarchyid IN (" + strInclause + ")";
  667. er = lpDatabase->DoDelete(strQuery);
  668. if(er != erSuccess)
  669. goto exit;
  670. }
  671. // list may contain non-attachment object IDs!
  672. if (!lstDeleteAttachments.empty()) {
  673. er = lpAttachmentStorage->DeleteAttachments(lstDeleteAttachments);
  674. if (er != erSuccess)
  675. goto exit;
  676. }
  677. er = ApplyFolderCounts(lpDatabase, mapFolderCounts);
  678. if(er != erSuccess)
  679. goto exit;
  680. // Clear map for next round
  681. mapFolderCounts.clear();
  682. // Commit the transaction
  683. if (!bNoTransaction) {
  684. er = lpAttachmentStorage->Commit();
  685. if (er != erSuccess)
  686. goto exit;
  687. er = lpDatabase->Commit();
  688. if(er != erSuccess)
  689. goto exit;
  690. }
  691. // Deletes have been committed, add the deleted items to the list of items we have deleted
  692. lstDeleted.splice(lstDeleted.begin(),lstToBeDeleted);
  693. } // while iterDeleteItems != end()
  694. exit:
  695. if (er != erSuccess && !bNoTransaction) {
  696. if(lpInternalAttachmentStorage)
  697. lpInternalAttachmentStorage->Rollback();
  698. lpDatabase->Rollback();
  699. }
  700. return er;
  701. }
  702. /*
  703. * Deleted object cache updates
  704. *
  705. * @param[in] lpSession Reference to a session object; cannot be NULL.
  706. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  707. * @param[in] lstDeleted List with deleted objects.
  708. */
  709. ECRESULT DeleteObjectCacheUpdate(ECSession *lpSession, unsigned int ulFlags, ECListDeleteItems &lstDeleted)
  710. {
  711. ECSessionManager *lpSessionManager = NULL;
  712. ECCacheManager *lpCacheManager = NULL;
  713. if (lpSession == NULL)
  714. return KCERR_INVALID_PARAMETER;
  715. lpSessionManager = lpSession->GetSessionManager();
  716. lpCacheManager = lpSessionManager->GetCacheManager();
  717. // Remove items from cache and update the outgoing queue
  718. for (const auto &di : lstDeleted) {
  719. // update the cache
  720. lpCacheManager->Update(fnevObjectDeleted, di.ulId);
  721. if (di.fRoot)
  722. lpCacheManager->Update(fnevObjectModified, di.ulParent);
  723. // Update cache, Remove index properties
  724. if (ulFlags & EC_DELETE_HARD_DELETE)
  725. lpCacheManager->RemoveIndexData(di.ulId);
  726. }
  727. return erSuccess;
  728. }
  729. /*
  730. * Deleted object notifications
  731. *
  732. * @param[in] lpSession Reference to a session object; cannot be NULL.
  733. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  734. * @param[in] lstDeleted List with deleted objects.
  735. */
  736. ECRESULT DeleteObjectNotifications(ECSession *lpSession, unsigned int ulFlags, ECListDeleteItems &lstDeleted)
  737. {
  738. ECSessionManager *lpSessionManager = NULL;
  739. std::list<unsigned int> lstParent;
  740. ECMapTableChangeNotifications mapTableChangeNotifications;
  741. //std::set<unsigned int> setFolderParents;
  742. size_t cDeleteditems = lstDeleted.size();
  743. unsigned int ulGrandParent = 0;
  744. if (lpSession == NULL)
  745. return KCERR_INVALID_PARAMETER;
  746. lpSessionManager = lpSession->GetSessionManager();
  747. // Now, send the notifications for MAPI_MESSAGE and MAPI_FOLDER types
  748. for (auto &di : lstDeleted) {
  749. // Update the outgoing queue
  750. // Remove the item from both the local and master outgoing queues
  751. if ((di.ulFlags & MSGFLAG_SUBMIT) &&
  752. di.ulParentType == MAPI_FOLDER &&
  753. di.ulObjType == MAPI_MESSAGE) {
  754. lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_DELETE, di.ulStoreId, di.ulId, EC_SUBMIT_LOCAL, MAPI_MESSAGE);
  755. lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_DELETE, di.ulStoreId, di.ulId, EC_SUBMIT_MASTER, MAPI_MESSAGE);
  756. }
  757. bool k = (di.ulParentType == MAPI_FOLDER &&
  758. di.ulObjType == MAPI_MESSAGE) ||
  759. di.ulObjType == MAPI_FOLDER;
  760. if (!k)
  761. continue;
  762. // Notify that the message has been deleted
  763. lpSessionManager->NotificationDeleted(di.ulObjType, di.ulId,
  764. di.ulStoreId, &di.sEntryId, di.ulParent,
  765. di.ulFlags & (MSGFLAG_ASSOCIATED | MSGFLAG_DELETED));
  766. // Update all tables viewing this message
  767. if (cDeleteditems < EC_TABLE_CHANGE_THRESHOLD) {
  768. lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_DELETE,
  769. di.ulFlags & (MSGFLAG_ASSOCIATED | MSGFLAG_DELETED),
  770. di.ulParent, di.ulId, di.ulObjType);
  771. if ((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
  772. // Update all tables viewing this message
  773. lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD,
  774. MSGFLAG_DELETED, di.ulParent, di.ulId, di.ulObjType);
  775. } else {
  776. // We need to send a table change notifications later on
  777. mapTableChangeNotifications[di.ulParent].insert(TABLECHANGENOTIFICATION(di.ulObjType, di.ulFlags & MSGFLAG_NOTIFY_FLAGS));
  778. if ((ulFlags & EC_DELETE_HARD_DELETE) != EC_DELETE_HARD_DELETE)
  779. mapTableChangeNotifications[di.ulParent].insert(TABLECHANGENOTIFICATION(di.ulObjType, (di.ulFlags & MSGFLAG_NOTIFY_FLAGS) | MSGFLAG_DELETED));
  780. }
  781. // @todo: Is this correct ???
  782. if (di.fRoot)
  783. lstParent.push_back(di.ulParent);
  784. }
  785. // We have a list of all the folders in which something was deleted, so get a unique list
  786. lstParent.sort();
  787. lstParent.unique();
  788. // Now, send each parent folder a notification that it has been altered and send
  789. // its parent a notification (ie the grandparent of the deleted object) that its
  790. // hierarchy table has been changed.
  791. for (auto pa_id : lstParent) {
  792. if(cDeleteditems >= EC_TABLE_CHANGE_THRESHOLD) {
  793. // Find the set of notifications to send for the current parent.
  794. auto pn = mapTableChangeNotifications.find(pa_id);
  795. if (pn != mapTableChangeNotifications.cend())
  796. // Iterate the set and send notifications.
  797. for (auto n = pn->second.cbegin();
  798. n != pn->second.cend(); ++n)
  799. lpSessionManager->UpdateTables(ECKeyTable::TABLE_CHANGE,
  800. n->ulFlags, pa_id, 0, n->ulType);
  801. }
  802. lpSessionManager->NotificationModified(MAPI_FOLDER, pa_id);
  803. if (lpSessionManager->GetCacheManager()->GetParent(pa_id, &ulGrandParent) == erSuccess)
  804. lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY,
  805. 0, ulGrandParent, pa_id, MAPI_FOLDER);
  806. }
  807. return erSuccess;
  808. }
  809. /**
  810. * Mark a store as deleted
  811. *
  812. * @param[in] lpSession Reference to a session object; cannot be NULL.
  813. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  814. * @param[in] ulStoreHierarchyId Store id to be delete
  815. * @param[in] ulSyncId ??????
  816. *
  817. * @return Kopano error code
  818. */
  819. ECRESULT MarkStoreAsDeleted(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulStoreHierarchyId, unsigned int ulSyncId)
  820. {
  821. ECRESULT er;
  822. std::string strQuery;
  823. ECSessionManager *lpSessionManager = NULL;
  824. ECSearchFolders *lpSearchFolders = NULL;
  825. ECCacheManager *lpCacheManager = NULL;
  826. FILETIME ft;
  827. if (lpSession == NULL || lpDatabase == NULL)
  828. return KCERR_INVALID_PARAMETER;
  829. lpSessionManager = lpSession->GetSessionManager();
  830. lpSearchFolders = lpSessionManager->GetSearchFolders();
  831. lpCacheManager = lpSessionManager->GetCacheManager();
  832. // Remove search results for deleted store
  833. lpSearchFolders->RemoveSearchFolder(ulStoreHierarchyId);
  834. // Mark item as deleted
  835. strQuery = "UPDATE hierarchy SET flags=flags|"+stringify(MSGFLAG_DELETED)+" WHERE id="+stringify(ulStoreHierarchyId);
  836. er = lpDatabase->DoUpdate(strQuery);
  837. if(er!= erSuccess)
  838. return er;
  839. // Add properties: PR_DELETED_ON
  840. GetSystemTimeAsFileTime(&ft);
  841. strQuery = "INSERT INTO properties(hierarchyid, tag, type, val_lo, val_hi) VALUES("+stringify(ulStoreHierarchyId)+","+stringify(PROP_ID(PR_DELETED_ON))+","+stringify(PROP_TYPE(PR_DELETED_ON))+","+stringify(ft.dwLowDateTime)+","+stringify(ft.dwHighDateTime)+") ON DUPLICATE KEY UPDATE val_lo="+stringify(ft.dwLowDateTime)+",val_hi="+stringify(ft.dwHighDateTime);
  842. er = lpDatabase->DoUpdate(strQuery);
  843. if(er!= erSuccess)
  844. return er;
  845. lpCacheManager->Update(fnevObjectDeleted, ulStoreHierarchyId);
  846. return erSuccess;
  847. }
  848. /*
  849. * Delete objects from different stores.
  850. *
  851. * Delete a store, folders, messages, reciepints and attachments.
  852. *
  853. * @param[in] lpSession Reference to a session object; cannot be NULL.
  854. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  855. * @param[in] ulObjectId Itemid which must be expand.
  856. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  857. * @param[in] ulSyncId ??????
  858. * @param[in] bNoTransaction Disable the database transactions.
  859. * @param[in] bCheckPermission Check the objects delete permissions.
  860. *
  861. * @return Kopano error code
  862. */
  863. ECRESULT DeleteObjects(ECSession *lpSession, ECDatabase *lpDatabase, unsigned int ulObjectId, unsigned int ulFlags, unsigned int ulSyncId, bool bNoTransaction, bool bCheckPermission)
  864. {
  865. ECListInt sObjectList = {ulObjectId};
  866. return DeleteObjects(lpSession, lpDatabase, &sObjectList, ulFlags, ulSyncId, bNoTransaction, bCheckPermission);
  867. }
  868. /*
  869. * Delete objects from different stores.
  870. *
  871. * Delete a store, folders, messages, reciepints and attachments.
  872. *
  873. * @param[in] lpSession Reference to a session object; cannot be NULL.
  874. * @param[in] lpDatabase Reference to a database object; cannot be NULL.
  875. * @param[in] lpsObjectList Reference to a list of objects that contains itemid and must be expanded.
  876. * @param[in] ulFlags Bitmask of flags that controls how the objects will deleted.
  877. * @param[in] ulSyncId ??????
  878. * @param[in] bNoTransaction Disable the database transactions.
  879. * @param[in] bCheckPermission Check the objects delete permissions.
  880. *
  881. * @return Kopano error code
  882. */
  883. ECRESULT DeleteObjects(ECSession *lpSession, ECDatabase *lpDatabase, ECListInt *lpsObjectList, unsigned int ulFlags, unsigned int ulSyncId, bool bNoTransaction, bool bCheckPermission)
  884. {
  885. ECRESULT er = erSuccess;
  886. ECListDeleteItems lstDeleteItems;
  887. ECListDeleteItems lstDeleted;
  888. ECSearchFolders *lpSearchFolders = NULL;
  889. ECSessionManager *lpSessionManager = NULL;
  890. if (lpSession == NULL || lpDatabase == NULL || lpsObjectList == NULL) {
  891. er = KCERR_INVALID_PARAMETER;
  892. goto exit;
  893. }
  894. // Make sure we're only deleting things once
  895. lpsObjectList->sort();
  896. lpsObjectList->unique();
  897. lpSessionManager = lpSession->GetSessionManager();
  898. lpSearchFolders = lpSessionManager->GetSearchFolders();
  899. if ((bNoTransaction && (ulFlags & EC_DELETE_HARD_DELETE)) ||
  900. (bNoTransaction && (ulFlags&EC_DELETE_STORE)) ) {
  901. assert(false); // This means that the caller has a transaction but that's not allowed
  902. er = KCERR_INVALID_PARAMETER;
  903. goto exit;
  904. }
  905. if(!(ulFlags & EC_DELETE_HARD_DELETE) && !bNoTransaction) {
  906. er = lpDatabase->Begin();
  907. if (er != erSuccess)
  908. goto exit;
  909. }
  910. // Collect recursive parent objects, validate item and check the permissions
  911. er = ExpandDeletedItems(lpSession, lpDatabase, lpsObjectList, ulFlags, bCheckPermission, &lstDeleteItems);
  912. if (er != erSuccess) {
  913. ec_log_info("Error while expanding delete item list, error code %u", er);
  914. goto exit;
  915. }
  916. // Remove search results for deleted folders
  917. if (ulFlags & EC_DELETE_STORE)
  918. lpSearchFolders->RemoveSearchFolder(*lpsObjectList->begin());
  919. else
  920. for (const auto &di : lstDeleteItems)
  921. if (di.ulObjType == MAPI_FOLDER &&
  922. di.ulFlags == FOLDER_SEARCH)
  923. lpSearchFolders->RemoveSearchFolder(di.ulStoreId, di.ulId);
  924. // before actual delete check items which are outside the sync scope
  925. if (ulSyncId != 0)
  926. CheckICSDeleteScope(lpDatabase, lstDeleteItems, ulSyncId);
  927. // Mark or delete objects
  928. if(ulFlags & EC_DELETE_HARD_DELETE)
  929. er = DeleteObjectHard(lpSession, lpDatabase, NULL, ulFlags, lstDeleteItems, bNoTransaction, lstDeleted);
  930. else
  931. er = DeleteObjectSoft(lpSession, lpDatabase, ulFlags, lstDeleteItems, lstDeleted);
  932. if (er != erSuccess) {
  933. ec_log_info("Error while deleting expanded item list, error code %u", er);
  934. goto exit;
  935. }
  936. if (!(ulFlags&EC_DELETE_STORE)) {
  937. //Those functions are not called with a store delete
  938. // Update store size
  939. er = DeleteObjectStoreSize(lpSession, lpDatabase, ulFlags, lstDeleted);
  940. if(er!= erSuccess) {
  941. ec_log_info("Error while updating store sizes after delete, error code %u", er);
  942. goto exit;
  943. }
  944. // Update ICS
  945. er = DeleteObjectUpdateICS(lpSession, ulFlags, lstDeleted, ulSyncId);
  946. if (er != erSuccess) {
  947. ec_log_info("Error while updating ICS after delete, error code %u", er);
  948. goto exit;
  949. }
  950. // Update local commit time on top level folders
  951. for (const auto &di : lstDeleteItems) {
  952. bool k = !(ulFlags & EC_DELETE_HARD_DELETE) &&
  953. di.fRoot && di.ulParentType == MAPI_FOLDER &&
  954. di.ulObjType == MAPI_MESSAGE;
  955. if (!k)
  956. continue;
  957. // directly hard-delete the item is not supported for updating PR_LOCAL_COMMIT_TIME_MAX
  958. er = WriteLocalCommitTimeMax(NULL, lpDatabase, di.ulParent, NULL);
  959. if (er != erSuccess) {
  960. ec_log_info("Error while updating folder access time after delete, error code %u", er);
  961. goto exit;
  962. }
  963. // the folder will receive a changed notification anyway, since items are being deleted from it
  964. }
  965. }
  966. // Finish transaction
  967. if(!(ulFlags & EC_DELETE_HARD_DELETE) && !bNoTransaction) {
  968. er = lpDatabase->Commit();
  969. if (er != erSuccess)
  970. goto exit;
  971. }
  972. // Update cache
  973. DeleteObjectCacheUpdate(lpSession, ulFlags, lstDeleted);
  974. // Send notifications
  975. if (!(ulFlags&EC_DELETE_STORE))
  976. DeleteObjectNotifications(lpSession, ulFlags, lstDeleted);
  977. exit:
  978. if (er != erSuccess && lpDatabase != NULL && !bNoTransaction &&
  979. !(ulFlags & EC_DELETE_HARD_DELETE))
  980. lpDatabase->Rollback();
  981. FreeDeletedItems(&lstDeleteItems);
  982. return er;
  983. }
  984. /**
  985. * Update PR_LOCAL_COMMIT_TIME_MAX property on a folder which contents has changed.
  986. *
  987. * This function should be called when the contents of a folder change
  988. * Affected: saveObject, emptyFolder, deleteObjects, (not done: copyObjects, copyFolder)
  989. *
  990. * @param[in] soap soap struct used for allocating memory for return value, can be NULL
  991. * @param[in] lpDatabase database pointer, should be in transaction already
  992. * @param[in] ulFolderId folder to update property in
  993. * @param[out] ppvTime time property that was written on the folder, can be NULL
  994. *
  995. * @return Kopano error code
  996. * @retval KCERR_DATABASE_ERROR database could not be updated
  997. */
  998. // @todo add parameter to pass ulFolderIdType, to check that it contains MAPI_FOLDER.
  999. ECRESULT WriteLocalCommitTimeMax(struct soap *soap, ECDatabase *lpDatabase, unsigned int ulFolderId, propVal **ppvTime)
  1000. {
  1001. ECRESULT er;
  1002. FILETIME ftNow;
  1003. std::string strQuery;
  1004. propVal *pvTime = NULL;
  1005. GetSystemTimeAsFileTime(&ftNow);
  1006. if (soap && ppvTime) {
  1007. pvTime = s_alloc<propVal>(soap);
  1008. pvTime->ulPropTag = PR_LOCAL_COMMIT_TIME_MAX;
  1009. pvTime->__union = SOAP_UNION_propValData_hilo;
  1010. pvTime->Value.hilo = s_alloc<hiloLong>(soap);
  1011. pvTime->Value.hilo->hi = ftNow.dwHighDateTime;
  1012. pvTime->Value.hilo->lo = ftNow.dwLowDateTime;
  1013. }
  1014. strQuery = "INSERT INTO properties (hierarchyid, tag, type, val_hi, val_lo) VALUES ("
  1015. +stringify(ulFolderId)+","+stringify(PROP_ID(PR_LOCAL_COMMIT_TIME_MAX))+","+stringify(PROP_TYPE(PR_LOCAL_COMMIT_TIME_MAX))+","
  1016. +stringify(ftNow.dwHighDateTime)+","+stringify(ftNow.dwLowDateTime)+")"+
  1017. " ON DUPLICATE KEY UPDATE val_hi="+stringify(ftNow.dwHighDateTime)+",val_lo="+stringify(ftNow.dwLowDateTime);
  1018. er = lpDatabase->DoInsert(strQuery);
  1019. if (er != erSuccess)
  1020. return er;
  1021. if (ppvTime)
  1022. *ppvTime = std::move(pvTime);
  1023. return erSuccess;
  1024. }
  1025. void FreeDeleteItem(DELETEITEM *src)
  1026. {
  1027. s_free(nullptr, src->sEntryId.__ptr);
  1028. }
  1029. void FreeDeletedItems(ECListDeleteItems *lplstDeleteItems)
  1030. {
  1031. for (auto &di : *lplstDeleteItems)
  1032. FreeDeleteItem(&di);
  1033. lplstDeleteItems->clear();
  1034. }
  1035. /**
  1036. * Update value in tproperties by taking value from properties for a list of objects
  1037. *
  1038. * This should be called whenever a value is changed in the 'properties' table outside WriteProps(). It updates the value
  1039. * in tproperties if necessary (it may not be in tproperties at all).
  1040. *
  1041. * @param[in] lpDatabase Database handle
  1042. * @param[in] ulPropTag Property tag to update in tproperties
  1043. * @param[in] ulFolderId Folder ID for all objects in lpObjectIDs
  1044. * @param[in] lpObjectIDs List of object IDs to update
  1045. * @return result
  1046. */
  1047. ECRESULT UpdateTProp(ECDatabase *lpDatabase, unsigned int ulPropTag, unsigned int ulFolderId, ECListInt *lpObjectIDs) {
  1048. std::string strQuery;
  1049. if(lpObjectIDs->empty())
  1050. return erSuccess; // Nothing to do
  1051. // Update tproperties by taking value from properties
  1052. strQuery = "UPDATE tproperties JOIN properties on properties.hierarchyid=tproperties.hierarchyid AND properties.tag=tproperties.tag AND properties.type=tproperties.type SET tproperties.val_ulong = properties.val_ulong "
  1053. "WHERE properties.tag = " + stringify(PROP_ID(ulPropTag)) + " AND properties.type = " + stringify(PROP_TYPE(ulPropTag)) + " AND tproperties.folderid = " + stringify(ulFolderId) + " AND properties.hierarchyid IN (";
  1054. for (auto iObjectid = lpObjectIDs->cbegin(); iObjectid != lpObjectIDs->cend(); ++iObjectid) {
  1055. if(iObjectid != lpObjectIDs->cbegin())
  1056. strQuery += ",";
  1057. strQuery += stringify(*iObjectid);
  1058. }
  1059. strQuery += ")";
  1060. return lpDatabase->DoUpdate(strQuery);
  1061. }
  1062. /**
  1063. * Update value in tproperties by taking value from properties for a single object
  1064. *
  1065. * This should be called whenever a value is changed in the 'properties' table outside WriteProps(). It updates the value
  1066. * in tproperties if necessary (it may not be in tproperties at all).
  1067. *
  1068. * @param[in] lpDatabase Database handle
  1069. * @param[in] ulPropTag Property tag to update in tproperties
  1070. * @param[in] ulFolderId Folder ID for all objects in lpObjectIDs
  1071. * @param[in] ulObjId Object ID to update
  1072. * @return result
  1073. */
  1074. ECRESULT UpdateTProp(ECDatabase *lpDatabase, unsigned int ulPropTag, unsigned int ulFolderId, unsigned int ulObjId) {
  1075. ECListInt list;
  1076. list.push_back(ulObjId);
  1077. return UpdateTProp(lpDatabase, ulPropTag, ulFolderId, &list);
  1078. }
  1079. /**
  1080. * Update folder count for a folder by adding or removing a certain amount
  1081. *
  1082. * This function can be used to incrementally update a folder count of a folder. The lDelta can be positive (counter increases)
  1083. * or negative (counter decreases)
  1084. *
  1085. * @param lpDatabase Database handle
  1086. * @param ulFolderId Folder ID to update
  1087. * @param ulPropTag Counter property to update (must be type that uses val_ulong (PT_LONG or PT_BOOLEAN))
  1088. * @param lDelta Signed integer change
  1089. * @return result
  1090. */
  1091. ECRESULT UpdateFolderCount(ECDatabase *lpDatabase, unsigned int ulFolderId, unsigned int ulPropTag, int lDelta)
  1092. {
  1093. ECRESULT er;
  1094. std::string strQuery;
  1095. unsigned int ulParentId;
  1096. unsigned int ulType;
  1097. if(lDelta == 0)
  1098. return erSuccess; // No change
  1099. er = g_lpSessionManager->GetCacheManager()->GetObject(ulFolderId, &ulParentId, NULL, NULL, &ulType);
  1100. if(er != erSuccess)
  1101. return er;
  1102. if (ulType != MAPI_FOLDER) {
  1103. ec_log_info("Not updating folder count %d for non-folder object %d type %d", lDelta, ulFolderId, ulType);
  1104. assert(ulType == MAPI_FOLDER);
  1105. return erSuccess;
  1106. }
  1107. strQuery = "UPDATE properties SET val_ulong = ";
  1108. // make sure val_ulong stays a positive number
  1109. if (lDelta < 0)
  1110. strQuery += "IF (val_ulong >= " + stringify(abs(lDelta),false,true) + ", val_ulong + " + stringify(lDelta,false,true) + ", 0)";
  1111. else
  1112. strQuery += "val_ulong + " + stringify(lDelta,false,true);
  1113. strQuery += " WHERE hierarchyid = " + stringify(ulFolderId) + " AND tag = " + stringify(PROP_ID(ulPropTag)) + " AND type = " + stringify(PROP_TYPE(ulPropTag));
  1114. er = lpDatabase->DoUpdate(strQuery);
  1115. if(er != erSuccess)
  1116. return er;
  1117. er = UpdateTProp(lpDatabase, ulPropTag, ulParentId, ulFolderId);
  1118. if(er != erSuccess)
  1119. return er;
  1120. return erSuccess;
  1121. }
  1122. ECRESULT CheckQuota(ECSession *lpecSession, ULONG ulStoreId)
  1123. {
  1124. ECRESULT er;
  1125. long long llStoreSize = 0;
  1126. eQuotaStatus QuotaStatus = QUOTA_OK;
  1127. er = lpecSession->GetSecurity()->GetStoreSize(ulStoreId, &llStoreSize);
  1128. if (er != erSuccess)
  1129. return er;
  1130. er = lpecSession->GetSecurity()->CheckQuota(ulStoreId, llStoreSize, &QuotaStatus);
  1131. if (er != erSuccess)
  1132. return er;
  1133. if (QuotaStatus == QUOTA_HARDLIMIT)
  1134. return KCERR_STORE_FULL;
  1135. return erSuccess;
  1136. }
  1137. ECRESULT MapEntryIdToObjectId(ECSession *lpecSession, ECDatabase *lpDatabase, ULONG ulObjId, const entryId &sEntryId)
  1138. {
  1139. ECRESULT er;
  1140. std::string strQuery;
  1141. er = RemoveStaleIndexedProp(lpDatabase, PR_ENTRYID, sEntryId.__ptr, sEntryId.__size);
  1142. if(er != erSuccess) {
  1143. ec_log_crit("ERROR: Collision detected while setting entryid. objectid=%u, entryid=%s, user=%u", ulObjId, bin2hex(sEntryId.__size, (unsigned char *)sEntryId.__ptr).c_str(), lpecSession->GetSecurity()->GetUserId());
  1144. return KCERR_DATABASE_ERROR;
  1145. }
  1146. strQuery = "INSERT INTO indexedproperties (hierarchyid,tag,val_binary) VALUES("+stringify(ulObjId)+", 0x0FFF, "+lpDatabase->EscapeBinary(sEntryId.__ptr, sEntryId.__size)+")";
  1147. er = lpDatabase->DoInsert(strQuery);
  1148. if(er != erSuccess)
  1149. return er;
  1150. g_lpSessionManager->GetCacheManager()->SetObjectEntryId((entryId*)&sEntryId, ulObjId);
  1151. return erSuccess;
  1152. }
  1153. ECRESULT UpdateFolderCounts(ECDatabase *lpDatabase, ULONG ulParentId, ULONG ulFlags, propValArray *lpModProps)
  1154. {
  1155. ECRESULT er = erSuccess;
  1156. if (ulFlags & MAPI_ASSOCIATED)
  1157. er = UpdateFolderCount(lpDatabase, ulParentId, PR_ASSOC_CONTENT_COUNT, 1);
  1158. else {
  1159. er = UpdateFolderCount(lpDatabase, ulParentId, PR_CONTENT_COUNT, 1);
  1160. struct propVal *lpPropMessageFlags = NULL;
  1161. lpPropMessageFlags = FindProp(lpModProps, PR_MESSAGE_FLAGS);
  1162. if (er == erSuccess && (!lpPropMessageFlags || (lpPropMessageFlags->Value.ul & MSGFLAG_READ) == 0))
  1163. er = UpdateFolderCount(lpDatabase, ulParentId, PR_CONTENT_UNREAD, 1);
  1164. }
  1165. return er;
  1166. }
  1167. /**
  1168. * Handles the outgoingqueue table according to the PR_MESSAGE_FLAGS
  1169. * of a message. This function does not do transactions, so you must
  1170. * already be in a database transaction.
  1171. *
  1172. * @param[in] lpDatabase Database object
  1173. * @param[in] ulSyncId syncid of the message
  1174. * @param[in] ulStoreId storeid of the message
  1175. * @param[in] ulObjId hierarchyid of the message
  1176. * @param[in] bNewItem message is new
  1177. * @param[in] lpModProps properties of the message
  1178. *
  1179. * @return Kopano error code
  1180. */
  1181. ECRESULT ProcessSubmitFlag(ECDatabase *lpDatabase, ULONG ulSyncId, ULONG ulStoreId, ULONG ulObjId, bool bNewItem, propValArray *lpModProps)
  1182. {
  1183. ECRESULT er = erSuccess;
  1184. DB_RESULT lpDBResult;
  1185. std::string strQuery;
  1186. struct propVal *lpPropMessageFlags = NULL; // non-free
  1187. ULONG ulPrevSubmitFlag = 0;
  1188. // If the messages was saved by an ICS syncer, then we need to sync the PR_MESSAGE_FLAGS for MSGFLAG_SUBMIT if it
  1189. // was included in the save.
  1190. lpPropMessageFlags = FindProp(lpModProps, PR_MESSAGE_FLAGS);
  1191. if (ulSyncId > 0 && lpPropMessageFlags) {
  1192. if (bNewItem) {
  1193. // Item is new, so it's not in the queue at the moment
  1194. ulPrevSubmitFlag = 0;
  1195. } else {
  1196. // Existing item. Check its current submit flag by looking at the outgoing queue.
  1197. strQuery = "SELECT hierarchy_id FROM outgoingqueue WHERE hierarchy_id=" + stringify(ulObjId) + " AND flags & " + stringify(EC_SUBMIT_MASTER) + " = 0 LIMIT 1";
  1198. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1199. if (er != erSuccess)
  1200. return er;
  1201. // Item is (1)/is not (0) in the outgoing queue at the moment
  1202. ulPrevSubmitFlag = lpDatabase->GetNumRows(lpDBResult) > 0;
  1203. }
  1204. if ((lpPropMessageFlags->Value.ul & MSGFLAG_SUBMIT) && ulPrevSubmitFlag == 0) {
  1205. // Message was previously not submitted, but it is now, so add it to the outgoing queue and set the correct flags.
  1206. strQuery = "INSERT INTO outgoingqueue (store_id, hierarchy_id, flags) VALUES("+stringify(ulStoreId)+", "+stringify(ulObjId)+"," + stringify(EC_SUBMIT_LOCAL) + ")";
  1207. er = lpDatabase->DoInsert(strQuery);
  1208. if (er != erSuccess)
  1209. return er;
  1210. strQuery = "UPDATE properties SET val_ulong = val_ulong | " + stringify(MSGFLAG_SUBMIT) +
  1211. " WHERE hierarchyid = " + stringify(ulObjId) +
  1212. " AND type = " + stringify(PROP_TYPE(PR_MESSAGE_FLAGS)) +
  1213. " AND tag = " + stringify(PROP_ID(PR_MESSAGE_FLAGS));
  1214. er = lpDatabase->DoUpdate(strQuery);
  1215. if (er != erSuccess)
  1216. return er;
  1217. // The object has changed, update the cache.
  1218. g_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulObjId);
  1219. // Update in-memory outgoing tables
  1220. g_lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_ADD, ulStoreId, ulObjId, EC_SUBMIT_LOCAL, MAPI_MESSAGE);
  1221. } else if ((lpPropMessageFlags->Value.ul & MSGFLAG_SUBMIT) == 0 && ulPrevSubmitFlag == 1) {
  1222. // Message was previously submitted, but is not submitted any more. Remove it from the outgoing queue and remove the flags.
  1223. strQuery = "DELETE FROM outgoingqueue WHERE hierarchy_id = " + stringify(ulObjId);
  1224. er = lpDatabase->DoDelete(strQuery);
  1225. if (er != erSuccess)
  1226. return er;
  1227. strQuery = "UPDATE properties SET val_ulong = val_ulong & ~" + stringify(MSGFLAG_SUBMIT) +
  1228. " WHERE hierarchyid = " + stringify(ulObjId) +
  1229. " AND type = " + stringify(PROP_TYPE(PR_MESSAGE_FLAGS)) +
  1230. " AND tag = " + stringify(PROP_ID(PR_MESSAGE_FLAGS));
  1231. er = lpDatabase->DoUpdate(strQuery);
  1232. if (er != erSuccess)
  1233. return er;
  1234. // The object has changed, update the cache.
  1235. g_lpSessionManager->GetCacheManager()->Update(fnevObjectModified, ulObjId);
  1236. // Update in-memory outgoing tables
  1237. g_lpSessionManager->UpdateOutgoingTables(ECKeyTable::TABLE_ROW_DELETE, ulStoreId, ulObjId, EC_SUBMIT_LOCAL, MAPI_MESSAGE);
  1238. }
  1239. }
  1240. return erSuccess;
  1241. }
  1242. ECRESULT CreateNotifications(ULONG ulObjId, ULONG ulObjType, ULONG ulParentId, ULONG ulGrandParentId, bool bNewItem, propValArray *lpModProps, struct propVal *lpvCommitTime)
  1243. {
  1244. unsigned int ulObjFlags = 0;
  1245. unsigned int ulParentFlags = 0;
  1246. if(!((ulObjType == MAPI_ATTACH || ulObjType == MAPI_MESSAGE) && ulObjType == MAPI_STORE) &&
  1247. (ulObjType == MAPI_MESSAGE || ulObjType == MAPI_FOLDER || ulObjType == MAPI_STORE))
  1248. {
  1249. g_lpSessionManager->GetCacheManager()->GetObjectFlags(ulObjId, &ulObjFlags);
  1250. // update PR_LOCAL_COMMIT_TIME_MAX in cache for disconnected clients who want to know if the folder contents changed
  1251. if (lpvCommitTime) {
  1252. sObjectTableKey key(ulParentId, 0);
  1253. g_lpSessionManager->GetCacheManager()->SetCell(&key, PR_LOCAL_COMMIT_TIME_MAX, lpvCommitTime);
  1254. }
  1255. if (bNewItem) {
  1256. // Notify that the message has been created
  1257. g_lpSessionManager->NotificationCreated(ulObjType, ulObjId, ulParentId);
  1258. g_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_ADD, ulObjFlags & MSGFLAG_NOTIFY_FLAGS, ulParentId, ulObjId, ulObjType);
  1259. // Notify that the folder in which the message resided has changed (PR_CONTENT_COUNT, PR_CONTENT_UNREAD)
  1260. if(ulObjFlags & MAPI_ASSOCIATED)
  1261. g_lpSessionManager->GetCacheManager()->UpdateCell(ulParentId, PR_ASSOC_CONTENT_COUNT, 1);
  1262. else {
  1263. g_lpSessionManager->GetCacheManager()->UpdateCell(ulParentId, PR_CONTENT_COUNT, 1);
  1264. struct propVal *lpPropMessageFlags = FindProp(lpModProps, PR_MESSAGE_FLAGS);
  1265. if (lpPropMessageFlags && (lpPropMessageFlags->Value.ul & MSGFLAG_READ) == 0)
  1266. // Unread message
  1267. g_lpSessionManager->GetCacheManager()->UpdateCell(ulParentId, PR_CONTENT_UNREAD, 1);
  1268. }
  1269. g_lpSessionManager->NotificationModified(MAPI_FOLDER, ulParentId);
  1270. if (ulGrandParentId) {
  1271. g_lpSessionManager->GetCacheManager()->GetObjectFlags(ulParentId, &ulParentFlags);
  1272. g_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, ulParentFlags & MSGFLAG_NOTIFY_FLAGS, ulGrandParentId, ulParentId, MAPI_FOLDER);
  1273. }
  1274. } else if (ulObjType == MAPI_STORE) {
  1275. g_lpSessionManager->NotificationModified(ulObjType, ulObjId);
  1276. } else {
  1277. // Notify that the message has been modified
  1278. if (ulObjType == MAPI_MESSAGE)
  1279. g_lpSessionManager->NotificationModified(ulObjType, ulObjId, ulParentId);
  1280. else
  1281. g_lpSessionManager->NotificationModified(ulObjType, ulObjId);
  1282. if (ulParentId)
  1283. g_lpSessionManager->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, ulObjFlags & MSGFLAG_NOTIFY_FLAGS, ulParentId, ulObjId, ulObjType);
  1284. }
  1285. }
  1286. return erSuccess;
  1287. }
  1288. ECRESULT WriteSingleProp(ECDatabase *lpDatabase, unsigned int ulObjId, unsigned int ulFolderId, struct propVal *lpPropVal, bool bColumnProp, unsigned int ulMaxQuerySize, std::string &strInsertQuery)
  1289. {
  1290. ECRESULT er;
  1291. std::string strColData;
  1292. std::string strQueryAppend;
  1293. unsigned int ulColId = 0;
  1294. assert(PROP_TYPE(lpPropVal->ulPropTag) != PT_UNICODE);
  1295. er = CopySOAPPropValToDatabasePropVal(lpPropVal, &ulColId, strColData, lpDatabase, bColumnProp);
  1296. if(er != erSuccess)
  1297. return erSuccess; // Data from client was bogus, ignore it.
  1298. if (!strInsertQuery.empty())
  1299. strQueryAppend = ",";
  1300. else if (bColumnProp)
  1301. strQueryAppend = "REPLACE INTO tproperties (hierarchyid,tag,type,folderid," + (std::string)PROPCOLVALUEORDER(tproperties) + ") VALUES";
  1302. else
  1303. strQueryAppend = "REPLACE INTO properties (hierarchyid,tag,type," + (std::string)PROPCOLVALUEORDER(properties) + ") VALUES";
  1304. strQueryAppend += "(" + stringify(ulObjId) + "," +
  1305. stringify(PROP_ID(lpPropVal->ulPropTag)) + "," +
  1306. stringify(PROP_TYPE(lpPropVal->ulPropTag)) + ",";
  1307. if (bColumnProp)
  1308. strQueryAppend += stringify(ulFolderId) + ",";
  1309. for (unsigned int k = 0; k < VALUE_NR_MAX; ++k) {
  1310. if (k == ulColId)
  1311. strQueryAppend += strColData;
  1312. else if (k == VALUE_NR_HILO)
  1313. strQueryAppend += "null,null";
  1314. else
  1315. strQueryAppend += "null";
  1316. if (k != VALUE_NR_MAX-1)
  1317. strQueryAppend += ",";
  1318. }
  1319. strQueryAppend += ")";
  1320. if (ulMaxQuerySize > 0 && strInsertQuery.size() + strQueryAppend.size() > ulMaxQuerySize)
  1321. return KCERR_TOO_BIG;
  1322. strInsertQuery.append(strQueryAppend);
  1323. return erSuccess;
  1324. }
  1325. ECRESULT WriteProp(ECDatabase *lpDatabase, unsigned int ulObjId, unsigned int ulParentId, struct propVal *lpPropVal) {
  1326. ECRESULT er;
  1327. std::string strQuery;
  1328. strQuery.clear();
  1329. WriteSingleProp(lpDatabase, ulObjId, ulParentId, lpPropVal, false, 0, strQuery);
  1330. er = lpDatabase->DoInsert(strQuery);
  1331. if(er != erSuccess)
  1332. return er;
  1333. if(ulParentId > 0) {
  1334. strQuery.clear();
  1335. WriteSingleProp(lpDatabase, ulObjId, ulParentId, lpPropVal, true, 0, strQuery);
  1336. er = lpDatabase->DoInsert(strQuery);
  1337. if(er != erSuccess)
  1338. return er;
  1339. }
  1340. return erSuccess;
  1341. }
  1342. ECRESULT GetNamesFromIDs(struct soap *soap, ECDatabase *lpDatabase, struct propTagArray *lpPropTags, struct namedPropArray *lpsNames)
  1343. {
  1344. ECRESULT er = erSuccess;
  1345. DB_RESULT lpDBResult;
  1346. DB_ROW lpDBRow = NULL;
  1347. DB_LENGTHS lpDBLen = NULL;
  1348. std::string strQuery;
  1349. // Allocate memory for return object
  1350. lpsNames->__ptr = s_alloc<namedProp>(soap, lpPropTags->__size);
  1351. lpsNames->__size = lpPropTags->__size;
  1352. memset(lpsNames->__ptr, 0, sizeof(struct namedProp) * lpPropTags->__size);
  1353. for (gsoap_size_t i = 0; i < lpPropTags->__size; ++i) {
  1354. strQuery = "SELECT nameid, namestring, guid FROM names WHERE id=" + stringify(lpPropTags->__ptr[i]-1) + " LIMIT 1";
  1355. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1356. if(er != erSuccess)
  1357. return er;
  1358. if(lpDatabase->GetNumRows(lpDBResult) == 1) {
  1359. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1360. lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
  1361. if(lpDBRow != NULL) {
  1362. if(lpDBRow[0] != NULL) {
  1363. // It's an ID type
  1364. lpsNames->__ptr[i].lpId = s_alloc<unsigned int>(soap);
  1365. *lpsNames->__ptr[i].lpId = atoi(lpDBRow[0]);
  1366. } else if(lpDBRow[1] != NULL) {
  1367. // It's a String type
  1368. lpsNames->__ptr[i].lpString = s_alloc<char>(soap, strlen(lpDBRow[1])+1);
  1369. strcpy(lpsNames->__ptr[i].lpString, lpDBRow[1]);
  1370. }
  1371. if(lpDBRow[2] != NULL) {
  1372. // Got a GUID (should always do so ...)
  1373. lpsNames->__ptr[i].lpguid = s_alloc<struct xsd__base64Binary>(soap);
  1374. lpsNames->__ptr[i].lpguid->__size = lpDBLen[2];
  1375. lpsNames->__ptr[i].lpguid->__ptr = s_alloc<unsigned char>(soap, lpDBLen[2]);
  1376. memcpy(lpsNames->__ptr[i].lpguid->__ptr, lpDBRow[2], lpDBLen[2]);
  1377. }
  1378. } else {
  1379. ec_log_crit("GetNamesFromIDs(): row/col NULL");
  1380. return KCERR_DATABASE_ERROR;
  1381. }
  1382. } else {
  1383. // No entry
  1384. lpsNames->__ptr[i].lpguid = NULL;
  1385. lpsNames->__ptr[i].lpId = NULL;
  1386. lpsNames->__ptr[i].lpString = NULL;
  1387. }
  1388. }
  1389. return erSuccess;
  1390. }
  1391. /**
  1392. * Resets the folder counts of a folder
  1393. *
  1394. * This function resets the counts of a folder by recalculating them from the actual
  1395. * database child entries. If any of the current counts is out-of-date, they are updated to the
  1396. * correct value and the foldercount_reset counter is increased. Note that in theory, foldercount_reset
  1397. * should always remain at 0. If not, this means that a bug somewhere has failed to update the folder
  1398. * count correctly at some point.
  1399. *
  1400. * WARNING this function creates its own transaction!
  1401. *
  1402. * @param[in] lpSession Session to get database handle, etc
  1403. * @param[in] ulObjId ID of the folder to recalc
  1404. * @param[out] lpulUpdates Will be set to number of counters that were updated (may be NULL)
  1405. * @return result
  1406. */
  1407. ECRESULT ResetFolderCount(ECSession *lpSession, unsigned int ulObjId, unsigned int *lpulUpdates)
  1408. {
  1409. ECRESULT er = erSuccess;
  1410. DB_RESULT lpDBResult;
  1411. DB_ROW lpDBRow = NULL;
  1412. std::string strQuery;
  1413. string strCC; // Content count
  1414. string strACC; // Assoc. content count
  1415. string strDMC; // Deleted message count
  1416. string strDAC; // Deleted assoc message count
  1417. string strCFC; // Child folder count
  1418. string strDFC; // Deleted folder count
  1419. string strCU; // Content unread
  1420. unsigned int ulAffected = 0;
  1421. unsigned int ulParent = 0;
  1422. ECDatabase *lpDatabase = NULL;
  1423. er = lpSession->GetDatabase(&lpDatabase);
  1424. if(er != erSuccess)
  1425. goto exit;
  1426. er = lpDatabase->Begin();
  1427. if(er != erSuccess)
  1428. goto exit;
  1429. // Lock the counters now since the locking order is normally counters/foldercontent/storesize/localcommittimemax. So our lock order
  1430. // is now counters/foldercontent/counters which is compatible (*cough* in theory *cough*)
  1431. strQuery = "SELECT val_ulong FROM properties WHERE hierarchyid = " + stringify(ulObjId) + " FOR UPDATE";
  1432. er = lpDatabase->DoSelect(strQuery, NULL); // don't care about the result
  1433. if (er != erSuccess)
  1434. goto exit;
  1435. // Gets counters from hierarchy: cc, acc, dmc, dac, cfc, dfc
  1436. // use for update, since the update query below must see the same values, mysql should already block here.
  1437. strQuery = "SELECT count(if(flags & 0x440 = 0 && type = 5, 1, null)) AS cc, count(if(flags & 0x440 = 0x40 and type = 5, 1, null)) AS acc, count(if(flags & 0x440 = 0x400 and type = 5, 1, null)) AS dmc, count(if(flags & 0x440 = 0x440 and type = 5, 1, null)) AS dac, count(if(flags & 0x400 = 0 and type = 3, 1, null)) AS cfc, count(if(flags & 0x400 and type = 3, 1, null)) AS dfc from hierarchy where parent=" + stringify(ulObjId) + " FOR UPDATE";
  1438. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1439. if (er != erSuccess)
  1440. goto exit;
  1441. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1442. if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL || lpDBRow[3] == NULL || lpDBRow[4] == NULL) {
  1443. er = KCERR_DATABASE_ERROR;
  1444. ec_log_crit("ResetFolderCount(): row/col NULL (1)");
  1445. goto exit;
  1446. }
  1447. strCC = lpDBRow[0];
  1448. strACC = lpDBRow[1];
  1449. strDMC = lpDBRow[2];
  1450. strDAC = lpDBRow[3];
  1451. strCFC = lpDBRow[4];
  1452. strDFC = lpDBRow[5];
  1453. // Gets unread counters from hierarchy / properties / tproperties
  1454. strQuery = "SELECT "
  1455. // Part one, unread count from non-deferred rows (get the flags from tproperties)
  1456. "(SELECT count(if(tproperties.val_ulong & 1,null,1)) from hierarchy left join tproperties on tproperties.folderid=" + stringify(ulObjId) + " and tproperties.tag = 0x0e07 and tproperties.type = 3 and tproperties.hierarchyid=hierarchy.id left join deferredupdate on deferredupdate.hierarchyid=hierarchy.id where parent=" + stringify(ulObjId) + " and hierarchy.type=5 and hierarchy.flags = 0 and deferredupdate.hierarchyid is null FOR UPDATE)"
  1457. " + "
  1458. // Part two, unread count from deferred rows (get the flags from properties)
  1459. "(SELECT count(if(properties.val_ulong & 1,null,1)) from hierarchy left join properties on properties.tag = 0x0e07 and properties.type = 3 and properties.hierarchyid=hierarchy.id join deferredupdate on deferredupdate.hierarchyid=hierarchy.id and deferredupdate.folderid = parent where parent=" + stringify(ulObjId) + " and hierarchy.type=5 and hierarchy.flags = 0 FOR UPDATE)"
  1460. ;
  1461. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1462. if (er != erSuccess)
  1463. goto exit;
  1464. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1465. if(lpDBRow == NULL || lpDBRow[0] == NULL) {
  1466. er = KCERR_DATABASE_ERROR;
  1467. ec_log_crit("ResetFolderCount(): row/col NULL (2)");
  1468. goto exit;
  1469. }
  1470. strCU = lpDBRow[0];
  1471. strQuery = "UPDATE properties SET val_ulong = CASE tag "
  1472. " WHEN " + stringify(PROP_ID(PR_CONTENT_COUNT)) + " THEN + " + strCC +
  1473. " WHEN " + stringify(PROP_ID(PR_ASSOC_CONTENT_COUNT)) + " THEN + " + strACC +
  1474. " WHEN " + stringify(PROP_ID(PR_DELETED_MSG_COUNT)) + " THEN + " + strDMC +
  1475. " WHEN " + stringify(PROP_ID(PR_DELETED_ASSOC_MSG_COUNT)) + " THEN + " + strDAC +
  1476. " WHEN " + stringify(PROP_ID(PR_FOLDER_CHILD_COUNT)) + " THEN + " + strCFC +
  1477. " WHEN " + stringify(PROP_ID(PR_SUBFOLDERS)) + " THEN + " + strCFC +
  1478. " WHEN " + stringify(PROP_ID(PR_DELETED_FOLDER_COUNT)) + " THEN + " + strDFC +
  1479. " WHEN " + stringify(PROP_ID(PR_CONTENT_UNREAD)) + " THEN + " + strCU +
  1480. " END WHERE hierarchyid = " + stringify(ulObjId) + " AND TAG in (" +
  1481. stringify(PROP_ID(PR_CONTENT_COUNT)) + "," +
  1482. stringify(PROP_ID(PR_ASSOC_CONTENT_COUNT)) + "," +
  1483. stringify(PROP_ID(PR_DELETED_MSG_COUNT)) + "," +
  1484. stringify(PROP_ID(PR_DELETED_ASSOC_MSG_COUNT)) + "," +
  1485. stringify(PROP_ID(PR_FOLDER_CHILD_COUNT)) + "," +
  1486. stringify(PROP_ID(PR_SUBFOLDERS)) + "," +
  1487. stringify(PROP_ID(PR_DELETED_FOLDER_COUNT)) + "," +
  1488. stringify(PROP_ID(PR_CONTENT_UNREAD)) +
  1489. ")";
  1490. er = lpDatabase->DoUpdate(strQuery, &ulAffected);
  1491. if(er != erSuccess)
  1492. goto exit;
  1493. if (ulAffected == 0)
  1494. // Nothing updated
  1495. goto exit;
  1496. // Trigger an assertion since in practice this should never happen
  1497. // assert(false);
  1498. g_lpStatsCollector->Increment(SCN_DATABASE_COUNTER_RESYNCS);
  1499. er = lpSession->GetSessionManager()->GetCacheManager()->GetParent(ulObjId, &ulParent);
  1500. if(er != erSuccess) {
  1501. // No parent -> root folder. Nothing else we need to do now.
  1502. er = erSuccess;
  1503. goto exit;
  1504. }
  1505. // Update tprops
  1506. strQuery = "REPLACE INTO tproperties (folderid, hierarchyid, tag, type, val_ulong) "
  1507. "SELECT " + stringify(ulParent) + ", properties.hierarchyid, properties.tag, properties.type, properties.val_ulong "
  1508. "FROM properties "
  1509. "WHERE tag IN (" +
  1510. stringify(PROP_ID(PR_CONTENT_COUNT)) + "," +
  1511. stringify(PROP_ID(PR_ASSOC_CONTENT_COUNT)) + "," +
  1512. stringify(PROP_ID(PR_DELETED_MSG_COUNT)) + "," +
  1513. stringify(PROP_ID(PR_DELETED_ASSOC_MSG_COUNT)) + "," +
  1514. stringify(PROP_ID(PR_FOLDER_CHILD_COUNT)) + "," +
  1515. stringify(PROP_ID(PR_SUBFOLDERS)) + "," +
  1516. stringify(PROP_ID(PR_DELETED_FOLDER_COUNT)) + "," +
  1517. stringify(PROP_ID(PR_CONTENT_UNREAD)) +
  1518. ") AND hierarchyid = " + stringify(ulObjId);
  1519. er = lpDatabase->DoInsert(strQuery);
  1520. if(er != erSuccess)
  1521. goto exit;
  1522. // Clear cache and update table entries. We do not send an object notification since the object hasn't really changed and
  1523. // this is normally called just before opening an entry anyway, so the counters retrieved there will be ok.
  1524. lpSession->GetSessionManager()->GetCacheManager()->Update(fnevObjectModified, ulObjId);
  1525. er = lpSession->GetSessionManager()->UpdateTables(ECKeyTable::TABLE_ROW_MODIFY, 0, ulParent, ulObjId, MAPI_FOLDER);
  1526. if(er != erSuccess)
  1527. goto exit;
  1528. exit:
  1529. if(er != erSuccess)
  1530. lpDatabase->Rollback();
  1531. else {
  1532. lpDatabase->Commit();
  1533. if (lpulUpdates)
  1534. *lpulUpdates = ulAffected;
  1535. }
  1536. return er;
  1537. }
  1538. /**
  1539. * Removes stale indexed properties
  1540. *
  1541. * In some cases, the database can contain stale (old) indexed properties. One example is when
  1542. * you replicate a store onto a server, then remove that store with kopano-admin --remove-store
  1543. * and then do the replication again. The second replication will attempt to create items with
  1544. * equal entryids and sourcekeys. Since the softdelete purge will not have removed the data from
  1545. * the system yet, we check to see if the indexedproperty that is in the database is actually in
  1546. * use by checking if the store it belongs to is deleted. If so, we remove the entry. If the
  1547. * item is used by a non-deleted store, then an error occurs since you cannot use the same indexed
  1548. * property for two items.
  1549. *
  1550. * @param[in] lpDatabase Database handle
  1551. * @param[in] ulPropTag Property tag to scan for
  1552. * @param[in] lpData Data if the indexed property
  1553. * @param[in] cbSize Bytes in lpData
  1554. * @return result
  1555. */
  1556. ECRESULT RemoveStaleIndexedProp(ECDatabase *lpDatabase, unsigned int ulPropTag, unsigned char *lpData, unsigned int cbSize)
  1557. {
  1558. ECRESULT er = erSuccess;
  1559. DB_RESULT lpDBResult;
  1560. DB_ROW lpDBRow = NULL;
  1561. std::string strQuery;
  1562. unsigned int ulObjId = 0;
  1563. unsigned int ulStoreId = 0;
  1564. bool bStale = false;
  1565. strQuery = "SELECT hierarchyid FROM indexedproperties WHERE tag= " + stringify(PROP_ID(ulPropTag)) + " AND val_binary=" + lpDatabase->EscapeBinary(lpData, cbSize) + " LIMIT 1";
  1566. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1567. if(er != erSuccess)
  1568. return er;
  1569. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1570. if(!lpDBRow || lpDBRow[0] == NULL)
  1571. return er; /* Nothing there, no need to do anything */
  1572. ulObjId = atoui(lpDBRow[0]);
  1573. // Check if the found item is in a deleted store
  1574. if(g_lpSessionManager->GetCacheManager()->GetStore(ulObjId, &ulStoreId, NULL) == erSuccess) {
  1575. // Find the store
  1576. strQuery = "SELECT hierarchy_id FROM stores WHERE hierarchy_id = " + stringify(ulStoreId) + " LIMIT 1";
  1577. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1578. if(er != erSuccess)
  1579. return er;
  1580. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  1581. if (lpDBRow == nullptr || lpDBRow[0] == nullptr)
  1582. bStale = true;
  1583. } else {
  1584. // The item has no store. This means it's safe to re-use the indexed prop. Possibly the store is half-deleted at this time.
  1585. bStale = true;
  1586. }
  1587. if(bStale) {
  1588. // Item is in a deleted store. This means we can delete it
  1589. er = lpDatabase->DoDelete("DELETE FROM indexedproperties WHERE hierarchyid = " + stringify(ulObjId));
  1590. if(er != erSuccess)
  1591. return er;
  1592. // Remove it from the cache
  1593. g_lpSessionManager->GetCacheManager()->RemoveIndexData(ulPropTag, cbSize, lpData);
  1594. }
  1595. else {
  1596. ec_log_crit("RemoveStaleIndexedProp(): caller wanted to remove the entry, but we cannot since it is in use");
  1597. return KCERR_COLLISION;
  1598. }
  1599. return erSuccess;
  1600. }
  1601. ECRESULT ApplyFolderCounts(ECDatabase *lpDatabase, unsigned int ulFolderId, const PARENTINFO &pi) {
  1602. ECRESULT er;
  1603. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_COUNT, pi.lItems);
  1604. if (er == erSuccess)
  1605. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_CONTENT_UNREAD, pi.lUnread);
  1606. if (er == erSuccess)
  1607. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_ASSOC_CONTENT_COUNT, pi.lAssoc);
  1608. if (er == erSuccess)
  1609. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_DELETED_MSG_COUNT, pi.lDeleted);
  1610. if (er == erSuccess)
  1611. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_DELETED_ASSOC_MSG_COUNT, pi.lDeletedAssoc);
  1612. if (er == erSuccess)
  1613. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_SUBFOLDERS, pi.lFolders);
  1614. if (er == erSuccess)
  1615. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_FOLDER_CHILD_COUNT, pi.lFolders);
  1616. if (er == erSuccess)
  1617. er = UpdateFolderCount(lpDatabase, ulFolderId, PR_DELETED_FOLDER_COUNT, pi.lDeletedFolders);
  1618. return er;
  1619. }
  1620. ECRESULT ApplyFolderCounts(ECDatabase *lpDatabase, const std::map<unsigned int, PARENTINFO> &mapFolderCounts) {
  1621. ECRESULT er;
  1622. // Update folder counts
  1623. for (const auto &p : mapFolderCounts) {
  1624. er = ApplyFolderCounts(lpDatabase, p.first, p.second);
  1625. if (er != erSuccess)
  1626. return er;
  1627. }
  1628. return erSuccess;
  1629. }
  1630. static ECRESULT LockFolders(ECDatabase *lpDatabase, bool bShared,
  1631. const std::set<unsigned int> &setParents)
  1632. {
  1633. std::string strQuery;
  1634. if(setParents.empty())
  1635. return erSuccess;
  1636. strQuery = "SELECT * FROM properties WHERE hierarchyid IN(";
  1637. for (auto pa_id : setParents) {
  1638. strQuery += stringify(pa_id);
  1639. strQuery += ",";
  1640. }
  1641. strQuery.resize(strQuery.size()-1);
  1642. strQuery += ") ";
  1643. if (bShared)
  1644. strQuery += "LOCK IN SHARE MODE";
  1645. else
  1646. strQuery += "FOR UPDATE";
  1647. return lpDatabase->DoSelect(strQuery, NULL);
  1648. }
  1649. static ECRESULT BeginLockFolders(ECDatabase *lpDatabase, unsigned int ulTag,
  1650. const std::set<std::string> &setIds, unsigned int ulFlags)
  1651. {
  1652. ECRESULT er = erSuccess;
  1653. DB_RESULT lpDBResult;
  1654. DB_ROW lpDBRow = NULL;
  1655. std::set<unsigned int> setMessages;
  1656. std::set<unsigned int> setFolders;
  1657. std::set<std::string> setUncached;
  1658. std::set<unsigned int> setUncachedMessages;
  1659. unsigned int ulId;
  1660. std::string strQuery;
  1661. // See if we can get the object IDs for the passed objects from the cache
  1662. for (const auto &s : setIds) {
  1663. if (g_lpSessionManager->GetCacheManager()->QueryObjectFromProp(ulTag, s.size(),
  1664. reinterpret_cast<unsigned char *>(const_cast<char *>(s.data())), &ulId) != erSuccess) {
  1665. setUncached.insert(s);
  1666. continue;
  1667. }
  1668. if (ulTag == PROP_ID(PR_SOURCE_KEY)) {
  1669. setFolders.insert(ulId);
  1670. } else if (ulTag != PROP_ID(PR_ENTRYID)) {
  1671. assert(false);
  1672. continue;
  1673. }
  1674. EntryId eid(s);
  1675. try {
  1676. if (eid.type() == MAPI_FOLDER)
  1677. setFolders.insert(ulId);
  1678. else if (eid.type() == MAPI_MESSAGE)
  1679. setMessages.insert(ulId);
  1680. else
  1681. assert(false);
  1682. } catch (runtime_error &e) {
  1683. ec_log_err("eid.type(): %s\n", e.what());
  1684. assert(false);
  1685. }
  1686. }
  1687. if(!setUncached.empty()) {
  1688. // For the items that were uncached, go directly to their parent (or the item itself for folders) in the DB
  1689. strQuery = "SELECT hierarchyid, hierarchy.type, hierarchy.parent FROM indexedproperties JOIN hierarchy ON hierarchy.id=indexedproperties.hierarchyid WHERE tag = " + stringify(ulTag) + " AND val_binary IN(";
  1690. for (auto i = setUncached.cbegin(); i != setUncached.cend(); ++i) {
  1691. if (i != setUncached.cbegin())
  1692. strQuery += ",";
  1693. strQuery += lpDatabase->EscapeBinary(*i);
  1694. }
  1695. strQuery += ")";
  1696. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1697. if(er != erSuccess)
  1698. return er;
  1699. while((lpDBRow = lpDatabase->FetchRow(lpDBResult))) {
  1700. if(lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL)
  1701. continue;
  1702. if(atoui(lpDBRow[1]) == MAPI_MESSAGE)
  1703. setFolders.insert(atoui(lpDBRow[2]));
  1704. else if(atoui(lpDBRow[1]) == MAPI_FOLDER)
  1705. setFolders.insert(atoui(lpDBRow[0]));
  1706. }
  1707. }
  1708. // For the items that were cached, but messages, find their parents in the cache first
  1709. for (const auto i : setMessages) {
  1710. unsigned int ulParent = 0;
  1711. if (g_lpSessionManager->GetCacheManager()->QueryParent(i, &ulParent) == erSuccess)
  1712. setFolders.insert(ulParent);
  1713. else
  1714. setUncachedMessages.insert(i);
  1715. }
  1716. // Query uncached parents from the database
  1717. if(!setUncachedMessages.empty()) {
  1718. strQuery = "SELECT parent FROM hierarchy WHERE id IN(";
  1719. for (auto i = setUncachedMessages.cbegin();
  1720. i != setUncachedMessages.cend(); ++i) {
  1721. if(i != setUncachedMessages.begin())
  1722. strQuery += ",";
  1723. strQuery += stringify(*i);
  1724. }
  1725. strQuery += ")";
  1726. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1727. if(er != erSuccess)
  1728. return er;
  1729. while((lpDBRow = lpDatabase->FetchRow(lpDBResult))) {
  1730. if(lpDBRow[0] == NULL)
  1731. continue;
  1732. setFolders.insert(atoui(lpDBRow[0]));
  1733. }
  1734. }
  1735. // Query objectid -> parentid for messages
  1736. if (setFolders.empty())
  1737. // No objects found that we can lock, fail.
  1738. return KCERR_NOT_FOUND;
  1739. er = lpDatabase->Begin();
  1740. if(er != erSuccess)
  1741. return er;
  1742. return LockFolders(lpDatabase, ulFlags & LOCK_SHARED, setFolders);
  1743. }
  1744. /**
  1745. * Begin a new transaction and lock folders
  1746. *
  1747. * Sourcekey of folders should be passed in setFolders.
  1748. *
  1749. */
  1750. ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const std::set<SOURCEKEY>& setFolders, unsigned int ulFlags)
  1751. {
  1752. std::set<std::string> setIds;
  1753. std::copy(setFolders.begin(), setFolders.end(), std::inserter(setIds, setIds.begin()));
  1754. return BeginLockFolders(lpDatabase, PROP_ID(PR_SOURCE_KEY), setIds, ulFlags);
  1755. }
  1756. /**
  1757. * Begin a new transaction and lock folders
  1758. *
  1759. * EntryID of messages and folders to lock can be passed in setEntryIds. In practice, only the folders
  1760. * in which the messages reside are locked.
  1761. */
  1762. ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const std::set<EntryId>& setEntryIds, unsigned int ulFlags)
  1763. {
  1764. std::set<std::string> setIds;
  1765. std::copy(setEntryIds.begin(), setEntryIds.end(), std::inserter(setIds, setIds.begin()));
  1766. return BeginLockFolders(lpDatabase, PROP_ID(PR_ENTRYID), setIds, ulFlags);
  1767. }
  1768. ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const EntryId &entryid, unsigned int ulFlags)
  1769. {
  1770. std::set<EntryId> set;
  1771. // No locking needed for stores
  1772. try {
  1773. if (entryid.type() == MAPI_STORE)
  1774. return lpDatabase->Begin();
  1775. } catch (runtime_error &e) {
  1776. ec_log_err("entryid.type(): %s\n", e.what());
  1777. return KCERR_INVALID_PARAMETER;
  1778. }
  1779. set.insert(entryid);
  1780. return BeginLockFolders(lpDatabase, set, ulFlags);
  1781. }
  1782. ECRESULT BeginLockFolders(ECDatabase *lpDatabase, const SOURCEKEY &sourcekey, unsigned int ulFlags)
  1783. {
  1784. return BeginLockFolders(lpDatabase, std::set<SOURCEKEY>({sourcekey}), ulFlags);
  1785. }
  1786. // Prepares child property data. This can be passed to ReadProps(). This allows the properties of child objects of object ulObjId to be
  1787. // retrieved with far less SQL queries, since this function bulk-receives the data. You may pass EITHER ulObjId OR ulParentId to retrieve an object itself, or
  1788. // children of an object.
  1789. ECRESULT PrepareReadProps(struct soap *soap, ECDatabase *lpDatabase, bool fDoQuery, bool fUnicode, unsigned int ulObjId, unsigned int ulParentId, unsigned int ulMaxSize, ChildPropsMap *lpChildProps, NamedPropDefMap *lpNamedPropDefs)
  1790. {
  1791. ECRESULT er = erSuccess;
  1792. unsigned int ulSize;
  1793. struct propVal sPropVal;
  1794. unsigned int ulChildId;
  1795. ECStringCompat stringCompat(fUnicode);
  1796. std::string strQuery;
  1797. DB_RESULT lpDBResult;
  1798. DB_ROW lpDBRow = NULL;
  1799. DB_LENGTHS lpDBLen = NULL;
  1800. if (ulObjId == 0 && ulParentId == 0)
  1801. return KCERR_INVALID_PARAMETER;
  1802. if(fDoQuery) {
  1803. // although we don't always use the names columns, we need to join anyway to check for existing nameids
  1804. // we may never stream propids > 0x8500 without the names data
  1805. if (ulObjId != 0)
  1806. strQuery = "SELECT " PROPCOLORDER ", hierarchyid, names.nameid, names.namestring, names.guid "
  1807. "FROM properties FORCE INDEX (PRIMARY) ";
  1808. else
  1809. strQuery = "SELECT " PROPCOLORDER ", hierarchy.id, names.nameid, names.namestring, names.guid "
  1810. "FROM properties FORCE INDEX (PRIMARY) "
  1811. "JOIN hierarchy FORCE INDEX (parenttypeflags) "
  1812. "ON properties.hierarchyid=hierarchy.id ";
  1813. strQuery +=
  1814. "LEFT JOIN names "
  1815. "ON (properties.tag-0x8501)=names.id ";
  1816. if (ulObjId)
  1817. strQuery += "WHERE hierarchyid=" + stringify(ulObjId);
  1818. else
  1819. strQuery += "WHERE hierarchy.parent=" + stringify(ulParentId);
  1820. strQuery += " AND (tag <= 0x8500 OR names.id IS NOT NULL)";
  1821. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1822. if(er != erSuccess)
  1823. return er;
  1824. } else {
  1825. er = lpDatabase->GetNextResult(&lpDBResult);
  1826. if(er != erSuccess)
  1827. return er;
  1828. }
  1829. while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
  1830. unsigned int ulPropTag;
  1831. lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
  1832. if(lpDBLen == NULL) {
  1833. ec_log_crit("PrepareReadProps(): FetchRowLengths failed");
  1834. return KCERR_DATABASE_ERROR; /* this should never happen */
  1835. }
  1836. ulPropTag = PROP_TAG(atoi(lpDBRow[FIELD_NR_TYPE]),atoi(lpDBRow[FIELD_NR_TAG]));
  1837. if (PROP_ID(ulPropTag) > 0x8500 && lpNamedPropDefs) {
  1838. std::pair<NamedPropDefMap::iterator, bool> resInsert = lpNamedPropDefs->insert(NamedPropDefMap::value_type(ulPropTag, NAMEDPROPDEF()));
  1839. if (resInsert.second) {
  1840. // New entry
  1841. if (lpDBLen[FIELD_NR_NAMEGUID] != sizeof(resInsert.first->second.guid)) {
  1842. ec_log_err("PrepareReadProps(): record size mismatch");
  1843. return KCERR_DATABASE_ERROR;
  1844. }
  1845. memcpy(&resInsert.first->second.guid, lpDBRow[FIELD_NR_NAMEGUID], sizeof(resInsert.first->second.guid));
  1846. if (lpDBRow[FIELD_NR_NAMEID] != NULL) {
  1847. resInsert.first->second.ulKind = MNID_ID;
  1848. resInsert.first->second.ulId = atoui((char*)lpDBRow[FIELD_NR_NAMEID]);
  1849. } else if (lpDBRow[FIELD_NR_NAMESTR] != NULL) {
  1850. resInsert.first->second.ulKind = MNID_STRING;
  1851. resInsert.first->second.strName.assign((char*)lpDBRow[FIELD_NR_NAMESTR], lpDBLen[FIELD_NR_NAMESTR]);
  1852. } else {
  1853. return KCERR_INVALID_TYPE;
  1854. }
  1855. }
  1856. }
  1857. // server strings are always unicode, for unicode clients.
  1858. if (fUnicode) {
  1859. if (PROP_TYPE(ulPropTag) == PT_STRING8)
  1860. ulPropTag = CHANGE_PROP_TYPE(ulPropTag, PT_UNICODE);
  1861. else if (PROP_TYPE(ulPropTag) == PT_MV_STRING8)
  1862. ulPropTag = CHANGE_PROP_TYPE(ulPropTag, PT_MV_UNICODE);
  1863. }
  1864. ulChildId = atoui(lpDBRow[FIELD_NR_MAX]);
  1865. auto iterChild = lpChildProps->find(ulChildId);
  1866. if (iterChild == lpChildProps->cend()) {
  1867. CHILDPROPS sChild;
  1868. sChild.lpPropTags = new DynamicPropTagArray(soap);
  1869. sChild.lpPropVals = new DynamicPropValArray(soap, 20);
  1870. // First property for this child
  1871. iterChild = lpChildProps->insert(ChildPropsMap::value_type(ulChildId, sChild)).first;
  1872. }
  1873. er = iterChild->second.lpPropTags->AddPropTag(ulPropTag);
  1874. if(er != erSuccess)
  1875. return er;
  1876. er = GetPropSize(lpDBRow, lpDBLen, &ulSize);
  1877. if(er == erSuccess && (ulMaxSize == 0 || ulSize < ulMaxSize)) {
  1878. // the size of this property is small enough to send in the initial loading sequence
  1879. er = CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &sPropVal);
  1880. if(er != erSuccess)
  1881. continue;
  1882. er = FixPropEncoding(soap, stringCompat, Out, &sPropVal);
  1883. if (er != erSuccess)
  1884. continue;
  1885. iterChild->second.lpPropVals->AddPropVal(sPropVal);
  1886. if (!soap)
  1887. FreePropVal(&sPropVal, false);
  1888. }
  1889. }
  1890. if(fDoQuery) {
  1891. if (ulObjId != 0)
  1892. strQuery = "SELECT " MVPROPCOLORDER ", hierarchyid, names.nameid, names.namestring, names.guid "
  1893. "FROM mvproperties ";
  1894. else
  1895. strQuery = "SELECT " MVPROPCOLORDER ", hierarchy.id, names.nameid, names.namestring, names.guid "
  1896. "FROM mvproperties "
  1897. "JOIN hierarchy "
  1898. "ON mvproperties.hierarchyid=hierarchy.id ";
  1899. strQuery +=
  1900. "LEFT JOIN names "
  1901. "ON (mvproperties.tag-0x8501)=names.id ";
  1902. if (ulObjId != 0)
  1903. strQuery += "WHERE hierarchyid=" + stringify(ulObjId) +
  1904. " AND (tag <= 0x8500 OR names.id IS NOT NULL) "
  1905. " GROUP BY hierarchyid, tag";
  1906. else
  1907. strQuery += "WHERE hierarchy.parent=" + stringify(ulParentId) +
  1908. " AND (tag <= 0x8500 OR names.id IS NOT NULL) "
  1909. "GROUP BY tag, mvproperties.type";
  1910. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1911. if(er != erSuccess)
  1912. return er;
  1913. } else {
  1914. er = lpDatabase->GetNextResult(&lpDBResult);
  1915. if(er != erSuccess)
  1916. return er;
  1917. }
  1918. // Do MV props
  1919. while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
  1920. lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
  1921. if(lpDBLen == NULL) {
  1922. ec_log_crit("PrepareReadProps(): FetchRowLengths failed(2)");
  1923. return KCERR_DATABASE_ERROR; /* this should never happen */
  1924. }
  1925. if (lpNamedPropDefs) {
  1926. unsigned int ulPropTag = PROP_TAG(atoi(lpDBRow[FIELD_NR_TYPE]),atoi(lpDBRow[FIELD_NR_TAG]));
  1927. if (PROP_ID(ulPropTag) > 0x8500) {
  1928. std::pair<NamedPropDefMap::iterator, bool> resInsert = lpNamedPropDefs->insert(NamedPropDefMap::value_type(ulPropTag, NAMEDPROPDEF()));
  1929. if (resInsert.second) {
  1930. // New entry
  1931. if (lpDBLen[FIELD_NR_NAMEGUID] != sizeof(resInsert.first->second.guid)) {
  1932. ec_log_crit("PrepareReadProps(): record size mismatch(2)");
  1933. return KCERR_DATABASE_ERROR;
  1934. }
  1935. memcpy(&resInsert.first->second.guid, lpDBRow[FIELD_NR_NAMEGUID], sizeof(resInsert.first->second.guid));
  1936. if (lpDBRow[FIELD_NR_NAMEID] != NULL) {
  1937. resInsert.first->second.ulKind = MNID_ID;
  1938. resInsert.first->second.ulId = atoui((char*)lpDBRow[FIELD_NR_NAMEID]);
  1939. } else if (lpDBRow[FIELD_NR_NAMESTR] != NULL) {
  1940. resInsert.first->second.ulKind = MNID_STRING;
  1941. resInsert.first->second.strName.assign((char*)lpDBRow[FIELD_NR_NAMESTR], lpDBLen[FIELD_NR_NAMESTR]);
  1942. } else {
  1943. return KCERR_INVALID_TYPE;
  1944. }
  1945. }
  1946. }
  1947. }
  1948. ulChildId = atoui(lpDBRow[FIELD_NR_MAX]);
  1949. auto iterChild = lpChildProps->find(ulChildId);
  1950. if (iterChild == lpChildProps->cend()) {
  1951. CHILDPROPS sChild;
  1952. sChild.lpPropTags = new DynamicPropTagArray(soap);
  1953. sChild.lpPropVals = new DynamicPropValArray(soap, 20);
  1954. // First property for this child
  1955. iterChild = lpChildProps->insert(ChildPropsMap::value_type(ulChildId, sChild)).first;
  1956. }
  1957. er = CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &sPropVal);
  1958. if(er != erSuccess)
  1959. continue;
  1960. er = FixPropEncoding(soap, stringCompat, Out, &sPropVal);
  1961. if (er != erSuccess)
  1962. continue;
  1963. er = iterChild->second.lpPropTags->AddPropTag(sPropVal.ulPropTag);
  1964. if(er != erSuccess)
  1965. continue;
  1966. iterChild->second.lpPropVals->AddPropVal(sPropVal);
  1967. if (!soap)
  1968. FreePropVal(&sPropVal, false);
  1969. }
  1970. return erSuccess;
  1971. }
  1972. ECRESULT FreeChildProps(std::map<unsigned int, CHILDPROPS> *lpChildProps)
  1973. {
  1974. for (const auto &cp : *lpChildProps) {
  1975. if (cp.second.lpPropVals)
  1976. delete cp.second.lpPropVals;
  1977. if (cp.second.lpPropTags)
  1978. delete cp.second.lpPropTags;
  1979. }
  1980. lpChildProps->clear();
  1981. return erSuccess;
  1982. }
  1983. } /* namespace */